Nacos 中配置 Map 类型,不香!

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: Nacos 中配置 Map 类型,不香!

大家好,我是君哥。


最近在使用 Nacos 过程中遇到一个场景,配置的字符串可以解析成 Map 类型使用,有一个配置如下:

map:
  test: key1:value1,key2:value2,key3:value3


后来有同事建议 Nacos 可以直接配置成 Map 类型,后台使用 Java Map 类型获取就可以。配置如下:

map:
  test:
    key1: value1
    key2: value2
    key3: value3


下面就来分享一下配置 Map 类型的过程中遇到的问题。

1 使用 Bean 方式获取配置

1.1 使用方式

参考网上的一些案例,第一个方式是把读取到的 Map 作为一个 Spring 的 Bean,一看代码就明白了。

@Bean
@ConfigurationProperties(prefix = "map.test")
public Map<String, String> mapping() {
    return new HashMap<>();
}

1.2 槽点

这样确实可以把 Nacos 中读取到的配置转换成 Map 类型,但一个致命的槽点就是 mapping 这个 bean 不能自动刷新。这样如果修改了 Nacos 中配置,要想让配置生效,就必须重启应用服务,这怎么能接受呢?

2 ConfigurationProperties

2.1 使用方式

直接使用 @Value 和 @NacosValue 是获取不到值的。下面的这种方式,类的定义上加注解 @ConfigurationProperties,再定义一个变量,名称跟 Nacos 中配置的后缀一样,这样是可以获取到 Map 类型的配置的。

@Component
@RefreshScope
@ConfigurationProperties(prefix = "map")
public class NacosRefresh {
    private Logger logger = LoggerFactory.getLogger(getClass());
    public void setTest(Map<String, String> test) {
        this.test = test;
    }
    private Map<String, String> test;
}

注意:上面的 setTest 方法是必须要的,不然 test 变量取不到值。

2.2 槽点

这样确实可以把 Nacos 中读取到的配置转换成 Map 类型,但是跟第一种方式一样,定义的 Map 类型变量不能自动刷新。

3 使用监听

Nacos API 提供了监听功能,可以监听配置的变化,对变化进行处理,只要在监听方法上增加 @NacosConfigListener 这个注解就可以生效。见下面代码:

@Service
public class NacosListener {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private Map<String, String> map = new HashMap<>();
    @NacosConfigListener(dataId = "maptest.yaml",groupId = "DEFAULT_GROUP")
    public void listener(String context){
        logger.info("================listener context:{}", context);
        if (StringUtils.isBlank(context)){
            return;
        }
        Yaml yaml = new Yaml();
        Map<String, Object> contextMap = yaml.load(context);
        Map<String, Object> map = (Map<String, Object>)contextMap.get("map");
        if (CollectionUtils.isEmpty(map)){
            return;
        }
        Map<String, String> test = (Map<String, String>) map.get("test");
        if (CollectionUtils.isEmpty(test)){
            return;
        }
        map.clear();
        map.putAll(test);
        map.forEach((k,v) -> logger.info("Entry in map, key:{},value:{}", k, v));
    }
}


这段代码是从 Nacos 配置中解析出 Map 类型的配置,然后把配置 put 到本地变量 map。这个也可以完成我们的需求,但是有几点需要注意。

3.1 服务重启

如果服务重启了,本地变量 map 拉不到值。因为上面监听的逻辑并没有走,即使在 Nacos 上重新发布一下,也不行。

微信图片_20221213112034.jpg

上面的监听方法,只有在 Nacos 配置发生变化并且发布后才会触发,比如 map.test 配置改变如下:

map:
  test:
    key1: value1
    key2: value2
    key3: value3
    key4: value4

3.2 并发问题

上面监听的代码里面,需要把本地变量 map 先 clear 然后再 putAll,如果这两个方法调用中间发生了线程上下文切换,读取线程可能会因为从 map 中取不到值而发生异常。

4 改进

上面讲解了使用 Nacos 配置 Map 类型的坑,不过使用 Nacos 配置 Map 类型也有个好处,不用解析字符串,直接可以转成 Map 类型。

4.1 使用字符串

完全不使用 Map 类型了,改成配置字符串,配置如下:

map:
  test: key1:value1,key2:value2,key3:value3


解析代码如下:

@NacosValue(value = "${map.test}", autoRefreshed = true)
private String mapTest;
public String get(String key){
    String[] keys = mapTest.split(",");
    for (String item : keys){
        if (!item.contains(key)){
            continue;
        }
        return item.split(":")[1];
    }
    return null;
}


这种写法的好处是不用监听 Nacos,配置改变后 mapTest 变量自动刷新,缺点是每次调用 get 方法都需要解析 mapTest 这个字符串。

4.2 刷新本地 Map

把解析字符串的结果放到本地变量 map 上,考虑到 Nacos 中配置可能会发生变化,用定时线程池每 1 秒刷新一次,代码如下:

private Map<String, String> map = new HashMap<>();
@NacosValue(value = "${map.test}", autoRefreshed = true)
private String mapTest;
@PostConstruct
public void refreshLocalMap(){
    ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(1);
    scheduled.scheduleAtFixedRate(() -> refresh(), 0, 1000, TimeUnit.MILLISECONDS);
}
public void refresh(){
    String[] keys = mapTest.split(",");
    for (String item : keys){
        String[] kv = item.split(":");
        map.put(kv[0], kv[1]);
    }
}


这个写法的好处是不用每次调用都解析字符串,而是由异步线程每秒钟刷新。但是也有两个问题:

  • 需要一个定时线程池,会消耗 CPU 资源。
  • refresh 方法是每秒执行一次,会有短暂的本地变量和 Nacos 配置不一致的问题。

5 总结

Nacos 中配置 Map 类型确实不香,主要原因是刷新不方便。但是对于配置不需要刷新的场景,还是很有好处的,尤其是 key 比较多的时候,比解析字符串方便很多,而且 Hash 的时间复杂度是 o(1) ,在数据结构中是最优秀的。


对于需要刷新的场景,无论使用哪种方案,都有优缺点,没有最好的,只有最适合的,要根据系统的业务场景来做选择。


相关文章
|
15天前
|
负载均衡 应用服务中间件 Nacos
Nacos配置中心
Nacos配置中心
47 1
Nacos配置中心
|
11天前
|
监控 Java 测试技术
Nacos 配置中心变更利器:自定义标签灰度
本文是对 MSE Nacos 应用自定义标签灰度的功能介绍,欢迎大家升级版本进行试用。
|
14天前
|
网络安全 Nacos 开发者
Nacos作为流行的微服务注册与配置中心,“节点提示暂时不可用”是常见的问题之一
Nacos作为流行的微服务注册与配置中心,其稳定性和易用性备受青睐。然而,“节点提示暂时不可用”是常见的问题之一。本文将探讨该问题的原因及解决方案,帮助开发者快速定位并解决问题,确保服务的正常运行。通过检查服务实例状态、网络连接、Nacos配置、调整健康检查策略等步骤,可以有效解决这一问题。
28 4
|
14天前
|
Java 网络安全 Nacos
Nacos作为流行的微服务注册与配置中心,其稳定性和易用性备受青睐。
Nacos作为流行的微服务注册与配置中心,其稳定性和易用性备受青睐。然而,实际使用中常遇到“客户端不发送心跳检测”的问题。本文深入探讨该问题的原因及解决方案,帮助开发者快速定位并解决问题,确保服务正常运行。通过检查客户端配置、网络连接、日志、版本兼容性、心跳策略、注册状态、重启应用和环境变量等步骤,系统地排查和解决这一问题。
36 3
|
14天前
|
安全 Nacos 数据库
Nacos是一款流行的微服务注册与配置中心,但直接暴露在公网中可能导致非法访问和数据库篡改
Nacos是一款流行的微服务注册与配置中心,但直接暴露在公网中可能导致非法访问和数据库篡改。本文详细探讨了这一问题的原因及解决方案,包括限制公网访问、使用HTTPS、强化数据库安全、启用访问控制、监控和审计等步骤,帮助开发者确保服务的安全运行。
29 3
|
23天前
|
SQL 关系型数据库 数据库连接
"Nacos 2.1.0版本数据库配置写入难题破解攻略:一步步教你排查连接、权限和配置问题,重启服务轻松解决!"
【10月更文挑战第23天】在使用Nacos 2.1.0版本时,可能会遇到无法将配置信息写入数据库的问题。本文将引导你逐步解决这一问题,包括检查数据库连接、用户权限、Nacos配置文件,并提供示例代码和详细步骤。通过这些方法,你可以有效解决配置写入失败的问题。
48 0
|
2月前
|
负载均衡 Java Nacos
SpringCloud基础2——Nacos配置、Feign、Gateway
nacos配置管理、Feign远程调用、Gateway服务网关
SpringCloud基础2——Nacos配置、Feign、Gateway
|
4月前
|
Java Nacos 数据库
使用 nacos 搭建注册中心及配置中心
使用 nacos 搭建注册中心及配置中心
101 5
|
4月前
|
NoSQL Java Nacos
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
144 3
|
3月前
|
安全 Nacos 数据安全/隐私保护
升级指南:从Nacos 1.3.0 到 2.3.0,并兼容 Seata 的鉴权配置
本文详细介绍了如何在微服务环境下从 Nacos 1.3.0 升级到 2.3.0,并确保 Seata 各版本的兼容性。作者小米分享了升级过程中的关键步骤,包括备份配置、更新鉴权信息及验证测试等,并解答了常见问题。通过这些步骤,可以帮助读者顺利完成升级并提高系统的安全性与一致性。
123 8
升级指南:从Nacos 1.3.0 到 2.3.0,并兼容 Seata 的鉴权配置