nacos配置中心介绍

徐键
介绍

    nacos是阿里18年开源的作为配置中心及服务发现的中间件,本文主要讨论其作为配置中心的一些功能及实现。 下图描述了spring cloud config Appollo Nacos三个配置中心的一些特性,个人比较倾向于nacos,因为nacos 部署、使用特别方便,跟spring整个生态无缝结合。

nacos架构图:

使用

    nacos由server client mysql组成,server包含配置管理及client获取配置的功能,数据由mysql存储,server通过http服务暴露配置,client使用http长轮询来获取server端配置的变更,通过监听器对变更数据进行处理,nacos也支持udp方式来推送配置的变更。来看下nacos的使用,现在主流应用都使用spring boot或者spring cloud, 所以看下spring boot及spring cloud结合nacos的使用。

spring boot使用方式:



1.引入依赖:
依赖的版本0.1.x对应spring boot 1.x,0.2.x对应spring boot 2.x,由于项目使用log4j2日志,所以需要去除logback依赖,并且log4j2日志的版本需要在2.7.0以上,推荐2.10.0版本

<dependency>  
    <groupId>com.alibaba.boot</groupId>
    <artifactId>nacos-config-spring-boot-starter</artifactId>
    <version>0.1.1</version>
    <exclusions>
        <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </exclusion>
        <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </exclusion>
    </exclusions>
</dependency>  


2.增加配置:
nacos.config.server-addr=127.0.0.1:8848
nacos.config.context-path=nacos

3.启动类增加annotation
@NacosPropertySource(dataId = "spring-boot-demo.properties", autoRefreshed = true)
@NacosPropertySource(dataId = "share.properties", groupId = Constants.DEFAULTGROUP, autoRefreshed = true, properties = @NacosProperties(namespace = "af0a428a-ff43-4971-8b3d-4eaf4ffcfbc9"))
可以配置多个注解获取多个属性源。注解中定义的信息是拉取配置需要用到的信息,dataId(必传)指属性源的id,一般是项目名称+profile+文件后缀,groupId指配置的分组,默认DEFAULT
GROUP,autoRefreshed 指定是否刷新,namespace 指定配置命名空间,比如不同区域服务可以使用不同命名空间。不同的分组和命名空间可以对配置进行不同维度的管理。

4.@Value(value = "${xx}") spring bean中使用该注解注入配置,不过使用这种方式无法实时刷新,因为sprint boot监听更新只是刷新了上下文environment对象中的配置,没有重新生成spring bean。

spring cloud使用方式:

1.引入依赖:
依赖的版本0.1.x对应spring boot 1.x,0.2.x对应spring boot 2.x,并且log4j2日志的版本需要在2.7.0以上,推荐2.10.0版本

<dependency>  
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>0.1.1.RELEASE</version>
</dependency>  


2.增加配置:
spring.cloud.nacos.config.context-path=nacos
spring.cloud.nacos.config.shared-dataids=common.properties
spring.cloud.nacos.config.refreshable-dataids=common.properties
spring.cloud.nacos.config.ext-config[0].dataId=ext.properties
spring.cloud.nacos.config.ext-config[0].refresh=true
spring.cloud.nacos.config.namespace=af0a428a-ff43-4971-8b3d-4eaf4ffcfbc9
spring.cloud.nacos.config.prefix=${spring.application.name}
spring.cloud.nacos.config.file-extension=properties
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
如上配置需要添加到bootstrap配置文件才可生效,可定义shared-dataids获取一些公共配置,使用ext-config获取额外配置,通过prefix和file-extension确定dataId,以上配置的namespace会作用于所有shared、 ext和应用的dataId,不像spring boot方式,不同的NacosPropertySource注解中的namespace会分别生效

3.spring bean增加@RefreshScope,通过@Value(value = "${xx}") 注入配置,该方式可以达到实时刷新效果,spring cloud通过监听变更会销毁bean,下次使用生成bean时重新注入属性达到刷新效果。

nacos实现

client拉取配置和感知配置变更的核心接口是ConfigService,定义了发布、删除、获取配置,添加删除监听器、获取server状态这些接口

String getConfig(String dataId, String group, long timeoutMs) throws NacosException;

void addListener(String dataId, String group, Listener listener) throws NacosException;

boolean publishConfig(String dataId, String group, String content) throws NacosException;

boolean removeConfig(String dataId, String group) throws NacosException;

void removeListener(String dataId, String group, Listener listener);

String getServerStatus();  

实现类NacosConfigService对配置的增删改查操作通过一个HttpAgent来访问server暴露的http服务来实现,NacosConfigService内部通过ClientWorker类来对不同http请求获取的配置进行check刷新

final ScheduledExecutorService executor;  
final ExecutorService executorService;  
/**
 * groupKey -> cacheData
 */
AtomicReference<Map<String, CacheData>> cacheMap = new AtomicReference<Map<String, CacheData>>(  
    new HashMap<String, CacheData>());

ClientWorker内部的核心成员如上,cacheMap 维护了不同groupKey(通过dataId,group,namespace组成)到CacheData(配置数据)映射,每次注册监听器之后把对应配置信息放入这个map,之后通过executorService每10ms提交一个LongPollingRunnable的线程

executor.scheduleWithFixedDelay(new Runnable() {  
    public void run() {
        try {
            checkConfigInfo();
        } catch (Throwable e) {
            LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
        }
    }
}, 1L, 10L, TimeUnit.MILLISECONDS);

checkConfigInfo方法如下

public void checkConfigInfo() {  
    // 分任务
    int listenerSize = cacheMap.get().size();
    // 向上取整为批数
    int longingTaskCount = (int)Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
    if (longingTaskCount > currentLongingTaskCount) {
        for (int i = (int)currentLongingTaskCount; i < longingTaskCount; i++) {
            // 要判断任务是否在执行 这块需要好好想想。 任务列表现在是无序的。变化过程可能有问题
            executorService.execute(new LongPollingRunnable(i));
        }
        currentLongingTaskCount = longingTaskCount;
    }
}

通过executorService提交一个长轮询检查线程,代码比较长就不贴了,流程就是对每个CacheData(配置数据),检查配置当前版本跟本地配置文件最后变更时间是否一致确定配置是否刷新,刷新则通知监听器做对应处理,然后使用http请求(默认超时30s)server端这个地址/v1/cs/config/listener(长轮询),server端这个地址会保持连接,如果配置没有变更,请求会在停留30s后返回,如果有配置更变,请求会马上返回,client接收到配置变更,会更新本地配置文件并且更新版本号。

server端通过LongPollingService来完成保持连接的能力,核心成员如下:

final ScheduledExecutorService scheduler;

/**
 * 长轮询订阅关系
 */
final Queue<ClientLongPolling> allSubs;  

ClientLongPolling是一个线程,通过scheduler默认30s定时处理请求,如果server端接收到配置变更请求,则通过发布LocalDataChangeEvent事件,LongPollingService继承监听器,触发监听执行DataChangeTask,内部通过allSubs维护的异步请求上下文AsyncContext直接返回response。

spring boot集成nacos通过NacosPropertySourcePostProcessor,该类实现BeanFactoryPostProcessor及EnvironmentAware,通过nacos client获取配置放入spring 上下文,通过是否刷新来决定是否注册监听。

spring cloud通过NacosPropertySourceLocator,在PropertySourceBootstrapConfiguration这个ApplicationContextInitializer里加载属性源,监听ApplicationReadyEvent事件后为属性源注册监听处理变更。

使用感受

server端本地可通过standalone模式启动,也可源码直接运行,方便调试,client使用也很方便,支持配置分组和命名空间的隔离,支持配置刷新回滚,配置灰发功能还有待开发,权限管理不足,总体使用还是非常简单方便,spring 生态集成度很高,比较推荐使用。