以一次 配置推送 解析整个交互过程,理解 Nacos 配置管理的架构
背景知识:长轮询 long-polling(简书:Long Polling长轮询详解),仅需经此一遭便可
Nacos 官方 描述了其命名的由来 Nacos: Dynamic Naming and Configuration Service;按照目前主流的使用场景,Nacos 有两个核心能力:服务注册发现(Naming)和配置中心(Configuration);而这里我们不讨论注册发现和通用基础能力,只讨论配置中心的配置变更如何达成集群的一致性。
一图胜千言
看过了不少 Nacos 的博客文章,但在阐述整体配置的功能时,都没有一个完整的图,说清楚整个集群的配置同步情况,**如何达成集群配置一致?如何做到客户端对配置的实时感知?**虽然图画得不优美,符号也不是准确,期望大体意思是能表述的。
画图水平有待持续提升~
集群成员平等,能力完全一致:Nacos-0 Nacos-1 Nacos-2 只是为了方便描述,各个客户端经过 ALB 后随机连接后端服务
MemoryCache :内存缓存配置元信息 dataId group namespace md5 betaIp 等等,不包含配置的内容
DiskFile :磁盘缓存配置的内容,获取时通过 nio channel.transfer 直接零拷贝返回网络
AsyncTask :异步通知所有集群成员,在失败时有多次重试
配置以数据库为准:管控台的所有操作直接读写数据库,DumpTask 会从数据库取出配置,然后 dump 入 MemoryCache 和 DiskFile
-
Nacos-0 处理 long-polling 的过程,holding 时会放入 queue 队列;然后等候 LocalDataChangeEvent 事件后,收到事件后告知客户端
-
Nacos-1 处理 publish-config 的过程,会先写数据库,再发出一个 ConfigDataChangeEvent ;再由 AsyncTask 告知 all Members
2.1. All Members 处理 DumpTask 的过程,主要是把 数据库为准 的配置 dump 入 MemoryCache 和 DiskFile
然后发布一个事件 LocalDataChangeEvent ,转而告知了 (1) -
Nacos-2 处理 get-config 的过程,先匹配 MemoryCache 获取配置信息,而配置内容保存在 DiskFile,它由nio channel.transfer 返回
关于 AsyncTask 通知所有集群成员 (包括自己)
- 好处是更平等地对待集群内的所有成员,发起者自身在处理 DumpTask 时不至于比其他成员提前太多;
- 若本地内优先处理,由于 LocalDataChangeEvent 让客户端长轮训返回,客户端发起配置获取时,存在延时其他节点尚未 DumpTask 结束,所以会拿到旧配置;然后再发起 long-polling 才可能感知到 md5 有变化而获取到最新配置,最糟糕情况是还没 DumpTask 结束,要 30s 超时后才获得最新变更。
关于 任意客户端 -> 任意服务端 获取配置变更的完备性
- 讨论极端场景:配置变更完 DumpTask 刚处理结束,LocalDataChangeEvent 还没开始,而 客户端此时发起 listening
- 这种情况得讨论 客户端的 ClientLongpolling 能否写入到维持队列 queue 里
- 已写入:则 DataChangeTask 就会正常返回变更通知给 客户端 ·正常
- 未写入1:存在一个 compare md5 with MemoryCache 的判断过程,由于 DumpTask 的处理已经把变更写入 MemoryCache,此时的 listening 会立马返回 ·正常
- 未写入2:若已经 compare md5 with MemoryCache 而刚好又没写入 维持队列 queue 里,那是最糟糕情况需 ·兜底
- 这种情况得讨论 客户端的 ClientLongpolling 能否写入到维持队列 queue 里
- 讨论极端场景:客户端被告知配置有变更,发起 get config 请求到另一个服务端
- 另一个服务端已经过 DumpTask ,此时最新配置获取 ·正常
- 另一个服务端尚未到 DumpTask 阶段(比较极端),此时仅获得旧配置,客户端再次发起 listening
- 再次发起 listening,服务端已经过 DumpTask 阶段,此时的 listening 会立马返回,再次发起 get config ·正常
- 再次发起 listening,服务端还未到 DumpTask 阶段(太极端),那是最糟糕情况需 ·兜底
- 兜底:最糟糕情况需 30s 超时后,再次请求才能获取到变更。
- 故障:集群成员间的配置不一致!通知失败、异常等,TODO 需增强的自身一致性检测;目前有 6h/次 全量 dumpAll 的异步 sch 任务。
更优的长连接 gRPC 期待 Nacos 2.x
Http/2 及 gRPC 的一些文章
- gRPC 长连接在微服务业务系统中的实践 : https://cloud.tencent.com/developer/article/1748323
Nacos 2.x
- gRPC 长连接 客户端与一个服务端建立长连接,则在服务端重启升级过程中,最后一批启动的会一直没有被连接,会造成整体服务的不均匀性;为此 Nacos 将引入 服务端 主动协调 客户端的均衡策略。
…
##简解源码 > 走读
注:… 表示异步执行, * 标识重点
###Long-polling .getConfigChange
ConfigController.listener() // The client listens for configuration changes.
ConfigServletInner.doPollingConfig()
LongPollingService.addLongPollingClient()
... ClientLongPolling.run()
create ScheduledFuture: asyncTimeoutFuture // timeout with return null
Queue<ClientLongPolling> allSubs.add(ClientLongPolling.this);
... **LocalDataChangeEvent** *// 关键的配置变更事件*
... DataChangeTask.run()
Queue<ClientLongPolling> allSubs.iterator() // compare and return changeGroupKeys
###Publish config .change Config
ConfigController.publishConfig() // Adds or updates non-aggregated data.
**PersistService.insertOrUpdate()** *// write into db first*
**NotifyCenter.publishEvent > ConfigDataChangeEvent** *// 关键配置变更事件*
... AsyncNotifyService.onEvent()
ConfigExecutor.executeAsyncNotify(new AsyncTask(nacosAsyncRestTemplate, queue)) // async task in queue
... AsyncTask.run() executeAsyncInvoke()
restTemplate url:(**/v1/cs/communication/dataChange**) // rest get url to notify all members.
####Notify data change // all members.
CommunicationController.notifyConfigInfo()
DumpService.dump()
TaskManager.addTask(taskKey, new DumpTask(..))
... DumpProcessor.process()
**PersistService.findConfigInfo()** *// get config from db*
DumpConfigHandler.configDump(ConfigDumpEvent)
DumpConfigHandler.configDump()
ConfigCacheService.dump()
**DiskUtil.saveToDisk(dataId, group, tenant, content)** *// dump in disk first*
ConfigCacheService.updateMd5()
MemoryCache.update() *// dump in memory second*
NotifyCenter.publishEvent > **LocalDataChangeEvent** *// 关键的配置变更事件*