目录
1、NacosConfigBootstrapConfiguration
1.1.1 Nacos Agent 验证serverAddr
1.1.2 GetServerListTask 更新endpoint
老规矩,springboot 组件,先看spring.factories。
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,\
com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration
1、NacosConfigBootstrapConfiguration
bootstrapContext是主applicationContext的父上下文,它优先于applicationContext创建。
它共初始化了三个bean,第一个是NacosConfigProperties。第二个是NacosConfigManager,第三个是nacosPropertySourceLocator。
1.1 NacosConfigManager
它的构造方法,主要就是调用创建configService。
static ConfigService createConfigService(
NacosConfigProperties nacosConfigProperties) {
//并发加锁,防止多次创建
if (Objects.isNull(service)) {
synchronized (NacosConfigManager.class) {
try {
if (Objects.isNull(service)) {
service = NacosFactory.createConfigService(
//将配置的一些关键属性转换做参数
nacosConfigProperties.assembleConfigServiceProperties());
}
}
catch (NacosException e) {
log.error(e.getMessage());
throw new NacosConnectionFailureException(
nacosConfigProperties.getServerAddr(), e.getMessage(), e);
}
}
}
return service;
}
跟踪NacosFactory.createConfigService代码,调用
public static ConfigService createConfigService(Properties properties) throws NacosException {
try {
//反射创建ConfigService,我们直接看创建类的构造方法
Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService");
Constructor constructor = driverImplClass.getConstructor(Properties.class);
ConfigService vendorImpl = (ConfigService) constructor.newInstance(properties);
return vendorImpl;
} catch (Throwable e) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
}
}
public NacosConfigService(Properties properties) throws NacosException {
ValidatorUtils.checkInitParam(properties);
//编码格式
String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);
if (StringUtils.isBlank(encodeTmp)) {
//默认utf-8
this.encode = Constants.ENCODE;
} else {
this.encode = encodeTmp.trim();
}
//重新初始化 namespace,如果是阿里云环境,并配置了从云环境获取namespace那么就取云环境的,
//没有则取配置文件的,配置文件没有则空
initNamespace(properties);
//①重点,初始化agent
this.agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
②
this.agent.start();
//③启动一个线程组 对比配置文件变化的,10毫秒
this.worker = new ClientWorker(this.agent, this.configFilterChainManager, properties);
}
1.1.1 Nacos Agent 验证serverAddr
先看看ServerHttpAgent的构造方法:
public ServerHttpAgent(Properties properties) throws NacosException {
//当配置了serverAddr 直接解析它组成serviceUrl 集合
//没配serverAddr则取endPoint,组成获取serviceUrl的链接
//两个属性都没配则会报 endpoint is blank
this.serverListMgr = new ServerListManager(properties);
//登录用的,目前我的server是走的默认用户名密码,因为nacos将只是内网可访问。当没配
//username时,后面的登录也将不验证
this.securityProxy = new SecurityProxy(properties, NACOS_RESTTEMPLATE);
this.namespaceId = properties.getProperty(PropertyKeyConst.NAMESPACE);
//maxRetry,编码等属性初始化
init(properties);
//nacos server或者用户是否有效,其中包含token有效期内不会重复发http
this.securityProxy.login(this.serverListMgr.getServerUrls());
// 构建定时线程池,并给它的线程赋名字和守护线程属性,核心线程1,最大int.max
this.executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.config.security.updater");
t.setDaemon(true);
return t;
}
});
//5秒 定时间隔执行,验证nacos server 是否在线或者配置的用户是否正确
this.executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
securityProxy.login(serverListMgr.getServerUrls());
}
}, 0, this.securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
}
ServerHttpAgent就是包装一些http请求,并加工nacos server的服务端地址,并启动一个定时任务不断验证server是否在线。MetricsHttpAgent通过包装它对外提供服务。
1.1.2 GetServerListTask 更新endpoint
在构造configService时,在开始构造了一个agent后,还会调用start方法,start方法调用的是serverListManager的start。
public synchronized void start() throws NacosException {
if (isStarted || isFixed) {
return;
}
//获取serverlist线程
GetServerListTask getServersTask = new GetServerListTask(addressServerUrl);
//注意这里的条件,serverUrls为空才会run,而serverUrls来源于serverAddr配置,也就是如果
//我们配置了serverAddr不是用的endpoint模式获取nacos server,这个线程也不会运行
for (int i = 0; i < initServerlistRetryTimes && serverUrls.isEmpty(); ++i) {
getServersTask.run();
try {
this.wait((i + 1) * 100L);
} catch (Exception e) {
LOGGER.warn("get serverlist fail,url: {}", addressServerUrl);
}
}
if (serverUrls.isEmpty()) {
LOGGER.error("[init-serverlist] fail to get NACOS-server serverlist! env: {}, url: {}", name,
addressServerUrl);
throw new NacosException(NacosException.SERVER_ERROR,
"fail to get NACOS-server serverlist! env:" + name + ", not connnect url:" + addressServerUrl);
}
//线程池运行线程,30秒运行检查一次 server 地址变更
this.executorService.scheduleWithFixedDelay(getServersTask, 0L, 30L, TimeUnit.SECONDS);
isStarted = true;
}
run方法就是执行了http方法从endpoint查询nacos server是否变更,如果变更将更新当前属性serverUrls。
1.1.3 ClientWorker轮询配置文件变化
public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager,
final Properties properties) {
//就是上面说的MetricsHttpAgent 包装类
this.agent = agent;
this.configFilterChainManager = configFilterChainManager;
// Initialize the timeout parameter
init(properties);
//checkConfigInfo的线程池
this.executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
t.setDaemon(true);
return t;
}
});
//线程数:cpu核心线程数
this.executorService = Executors
.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());
t.setDaemon(true);
return t;
}
});
//每10毫秒检查一次config配置
this.executor.scheduleWithFixedDelay(new Runnable() {
@Override
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() {
// Dispatch taskes.
int listenerSize = cacheMap.size();
// Round up the longingTaskCount.
int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
if (longingTaskCount > currentLongingTaskCount) {
for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
// The task list is no order.So it maybe has issues when changing.
executorService.execute(new LongPollingRunnable(i));
}
currentLongingTaskCount = longingTaskCount;
}
}
因为是异步任务,当初始化完ClientWorker时,cacheMap是空的,因此是不会往下执行多线程任务的,那么这个cacheMap是什么时候存放进去数据的呢,我们可以在当前类查看这个cacheMap它在哪里都被引用了,它是一个ConcrrentHashMap,哪些方法是put数据的。源码太多,画了个简易流程图
总结:
1、当配置serverAddr时:启动线程每5秒查看server是否在线
当配置endpoint时:每30秒启动线程从endpoint中更新server地址
2、从server里查出配置信息,按dataId+groupId 在本地文件系统存储 snap文件
3、每10毫秒启动线程对比 clientWorker中的cacheMap内容 和拉取 server 内容进行对比,使用MD5对比。MD5加密的内容就是文件内容。
4、启动监听器,监听ApplicationReadyEvent事件,启动完成后,会从本地文件将内容加载出来,存放到clientWorker的cacheMap内。