Nacos本地缓存配置实践

背景

前段时间做了一个项目,由于nacos的不稳定性,导致了生产环境拉取配置失败了,从而影响了生产环境的业务。

于是团队就做了一个大胆的决定,为了避免因为依赖nacos导致业务的不可用,我们一致决定,在本地做nacos的配置缓存。本篇文章只讨论nacos配置缓存的实践,不涉及注册中心。

经过几轮测试和验证,最终这个方案落地了,做了实际的故障演练,把nacos断了之后,应用是能正确的拿到缓存的配置。

下面我们就来看看如何实现的。

缓存配置的步骤

实现本地缓存配置数据的步骤,我这里做了几个具体步骤的总结:

  1. 首先,有一个Nacos服务器,这个是必须的,并且已经创建了相应的配置。
  2. 其次,在程序中引入Nacos的客户端SDK依赖,这个也是必须的。
  3. 然后使用SDK从Nacos服务器获取配置数据,这个也是必须的。
  4. 重点步骤来了,我的实现方式是,将从nacos服务端获取到的配置数据保存在本地缓存中,可以使用内存、文件或其他缓存机制。
  5. 然后在需要访问配置数据的地方,首先检查本地缓存是否存在数据。如果存在,直接使用缓存数据;如果不存在,再从Nacos服务器获取最新的配置数据。
  6. 最后就是要定期刷新本地缓存,以确保获取到的配置数据是最新的。

这只是其中一种实现方式,还有主动轮训的方式,这里就不讲了。

本次实践不涉及强实时要求的配置的更新。

测试代码

先用java代码测试一下可行性,试一下在本地缓存配置数据:

java 复制代码
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.utils.StringUtils;

import java.util.Properties;
import java.util.concurrent.Executor;

public class NacosConfigCacheExample {
    private static final String SERVER_ADDR = "localhost:8848";
    private static final String GROUP_ID = "DEFAULT_GROUP";
    private static final String DATA_ID = "example-config";
    private static final String CACHE_FILE_PATH = "/path/to/cache/file";

    private static ConfigService configService;

    public static void main(String[] args) throws NacosException {
        // 创建Nacos配置服务实例
        Properties properties = new Properties();
        properties.put("serverAddr", SERVER_ADDR);
        configService = NacosFactory.createConfigService(properties);

        // 从Nacos服务器获取配置数据
        String configData = configService.getConfig(DATA_ID, GROUP_ID, 5000);

        // 将配置数据保存到本地缓存文件
        saveConfigToCache(configData);

        // 注册配置变更监听器
        configService.addListener(DATA_ID, GROUP_ID, new Listener() {
            @Override
            public void receiveConfigInfo(String configInfo) {
                // 当配置发生变化时,更新本地缓存
                saveConfigToCache(configInfo);
            }

            @Override
            public Executor getExecutor() {
                return null; // 使用默认的执行器
            }
        });

        // 从本地缓存获取配置数据
        String cachedConfigData = readConfigFromCache();
        if (StringUtils.isNotBlank(cachedConfigData)) {
            // 使用缓存数据
            System.out.println("Using cached config data: " + cachedConfigData);
        } else {
            // 缓存数据为空,从Nacos服务器获取最新的配置数据
            String latestConfigData = configService.getConfig(DATA_ID, GROUP_ID, 5000);
            System.out.println("Using latest config data: " + latestConfigData);
        }
    }

    private static void saveConfigToCache(String configData) {
        // 将配置数据保存到本地缓存文件
        // 这里使用了简单的文件存储方式,你可以根据实际需求选择其他缓存机制
        try (FileWriter writer = new FileWriter(CACHE_FILE_PATH)) {
            writer.write(configData);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static String readConfigFromCache() {
        // 从本地缓存文件读取配置数据
        // 这里使用了简单的文件存储方式,你可以根据实际需求选择其他缓存机制
        try (BufferedReader reader = new BufferedReader(new FileReader(CACHE_FILE_PATH))) {
            StringBuilder configData = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                configData.append(line);
            }
            return configData.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

跑了一下,是可以成功拿到数据,并缓存到文件中的。同时在缓存数据为空的时候,是可以从nacos读取最新的数据的。

那么下面就在正式的spring项目中开干。

Spring Boot代码DEMO

项目是用的Spring Boot,那么在项目中引入nacos这种简单的操作就浅写一下吧。

xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

然后,在配置文件中添加Nacos相关的配置这种简单的操作也浅写一下吧。

yaml 复制代码
spring:
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        group: DEFAULT_GROUP
        namespace: your-namespace

接下来,创建一个配置类,用于获取和缓存配置数据:

typescript 复制代码
import com.alibaba.nacos.api.config.annotation.NacosConfigListener;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

@Component
public class ConfigCache {
    private static final String CACHE_FILE_PATH = "/path/to/cache/file";

    private Map<String, String> configDataMap = new HashMap<>();

    public String getConfigData(String dataId) {
        String configData = configDataMap.get(dataId);
        if (configData == null || configData.isEmpty()) {
            configData = readConfigFromCache(dataId);
        }
        return configData;
    }

    @NacosConfigListener(dataId = "example-config", groupId = "DEFAULT_GROUP")
    public void onConfigUpdate(String config, String dataId) {
        configDataMap.put(dataId, config);
        saveConfigToCache(config, dataId);
    }

    private void saveConfigToCache(String configData, String dataId) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(CACHE_FILE_PATH + dataId))) {
            writer.write(configData);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String readConfigFromCache(String dataId) {
        try (BufferedReader reader = new BufferedReader(new FileReader(CACHE_FILE_PATH + dataId))) {
            StringBuilder configData = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                configData.append(line);
            }
            return configData.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

这里配置不多的话,可以用一个map来存储。可以加快获取的速度。

当map中没有的时候,再去文件中读取缓存的配置。这里展示的是用文件存储的。

实际的项目中,最终是替换成了redis来存储的。

文件存储的问题在于是,配置很多的话,会产生很多个文件。如果写一个文件的话,当配置很多的时候IO效率又很慢。

用map存在内存中的问题是,如果配置很多,会占用很多内存。但好处是它查询很快。

最后,在业务代码中可以使用ConfigCache类来获取配置数据:

kotlin 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ExampleController {
    @Autowired
    private ConfigCache configCache;

    @GetMapping("/config")
    public String getConfigData() {
        return configCache.getConfigData("example-config");
    }
}

总结一下里面的坑

实际测试下来,最终发现,不同的项目会用不同的缓存。

只用MAP

在配置很少的那种服务里,比如只有几个或者十几个,就简单的用map缓存在内存中就可以了。

  • 但它的问题是,如果是服务本身挂了,那么重启后,这些map中的数据就丢失了。
  • 另外一个问题是,服务有多个实例的时候,每个实例中缓存的map是不一样的。当正好没有缓存的实例去访问nacos拿数据的时候,nacos正好挂了,那么一样会失败。虽然概率很低。

用MAP+文件

对于配置多,也不在乎读取效率的服务里,就用MAP+文件。其实我们的项目中最终只有一个服务用了这种模式。

  • 它的问题在于,配置很多的服务中,随着服务的使用,MAP占用的内存会越来越大。
  • 用文件存储的问题在于,它有一定的概率会出现IO问题。导致读写文件失败。所以兜底的操作就是最终都去nacos读取。
  • 另外一个问题在于,写到文件中的配置都是明文的。运维或者开发登陆到机器上就能看到配置的信息。有一定的安全风险。

用Redis

绝大部分的服务最终都用了这种模式。因为用了redis,所以也不需要用MAP了。经过测试,从MAP中读取,和从Redis读取的时间差可以忽略不计,几乎无感知。

  • 最明显的缺点肯定就是会增加一个redis,在整体架构上多了一环,那就多了一个不稳定因素。因为redis也有挂掉的可能。
  • 这个redis到底是独立的只用于配置,还是和其他的缓存的redis合用一个,也是一个纠结的问题。不过实际的决策还是要根据业务来。
  • 还有一个坑是,如果redis和nacos在同一个可用区,那这个可用区挂掉之后,会导致两边都拿不到数据,一样导致不可用。所以redis和nacos本身的高可用部署也是需要考虑的。

通用问题

  • 数据一致性问题。缓存中的数据和nacos中的数据可能是不一致的。虽然有监听更新,但在大数据量和频繁读取的场景中,也有可能导致不一致的情况出现。所以这种对一致性要求很高的场景,建议做一些一致性保障的逻辑。
  • 如果Nacos配置数据量非常大或者数量众多,如果是用的缓存到本地文件的方式,可能会占用大量的存储空间,并且读取和写入大量的配置数据可能会影响应用的性能。所以量大的场景不太建议用文件缓存。
  • 如果在配置中有一些敏感数据,比如密码、敏感数据等,用缓存的方式都可能增加新的安全风险。比如缓存到文件是会被读取到的,缓存到redis通常都是明文存储的。

最后总结

考虑了多种实现方式,最终选了上述的方式。另外一种实现方式是,可以主动轮训。

但它的问题在于,主动轮训会需要确定好时间间隔,太短可能占用应用的性能,太长可能数据更新不及时等。另外就是轮训也有数据一致性问题。

还有一种缓存方式,就是把量大的配置做分批缓存。比如每批缓存500个配置。由于和我们的业务不太匹配,就没有尝试这种方式。

总之,我们做nacos本地缓存是为了避免nacos故障导致的业务不可用。每个企业每个项目每个业务遇到的问题可能不一样,大家应该根据自己的实际场景,来寻找最适合的解决方案。

相关推荐
追逐时光者3 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_3 小时前
敏捷开发流程-精简版
前端·后端
苏打水com4 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
西瓜er4 小时前
JAVA:Spring Boot 集成 FFmpeg 实现多媒体处理
java·spring boot·ffmpeg
间彧5 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧5 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧5 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧5 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧5 小时前
Spring Cloud Gateway详解与应用实战
后端
EnCi Zheng6 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端