使用Nacos实现分布式配置中心

本文详细介绍了分布式配置中心的作用,对比了SpringCloudConfig、Apollo和Nacos等常见配置中心,并重点讲解了如何基于Nacos实现配置管理,包括配置实时推送、版本管理、权限控制等功能。此外,还展示了如何自定义配置文件名称,通过重写配置加载过程和创建自定义客户端来满足特定需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分布式配置中心

​ 分布式配置中心主要是解决在分布式的场景下,一个服务可能会有多台实例,在传统的单体架构下是每一台服务器上都会有一个配置文件,我们可以通过修改配置文件来实现配置的更改,但在分布式架构下,我们要实现该效果就必须每台实例都更改一遍,这样不仅增加了很高的运维成本,而且还可能会因为遗漏导致一系列别的问题发生。因此分布式配置中心就应运而生,通过分布式配置中心的方式来解决该问题。

常见的配置中心

​ 配置中心对比

功能点SpringCloud ConfigApolloNacos
开源时间2014.92016.52018.6
配置实时推送支持(SpringCloud Bus)支持(HTTP长轮训1s内)支持(HTTP长轮训1s内)
版本管理支持(GIt)支持支持
配置回滚支持(GIt)支持支持
灰度发布支持支持待支持
权限管理支持支持待支持
多集群支持支持支持
监控查询支持支持支持
语言Javago/c++/java/python/php/.net/openApipython/java/node.js/openApi
单机部署config-server+git+springcloud bus(支持配置实时推送)apollo-quikstart+mysqlnacos单节点
集群部署config-server+git+mq(部署复杂)config+admin+portal+mysql(部署复杂)nacos+mysql(部署简单)
配置格式校验不支持支持支持
通信协议HTTP/AMQPHTTPHTTP

​ 对于目前SpringCloud生态来讲选型比较简单,Alibaba使用Nacos,NetFlix使用SpringCloud Config

基于Nacos实现

Nacos文档

  • pom中引入jar包:
<dependency>
  <groupId>io.terminus.common</groupId>
  <artifactId>terminus-spring-boot-starter-configcenter</artifactId>
</dependency>
  • 增加bootstrap.yml配置文件,注意必须在该配置文件中声明nacos的config配置:
spring:
  cloud:
    nacos:
      config:
        server-addr: ${NACOS_ADDRESS:127.0.0.1:8848} #配置中心地址
        group: ${CONFIGCENTER_GROUP_NAME:}
        namespace: ${CONFIGCENTER_TENANT_ID:}
        file-extension: yaml
  profiles:
    active: dev
  • 在Nacos对应的namespace下创建基于以下规则的配置文件
${prefix}-${spring.profiles.active}.${file-extension}

如图所示:
在这里插入图片描述

  • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
  • spring.profiles.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档注意:当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}
  • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 propertiesyaml 类型。
  • 通过 Spring Cloud 原生注解 @RefreshScope 实现配置自动更新:
@RestController
@RequestMapping("/config")
@RefreshScope
public class ConfigController {

    @Value("${useLocalCache:false}")
    private boolean useLocalCache;

    @RequestMapping("/get")
    public boolean get() {
        return useLocalCache;
    }
}
  • 手动修改Nacos中的配置文件,可以发现Java代码中获取到的值也发生变更

日志配置

​ 当前配置了Nacos作为配置中心后,会发现后台不断的在打印日志,因此我们需要修改配置中心的日志打印级别

logging
  level:
    com:
      alibaba:
        nacos:
          client:
            naming: error

自定义处理

如果我们有需求要摒弃,Nacos默认的配置规则,比如我们自定义自己的配置文件名称的,那么此时我们能够怎么实现呢?

  • 首先重写Nacos的配置文件类SdkPropertySourceLocator加载过程:
@Order(0)
public class SdkPropertySourceLocator implements PropertySourceLocator {
    public SdkPropertySourceLocator() {
    }

    public PropertySource<?> locate(Environment environment) {
        this.initNacosConfigProperties(environment);
        return null;
    }

    @Bean
    private NacosConfigProperties initNacosConfigProperties(Environment environment) {
        NacosConfigProperties properties = new NacosConfigProperties();
        properties.setServerAddr(environment.getProperty("CONFIGCENTER_ADDRESS"));
        properties.setNamespace(environment.getProperty("CONFIGCENTER_TENANT_NAME"));
        properties.setGroup(environment.getProperty("CONFIGCENTER_GROUP_NAME"));
        List<Config> extConfig = new ArrayList();
        Config config1 = new Config();
        config1.setDataId("default_project.yml");
        config1.setGroup("default_project");
        config1.setRefresh(true);
        extConfig.add(config1);
        Config config2 = new Config();
        config2.setDataId("application.yml");
        config2.setGroup(environment.getProperty("CONFIGCENTER_GROUP_NAME"));
        config2.setRefresh(true);
        extConfig.add(config2);
        Config config3 = new Config();
        config3.setDataId("extra.yml");
        config3.setGroup(environment.getProperty("CONFIGCENTER_GROUP_NAME"));
        config3.setRefresh(true);
        extConfig.add(config3);
        properties.setExtConfig(extConfig);
        return properties;
    }
}
  • 增加自动转配类ConfigCenterAutoConfiguration
@Configuration
@ConditionalOnClass({ConfigCenterClient.class})
public class ConfigCenterAutoConfiguration {
    private static final Logger log = LoggerFactory.getLogger(ConfigCenterAutoConfiguration.class);
    @Autowired
    private NacosConfigProperties nacosConfigProperties;

    public ConfigCenterAutoConfiguration() {
    }

    @Bean
    public ConfigCenterClient configCenterClient() {
        if (!StringUtils.isEmpty(this.nacosConfigProperties.getServerAddr())) {
            return new NacosConfigCenterClient(this.nacosConfigProperties);
        } else {
            log.info("no config-center client find!");
            return null;
        }
    }
}
  • 增加配置中心客户端文件
public class NacosConfigCenterClient implements ConfigCenterClient {
    private static final Logger log = LoggerFactory.getLogger(NacosConfigCenterClient.class);
    private final ConfigService configService;
    private final NacosConfigProperties configProperties;
    private final Map<String, Listener> listenerMap = new HashMap();

    public NacosConfigCenterClient(NacosConfigProperties configProperties) {
        this.configService = configProperties.configServiceInstance();
        this.configProperties = configProperties;
    }

    public synchronized boolean publishConfig(String key, String value) {
        try {
            if ("application.yml".equals(key)) {
                log.error("publishConfig key cannot be \"application.yml\"!");
                return false;
            } else {
                return this.configService.publishConfig(key, this.configProperties.getGroup(), value);
            }
        } catch (NacosException var4) {
            log.error("publishConfig error, key:" + key + ", e:" + var4.getMessage(), var4);
            return false;
        }
    }

    public synchronized boolean deleteConfig(String key) {
        try {
            return this.configService.removeConfig(key, this.configProperties.getGroup());
        } catch (NacosException var3) {
            log.error("deleteConfig error, key:" + key + ", e:" + var3.getMessage(), var3);
            return false;
        }
    }

    public String getConfig(String key, String defaultValue) {
        try {
            String content = this.configService.getConfig(key, this.configProperties.getGroup(), 3000L);
            return StringUtils.isEmpty(content) ? defaultValue : content;
        } catch (NacosException var4) {
            log.error("getConfig error, key:" + key + ", e:" + var4.getMessage(), var4);
            return defaultValue;
        }
    }

    public synchronized boolean updateSpringConfig(String key, String value) {
        try {
            String configs = this.getConfig("application.yml", "");
            if (StringUtils.isEmpty(configs)) {
                log.info("updateSpringConfig can't find key:" + key);
                return false;
            } else {
                Map<String, Object> propertyMap = (Map)(new Yaml()).load(configs);
                if (!CollectionUtils.isEmpty(propertyMap) && propertyMap.containsKey(key)) {
                    propertyMap.put(key, value);
                    return this.configService.publishConfig("application.yml", this.configProperties.getGroup(), map2Yaml(propertyMap));
                } else {
                    log.info("updateSpringConfig can't find key:" + key);
                    return true;
                }
            }
        } catch (Exception var5) {
            log.error("updateSpringConfig error, key:" + key + ", e:" + var5.getMessage(), var5);
            return false;
        }
    }

    public boolean saveSpringConfig(String key, String value) {
        try {
            String configs = this.getConfig("application.yml", "");
            if (StringUtils.isEmpty(configs)) {
                log.info("saveSpringConfig can't find key:application.yml");
                return false;
            } else {
                Map<String, Object> propertyMap = (Map)(new Yaml()).load(configs);
                if (propertyMap == null) {
                    propertyMap = new HashMap();
                }

                ((Map)propertyMap).put(key, value);
                return this.configService.publishConfig("application.yml", this.configProperties.getGroup(), map2Yaml((Map)propertyMap));
            }
        } catch (Exception var5) {
            log.error("saveSpringConfig error, key:" + key + ", e:" + var5.getMessage(), var5);
            return false;
        }
    }

    public void addListener(String key, io.terminus.common.configcenter.component.Listener listener) {
        if ("application.yml".equals(key)) {
            log.error("addListener key cannot be \"application.yml\"!");
        } else {
            try {
                this.configService.addListener(key, this.configProperties.getGroup(), this.getListener(listener));
            } catch (NacosException var4) {
                log.error("addListener error, key:" + key + ", e:" + var4.getMessage(), var4);
            }

        }
    }

    public void removeListener(String key, io.terminus.common.configcenter.component.Listener listener) {
        this.configService.removeListener(key, this.configProperties.getGroup(), this.getListener(listener));
    }

    private static String map2Yaml(Map<String, Object> map) {
        if (CollectionUtils.isEmpty(map)) {
            return "##";
        } else {
            StringBuilder sb = new StringBuilder();
            sb.append("##");
            map.entrySet().forEach((entry) -> {
                sb.append("\n" + String.valueOf(entry.getKey()) + ": " + entry.getValue());
            });
            return sb.toString();
        }
    }

    private Listener getListener(final io.terminus.common.configcenter.component.Listener listener) {
        String hashCode = String.valueOf(listener.hashCode());
        if (this.listenerMap.containsKey(hashCode)) {
            return (Listener)this.listenerMap.get(hashCode);
        } else {
            Listener nacosListener = new Listener() {
                public void receiveConfigInfo(String configInfo) {
                    listener.receiveConfigInfo(configInfo);
                }

                public Executor getExecutor() {
                    return listener.getExecutor();
                }
            };
            this.listenerMap.put(hashCode, nacosListener);
            return nacosListener;
        }
    }

    public boolean saveSpringConfig(Map<String, String> paramMap) {
        try {
            String configs = this.getConfig("application.yml", "");
            if (StringUtils.isEmpty(configs)) {
                log.info("saveSpringConfig can't find key:application.yml");
                return false;
            } else {
                Map<String, Object> propertyMap = (Map)(new Yaml()).load(configs);
                if (propertyMap == null) {
                    propertyMap = new HashMap();
                }

                ((Map)propertyMap).putAll(paramMap);
                return this.configService.publishConfig("application.yml", this.configProperties.getGroup(), map2Yaml((Map)propertyMap));
            }
        } catch (Exception var4) {
            log.error("saveSpringConfig error, e:" + var4.getMessage(), var4);
            return false;
        }
    }
}
  • 增加spring.factories配置文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration = io.terminus.common.configcenter.autoconfigure.ConfigCenterAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration = io.terminus.common.configcenter.autoconfigure.SdkPropertySourceLocator
  • 原理说明

主要实现原理就是在NacosConfigCenterClient类中,给当前的configservice注册增加了一个监听器,文件发生变化就会通知到监听器,然后进行相应的处理

    public void addListener(String key, io.terminus.common.configcenter.component.Listener listener) {
        if ("application.yml".equals(key)) {
            log.error("addListener key cannot be \"application.yml\"!");
        } else {
            try {
                this.configService.addListener(key, this.configProperties.getGroup(), this.getListener(listener));
            } catch (NacosException var4) {
                log.error("addListener error, key:" + key + ", e:" + var4.getMessage(), var4);
            }

        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值