【深入理解SpringCloud微服务】深入理解nacos配置中心(三)——服务端启动与获取配置源码分析
【深入理解SpringCloud微服务】深入理解nacos配置中心(三)——服务端启动与获取配置源码分析
- 原理回顾
- 服务端启动
- 获取配置
- 源码分析
- 服务端启动
- ExternalDumpService#init()
- ConfigCacheService#dump()
- 获取配置
原理回顾
服务端启动
我们在《宏观理解nacos配置中心原理》的文章说到了,服务端启动的原理。
会调用DumpService查询MySQL,然后把查询到的配置信息dump到磁盘成为一个个的配置文件,每个DataId对应一个配置文件。
获取配置
然后服务端接收到获取配置文件的请求时,从磁盘中查询对应文件返回,而不是去查询数据库。
源码分析
服务端启动
ExternalDumpService#init()
我们以nacos配置的数据库是MySQL的情况为例子,DumpService的实现类就是ExternalDumpService。
nacos配置中心其实就是一个SpringBoot应用,它的启动就是SpringBoot启动。然后由于ExternalDumpService的init()方法被@PostConstruct注解修饰,因此在初始化ExternalDumpService时init()方法会被调用。
@PostConstruct
@Override
protected void init() throws Throwable {
dumpOperate(processor, dumpAllProcessor, dumpAllBetaProcessor, dumpAllTagProcessor);
}
protected void dumpOperate(DumpProcessor processor, DumpAllProcessor dumpAllProcessor,
DumpAllBetaProcessor dumpAllBetaProcessor, DumpAllTagProcessor dumpAllTagProcessor) throws NacosException {
...
dumpConfigInfo(dumpAllProcessor);
...
}
private void dumpConfigInfo(DumpAllProcessor dumpAllProcessor) throws IOException {
...
dumpAllProcessor.process(new DumpAllTask());
...
}
经过一轮调用,进入到DumpAllProcessor#process方法。
@Override
public boolean process(NacosTask task) {
// 查询最大的id值
long currentMaxId = persistService.findConfigMaxId();
long lastMaxId = 0;
while (lastMaxId < currentMaxId) {
// 分页查询MySQL
Page<ConfigInfoWrapper> page = persistService.findAllConfigInfoFragment(lastMaxId, PAGE_SIZE);
if (page != null && page.getPageItems() != null && !page.getPageItems().isEmpty()) {
for (ConfigInfoWrapper cf : page.getPageItems()) {
long id = cf.getId();
lastMaxId = Math.max(id, lastMaxId);
...
// 调用ConfigCacheService把查询到的每一条记录dump到磁盘成一个配置文件
ConfigCacheService.dump(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getContent(),
cf.getLastModified(), cf.getType(), cf.getEncryptedDataKey());
...
}
...
}
...
}
return true;
}
ConfigCacheService#dump()
public static boolean dump(String dataId, String group, String tenant, String content, long lastModifiedTs,
String type, String encryptedDataKey) {
// 根据dataId, group, tenant三元组算出一个groupKey(其实就是拼接)
String groupKey = GroupKey2.getKey(dataId, group, tenant);
...
try {
// 根据配置文件内容算出一个md5值
final String md5 = MD5Utils.md5Hex(content, Constants.ENCODE);
...
// 把配置文件内容dump到磁盘成为一个配置文件
DiskUtil.saveToDisk(dataId, group, tenant, content);
}
// 更新缓存中的md5值
updateMd5(groupKey, md5, lastModifiedTs, encryptedDataKey);
return true;
} ...
}
ConfigCacheService的dump方法首先根据配置文件内容算出一个md5值,然后把配置文件内容dump到磁盘成为一个配置文件,最后更新缓存中的md5值。
public static void updateMd5(String groupKey, String md5, long lastModifiedTs, String encryptedDataKey) {
// 从一个ConcurrentHashMap<String, CacheItem>中根据groupKey获取CacheItem
CacheItem cache = makeSure(groupKey, encryptedDataKey, false);
// 如果CacheItem等于空,或者CacheItem中的md5值与刚算出的md5值不匹配,则进入分支
if (cache.md5 == null || !cache.md5.equals(md5)) {
// 更新CacheItem的md5值
cache.md5 = md5;
cache.lastModifiedTs = lastModifiedTs;
// 发布一个LocalDataChangeEvent事件,异步通知客户端
NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey));
}
}
ConfigCacheService的updateMd5方法首先从从一个ConcurrentHashMap<String, CacheItem>中根据groupKey获取CacheItem,然后判断如果CacheItem等于空或者CacheItem中的md5值与刚算出的md5值不匹配则进入分支,由于是刚启动,因此CacheItem肯定等于空,所以会进入if分支。然后if分支中更新CacheItem的md5值,并发布一个发布一个LocalDataChangeEvent事件异步通知客户端。
这里NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey))发布的事件不是Spring的事件监听机制,而是nacos自己封装的事件监听机制。
获取配置
由于已经查询MySQL然后dump到磁盘成配置文件了,因此当接收到获取配置的RPC请求时,就不需要再去查询MySQL,而是读取磁盘的配置文件即可。
我们可以根据客户端发送GRPC远程调用时创建的request对象的类型,找到服务端处理该请求的Handler。我们在上一篇文章《客户端启动源码分析》中说到request请求对象的类型是ConfigQueryRequest。
通过ConfigQueryRequest,就可以找到处理获取配置请求的方法在ConfigQueryRequestHandler的handle方法。
public ConfigQueryResponse handle(ConfigQueryRequest request, RequestMeta meta) throws NacosException {
try {
return getContext(request, meta, request.isNotify());
} catch (...) {...}
}
ConfigQueryRequestHandler的handle方法调用getContext方法。
private ConfigQueryResponse getContext(ConfigQueryRequest configQueryRequest, RequestMeta meta, boolean notify)
throws UnsupportedEncodingException {
...
// 从磁盘根据dataId, group, tenant三元组读取配置文件
file = DiskUtil.targetFile(dataId, group, tenant);
...
// 读取配置文件中的内容,设置到response对象中
content = readFileContent(file);
response.setContent(content);
...
return response;
}