文章目录
- 概述
- 一、配置持久化到数据库
- 二、发布事件
- 2.1、事件发布者端
- 2.1.1、DefaultPublisher#publish
- 2.1.2、DefaultPublisher#run
- 2.1.3、DefaultPublisher#receiveEvent
- 2.2、事件订阅者端
- 2.2.1、Subscriber#onEvent
- 2.2.2、ConfigCacheService#dump
- 总结:Nacos 配置中心服务端事件处理机制详解
- 服务端事件发布流程
- 服务端事件消费流程:
- 事件发布与订阅关系表
- 设计理念回顾
概述
Nacos除了可以实现服务的注册与发现,还具有统一进行配置管理的作用,只需要额外引入依赖:
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency>
可以将项目中的配置文件,转移到配置中心:
同时需要在项目的resource
目录下,加入bootstrap.yml
或bootstrap.properties
文件,目的是为了在Spring Boot启动的过程中先加载注册中心地址等基础配置项:
spring:application:name: spring-cloud-nacos-config-demo #微服务名称# profiles:
# active: dev #加载开发环境的配置文件 mall-user-config-demo-dev.ymlcloud:nacos:config: #配置nacos配置中心地址server-addr: 192.168.101.1:8848username: nacospassword: nacosfile-extension: yml # 指定配置文件的扩展名为yml#namespace:# 自定义 Data Id 的配置shared-configs: #不同工程的通用配置 支持共享的 DataId- data-id: common.ymlgroup: DEFAULT_GROUPrefresh: true
# - data-id: openfeign.yml
# group: GLOBALE_GROUP
# refresh: true #支持动态刷新
#
# extension-configs: # 支持一个应用多个 DataId 的配置
# - data-id: actuator.yml
# - data-id: common.yml
# group: REFRESH_GROUP
# refresh: true #支持动态刷新
在配置中心的页面上,新增或修改配置,实际调用的是/nacos/v1/cs/configs
。
对应的是Nacos 服务端 源码中的ConfigController
的publishConfig
方法,在该方法中,主要完成了两件事:
- 将配置持久化到数据库。
- 发布
ConfigDataChangeEvent
事件,让客户端能感知配置更新。
一、配置持久化到数据库
将配置持久化到数据库,实际调用的是ExternalStoragePersistServiceImpl#insertOrUpdate
方法,整体的逻辑是,先执行新增 操作,如果抛出了DataIntegrityViolationException
(数据完整性违规)异常,则会改为执行 更新 操作。
DataIntegrityViolationException 是 Spring 框架中定义的异常。这个异常表示违反了数据库的完整性约束。例如唯一性约束、非空约束、外键约束等。
使用编程式事务执行数据库的新增或修改操作:
底层使用的原生JDBC:
二、发布事件
在将页面上的数据保存到数据库中后,就会发布一个ConfigDataChangeEvent
事件。
调用的是NotifyCente
的publishEvent
。NotifyCenter
是 Nacos 内置的异步事件总线,用于模块间解耦地传递和响应事件,是支撑配置推送、服务变更通知的关键机制,内部主要实现了:
- 事件注册:订阅你感兴趣的事件类型
- 事件发布:将事件投递出去
- 事件处理:触发订阅者的回调处理逻辑
2.1、事件发布者端
在NotifyCente
的publishEvent
方法中,主要完成了两件事:
- 根据eventType解析出topic,再根据topic从publisherMap属性中找到对应的发布器。
- 利用发布器将事件放入队列中。
选择DefaultPublisher
的实现:
DefaultPublisher
继承了Thread
,该类的对象就是一个可以运行的线程。同时实现了EventPublisher
接口:
EventPublisher
作为Nacos事件总线发布器的顶级接口,制定了一套初始化、添加订阅者、移除订阅者、发布事件的规范。
2.1.1、DefaultPublisher#publish
在DefaultPublisher
的publish
方法中:
- 尝试将当前事件存放到阻塞队列中。
offer
方法,在队列中元素已满的情况下,不会抛出异常,而是会返回false。 - 当队列中元素已满,则让消费者去处理当前事件。
2.1.2、DefaultPublisher#run
因为DefaultPublisher
继承了Thread
,所以在调用DefaultPublisher
的start
方法时,实际会执行run
方法中的逻辑,在run
方法中,完成了:
- 从队列中获取事件 (如果获取不到事件,则会在这一行陷入阻塞,不会导致cpu空转。和Nacos服务端处理客户端注册信息的设计是同样的道理)
- 通知消费者处理事件。
2.1.3、DefaultPublisher#receiveEvent
无论是run
方法,还是publish
方法,最终都指向了receiveEvent
,在该方法中,首先会去遍历所有的订阅者,然后调用notifySubscriber
方法让每一个订阅者去处理事件。
最终调用进notifySubscriber
方法。会回调订阅者对象的onEvent
方法去进行处理。如果订阅者配置了线程池,则还会使用线程池去处理(默认为null,没有配置)。
run
方法和publish
方法,最终都调用了receiveEvent
,两者调用的区别在于触发时机不同:
run
方法触发receiveEvent
的时机,是在DefaultPublisher
的start
方法被调用时:
start
方法又是init
方法调用的。init
方法的调用时机,是在NotifyCenter
的静态代码块中的。伴随着NotifyCenter
的加载而加载,换言之是在Nacos服务端的启动过程中,就已经调用。
publish
方法则是在服务端接收到页面新增/修改配置的请求后,才会触发的。
所以run
方法是先于 publish
方法触发的。
2.2、事件订阅者端
2.2.1、Subscriber#onEvent
onEvent
方法是在AsyncNotifyService
中的逻辑,在AsyncNotifyService
的构造中,首先会注册一个ConfigDataChangeEvent
类型的事件,并且注册一个订阅者:
在2.3的notifySubscriber
方法中,会回调onEvent
方法,执行其中的逻辑:
Nacos 2.x版本中,进入的是下图的分支:
在AsyncRpcTask
的run
方法中,如果是当前节点,就会直接存储一份配置:
实际上也是将其加入了TaskManager
的队列中,异步处理:
2.2.2、ConfigCacheService#dump
在ConfigCacheService
的dump
方法中,会将配置信息写入磁盘,并且配置信息发生变更时,更新文件的md5值:
还会发布一个LocalDataChangeEvent
事件。注意,在ConfigController
中发布的,是ConfigDataChangeEvent
事件。同样是利用NotifyCenter去发布。
这个事件是被RpcConfigChangeNotifier
订阅的。
在configDataChanged
中,会将配置信息推送到客户端:
并且支持重试机制:
总结:Nacos 配置中心服务端事件处理机制详解
当 Nacos 作为配置中心使用时,页面端的新增或修改操作最终会通过 Controller 层发送请求到服务端。服务端接收到请求后,主要完成以下两项工作:
- 将配置信息持久化到数据库;
- 发布
ConfigDataChangeEven
t事件,驱动后续变更通知流程。
其中,数据库写入过程采用编程式事务而非注解式事务,主要考虑到业务逻辑中可能存在较为复杂的数据库操作,编程式事务在控制粒度与异常处理方面更为灵活。
事件发布机制则基于 Nacos 自研的异步事件总线实现,整体设计思路与 Nacos 服务端处理客户端注册信息的机制保持一致。
服务端事件发布流程
服务端事件的发布大致可划分为两个阶段:
- 初始化阶段:服务端在启动过程中,会启动一个后台线程,进入死循环监听事件。该线程会尝试从一个阻塞队列中拉取事件:
- 如果队列为空,线程将阻塞等待,不会造成 CPU 空转;
- 一旦有事件被投递到队列中,线程会立即唤醒并执行对应的事件处理逻辑。这一机制有效地保证了资源的高效利用,也符合高并发下的异步处理模型设计。
- 运行阶段:在服务端启动完成后,当前端发起新增或修改配置的请求时,事件发布器会将
ConfigDataChangeEvent
投递至上述阻塞队列,并由初始化阶段的线程消费。该线程在获取到事件后,会根据配置类型及作用域通知对应的订阅者执行后续处理逻辑。
服务端事件消费流程:
事件消费逻辑主要在订阅者侧完成,通常包括以下两步操作:
- 数据落盘:订阅者会将变更后的配置信息写入本地磁盘,同时更新其对应的 MD5 值,用于变更对比及客户端变更判断。
- 随后,订阅者会发布
LocalDataChangeEvent
事件,该事件的监听器会通过 RPC 向客户端发送变更通知,提示配置已发生变更。此通知机制内置了失败重试机制,确保高可用性
事件发布与订阅关系表
事件类型 | 发布器 | 监听器 / 订阅者 | 功能描述 |
---|---|---|---|
ConfigDataChangeEvent | 配置 Controller 层逻辑 | ConfigChangePublisher | 用于标记某条配置数据已变更,驱动本地磁盘写入与事件级联 |
LocalDataChangeEvent | ConfigChangePublisher | ClientRpcNotifySubscriber 等 | 通过 RPC 通知客户端配置已变更,支持失败重试,确保通知可靠性 |
设计理念回顾
Nacos 配置中心的事件驱动模型体现了以下设计理念:
- 异步解耦:通过阻塞队列与事件总线,服务端变更操作与客户端通知完全解耦,提升系统吞吐能力;
- 资源高效利用:利用阻塞机制避免线程空转,实现高效的资源调度;
- 可靠传递机制:客户端通知具备重试机制,确保在网络抖动或客户端异常的情况下,变更信息能最终送达;
- 一致性保障:通过 MD5 校验与本地磁盘备份,保障服务端与客户端配置信息的一致性。