【深入理解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@Overrideprotected 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方法。
@Overridepublic boolean process(NacosTask task) {// 查询最大的id值long currentMaxId = persistService.findConfigMaxId();long lastMaxId = 0;while (lastMaxId < currentMaxId) {// 分页查询MySQLPage<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获取CacheItemCacheItem 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;}