头图

在 Nacos console 修改了配置以后,服务端底层怎么存储配置?客户端怎么知道配置修改了?怎么通知集群其他节点?让我来揭开它神秘的面纱。

服务端接收配置更新请求

在控制台页面更新一项配置,看看控制台发送了什么请求给服务端。

image-20241120140118352

image-20241120140048490

控制台发送了一个 POST 请求:/nacos/v1/cs/configs,在官方 API指南 可以找到 API 定义。

image-20241120140623329

我 Nacos 源码是 2.* 版本,但是打开的 console 发布配置请求的 API 还是 1.* 版本。

官方说v2 是兼容 v1 的,在源码的 Controller 层面两个版本使用的也是相同的 Service 类处理请求。

请求进入com.alibaba.nacos.config.server.controller.ConfigController#publishConfig,封装配置和请求信息后调用ConfigOperationService#publishConfig

image-20241120142833789

publishConfig方法做了两件事:

  • 添加或更新配置信息到数据库
  • 发布配置数据变更事件ConfigDataChangeEvent,这是客户端能感知配置更新的根本原因

image-20241121165044280

ConfigDataChangeEvent被两个类监听:

  • DumpService: 将配置信息转存到本地磁盘
  • AsyncNotifyService:通知集群其他节点和订阅配置的客户端配置发生变更

DumpService:转存配置信息到本地

DumpService在构造函数中订阅了ConfigDataChangeEvent事件,当监听到事件发生,调用handleConfigDataChange方法。

image-20241121170312201

我回看了一下前面的文章,一些不重要的方法也有截图,白白的浪费了大家的阅读时间,对于简单的方法后面只会列出方法调用链路
  • 👇handleConfigDataChange(Event event)
  • 👇dumpFormal(String dataId, String group, String tenant, long lastModified, String handleIp)

dumpFormal方法作用是转存正式数据到本地磁盘和缓存,向任务管理器dumpTaskMgr提交了一个 任务DumpTask

image-20241121171502273

dumpTaskMgrTaskManager实例,在DumpService的构造函数中创建了该实例,并且为它指定了DumpProcessor作为默认任务处理器

image-20241122095303734

TaskManager类本身没有实现执行任务的方法,而是继承自 NacosDelayTaskExecuteEngine, 来看下他是如何执行任务的。

在构造函数中创建单线程执行器,确保任务都能处理成功,并且每个任务之间间隔 100 毫秒。

image-20241121173736126

执行器会一直执行ProcessRunnableProcessRunnable实现了Runnable接口,在run方法内调用processTaks方法处理任务

image-20241121174039790

通过 taskKey 获取NacosTaskProcessor类型的处理器,调用处理器的process方法处理任务。这里实现了任务重试机制,如果执行失败则放入队列稍后执行。

NacosTaskProcessor是一个处理器接口,他有很多实现,分别用来处理不同的任务

image-20241121175209799

dump 正式数据的任务则是由上面指定的默认任务处理器DumpProcessor来处理。

调用处理器的的process方法处理任务:

  • 👇process(NacosTask task)
  • 👇DumpConfigHandler.configDump(build.build())
  • 👇ConfigCacheService.dump(dataId, group, namespaceId, content, lastModified, event.getType(), event.getEncryptedDataKey())
  • 👇dumpWithMd5(dataId, group, tenant, content, null, lastModifiedTs, type, encryptedDataKey)
  • 👇ConfigDiskServiceFactory.getInstance().saveToDisk(dataId, group, tenant, content)

调用saveToDisk方法保存到磁盘;updateMd5方法更新本地缓存的 md5和最后更新时间,并发布LocalDataChangeEvent事件。

image-20241122173301754

进入com.alibaba.nacos.config.server.service.dump.disk.ConfigRawDiskService#saveToDisk, 可以看到targetFile就是nacos.home下要更新的文件路径,向文件写入新的配置信息。image-20241122173750569

继续看updateMd5方法,更新本地JVM缓存中的配置以后,发布LocalDataChangeEvent本地数据变更事件,这个事件的目的就是告诉客户端:配置发生了变更

image-20241122174633242

AsyncNotifyService:通知集群节点

上面DumpService把修改后的配置更新到数据库,磁盘,JVM缓存了,但是这都是在本机发生的,集群的其他节点还是旧的配置呢,那么如何同步呢?

AsyncNotifyService异步通知服务就发挥作用了,它也监听了ConfigDataChangeEvent事件,所以当配置变更时,它就负责通知其它节点。

image-20241124205802480

当事件发生时,调用handleConfigDataChangeEvent()

  • 获取除了自己以外的集群成员
  • 为每个成员创建一个NotifySingleRpcTask放入同一队列中
  • 创建一个AsyncRpcTask去处理队列中的任务

image-20241124205934568

AsyncRpcTask调用executeAsyncRpcTask()处理任务

  • 依次从队列取出任务
  • 构建集群同步请求体ConfigChangeClusterSyncRequest,集群其他节点收到此类型请求,会进行数据同步
  • 检查集群成员节点健康状态
  • 调用configClusterRpcClientProxy.syncConfigChange()通知配置变更

image-20241124210552163

集群成员收到请求会做什么呢

ConfigChangeClusterSyncRequestHandler继承了RequestHandler,处理ConfigChangeClusterSyncRequest类型的请求。

  • 从请求体中拿到配置修改的关键信息,比如dataId,group
  • 调用DumpService.dump()
  • dump()调用dumpFormal()转存配置信息到本地

image-20241124211050621

通知客户端配置变更

上面在DumpService小节中讲到,更新了本地JVM缓存以后,发布了LocalDataChangeEvent本地数据变更事件。

一共有两处监听了LocalDataChangeEvent本地数据变更事件

  • RpcConfigChangeNotifier
  • LongPollingService

RpcConfigChangeNotifier

RpcConfigChangeNotifier订阅了LocalDataChangeEvent事件,事件发生时调用configDataChanged方法通知配置的监听者。

image-20241122175117634

  • 首先获取监听配置的客户端列表、
  • 创建通知请求ConfigChangeNotifyRequest,客户端收到这个请求后,会重新获取配置
  • 为每个客户端创建RpcPushTask

image-20241124192118777

然后将任务放入执行器准备执行。

  • 👇ConfigExecutor.scheduleClientConfigNotifier(retryTask, retryTask.getTryTimes() * 2, TimeUnit.SECONDS)

Rpc推送任务

RpcPushTask实现了Runnable接口,它的run方法做了两件事:

  • 检查TPS,避免服务器资源耗尽。官方称为反脆弱,可参考反脆弱插件image-20241124185123706
  • 推送请求并设置回调

调用RpcPushService.pushWithCallback()发送GRPC请求通知客户端配置变更

image-20241124190040297

在客户端的ClientWorker中注册了ConfigChangeNotifyRequest的处理器,当收到请求后调用notifyListenConfig()更新配置。

在《nacos源码分析-客户端启动与配置动态更新的实现细节》中讲过ClientWorker 主要用于封装与 Nacos 配置服务的交互逻辑,提供配置的获取、监听和更新等功能。

image-20241124192540984

LongPollingService

Nacos1.*版本 是基于长轮询机制监听配置变更,客户端调用/nacos/v1/cs/configs/listenerapi监听配置(参考API指南)。

当配置发生变更时,也需要通知那些通过长轮询监听的客户端。

从下图可以看到本地数据变更事件LocalDataChangeEvent发生时,创建了一个DataChangeTask放入执行器执行。

image-20241124201940623

DataChangeTask遍历全部订阅者,如果客户端监听了修改的配置 key,那么移除和客户端的订阅关系,因为客户端即将接收响应,然后向客户端发送响应

image-20241124203348938

调用sendResponse()响应客户端请求

调用generateResponse()返回修改的配置 key 给客户端

image-20241124203703381

原理图

ProcessOn 地址:www.processon.com/diagraming/…

Nacos配置变更通知集群节点和客户端.png

总结

现在可以来解答开头的问题了。

1.服务端底层怎么存储配置?

  • 添加或更新配置信息到数据库
  • 发布配置数据变更事件ConfigDataChangeEvent
  • DumpService监听到ConfigDataChangeEvent事件,将配置保存到本地磁盘和JVM缓存

2.客户端怎么知道配置修改了?

  • 本地缓存更新后,发布LocalDataChangeEvent事件
  • RpcConfigChangeNotifier监听到本地数据变更事件LocalDataChangeEvent
  • 创建通知请求ConfigChangeNotifyRequest,发起 GRPC调用通知客户端
  • 通过长轮询监听的客户端,则由LongPollingService携带更新的配置 key 响应客户端

3.怎么通知集群其他节点?

  • AsyncNotifyService监听配置数据变更事件ConfigDataChangeEvent
  • 监听到事件发生后,向集群成员发送ConfigChangeClusterSyncRequest,告知变更的配置信息

王大锤
0 声望0 粉丝