nacos-config工作原理解析
说明:本文是基于nacos-1.1.0版本进行解读,请注意区分。
1. 什么是nacos?
nacos官方手册上对nacos的描述比较权威,这里不再赘述,手册地址https://nacos.io/zh-cn/docs/what-is-nacos.html
2. java的sdk
对工作原理的分析是从sdk入手的,如果你没有直接使用过sdk进行编程,建议先熟悉一下,具体可参考官方手册https://nacos.io/zh-cn/docs/sdk.html
3. nacos的工作流程
3.1 getConfig()方法直接获取配置信息
通过这种方式获取配置信息,工作流程比较简单,客户端每次发送http GET请求到server端获取最新的配置信息。
3.2 通过receiveConfigInfo()从监听中回调获取
3.2.1 源码解读
// sdk编程时的主入口
public static ConfigService createConfigService(Properties properties) throws NacosException {
return ConfigFactory.createConfigService(properties);
}
public static ConfigService createConfigService(Properties properties) throws NacosException {
try {
// 这里是通过反射创建一个NacosConfigService对象并返回
// 因此,我们sdk编程的时候其实最终使用的是这个类的实例
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 {
String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);
if (StringUtils.isBlank(encodeTmp)) {
encode = Constants.ENCODE;
} else {
encode = encodeTmp.trim();
}
// 这里是初始化命名空间
initNamespace(properties);
// 初始化http agent,后续与server端通信会用到
agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
agent.start();
// 这里是客户端工作的核心,接下来我们要重点关注
worker = new ClientWorker(agent, configFilterChainManager, properties);
}
// ClientWorker里面主要是初始化两个线程池
// executor线程池中延迟10ms启动了一个线程去执行checkConfigInfo()方法
public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager, final Properties properties) {
this.agent = agent;
this.configFilterChainManager = configFilterChainManager;
// Initialize the timeout parameter
init(properties);
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;
}
});
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;
}
});
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
// 这个方法是客户端的处理核心
// 每隔10ms就会调度一次这个方法
checkConfigInfo();
} catch (Throwable e) {
LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
}
}
}, 1L, 10L, TimeUnit.MILLISECONDS);
}
// 这里是采用分批的方式防止一次请求数据过大导致失败
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;
}
}
class LongPollingRunnable implements Runnable {
private int taskId;
public LongPollingRunnable(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
List<CacheData> cacheDatas = new ArrayList<CacheData>();
List<String> inInitializingCacheList = new ArrayList<String>();
try {
// check failover config
// cacheMap:增加监听时,将具体的监听对应的data封装成CacheData缓存进cacheMap
// 在封装CacheData时,会计算出CacheData对应的taskId
for (CacheData cacheData : cacheMap.get().values()) {
if (cacheData.getTaskId() == taskId) {
cacheDatas.add(cacheData);
try {
// 检查内存中的缓存信息与文件中的缓存信息是否一致,并进行相应处理
checkLocalConfig(cacheData);
if (cacheData.isUseLocalConfigInfo()) {
// 监听对象创建时,会在对象中缓存一个当前的信息值
// 判断监听的信息是否好内存中缓存的一致,不一致就回调监听中的回调的方法
cacheData.checkListenerMd5();
}
} catch (Exception e) {
LOGGER.error("get local config info error", e);
}
}
}
// check server config
// 这里就是大名鼎鼎的长轮询
// 通过长轮询的方式,从服务端获取data变化的dataId
// 默认是30s超时
List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
// 获得变化的dataId清单之后,循环从server端查询最新的配置信息,并更新到本地的缓存文件中
for (String groupKey : changedGroupKeys) {
String[] key = GroupKey.parseKey(groupKey);
String dataId = key[0];
String group = key[1]