Nacos 与事件总线
Nacos 与事件总线
场景:读 Nacos 配置中心与注册中心相关源码时,先把内部通信模型摸清。
结论:Nacos 内部是 EDA(事件驱动),核心是 EventBus;配置变更、实例变更都走「发事件 → 总线路由 → Listener 处理」。
边界:本文不写 Nacos 安装部署、客户端接入细节,也不展开 Guava EventBus 的完整 API。
配套视频:Nacos与事件总线
1. 我主要看了哪两块源码
读 Nacos 时我没有一上来铺全量模块,而是先盯两条业务主线:
| 模块 | 我重点看什么 |
|---|---|
| 配置中心 | 配置变更之后,整条 配置刷新 怎么串起来 |
| 注册中心 | 实例 健康检查 与 心跳 怎么判定上下线 |
这两块表面上功能不同,底层都依赖同一套思路:状态变化后发布事件,让其他组件订阅并响应,而不是模块之间互相硬调。
2. 配置刷新:为什么会想到 EventBus
配置中心这块,核心不是「配置存在哪里」,而是「配置变了之后系统怎么知道、怎么刷」。
Nacos 内部采用的是 EDA(Event-Driven Architecture),形态上和消息队列很像:
- 生产者产生事件,把事件丢给 EventBus(事件总线)
- 事件总线按订阅关系,把事件路由给对应 Listener(订阅者)
- 订阅者各自完成后续处理
落到配置变更,可以粗写成:
配置发生变更
→ Nacos 内部发布「配置变更」事件
→ EventBus 收到后路由给订阅方
→ 客户端侧拿到通知,更新本地缓存的配置
→ 完成配置刷新
所以读配置中心源码时,EventBus 不是边角工具类,而是配置刷新链路的中枢。发布方只负责「告诉大家配置变了」,不需要知道后面有多少模块在监听。
3. 注册中心:健康检查与心跳(同一套状态维护思路)
注册中心这边,我重点看的是实例健康状态怎么维护。
大致机制是:
- 每个实例维护自己的健康状态
- 内部有 健康检查线程池,周期扫描全部实例
- 依据 最后一次心跳时间 判断是否健康
常见阈值可以记成(具体以你读到的版本配置为准):
| 条件 | 行为 |
|---|---|
| 实例大约每 5 秒 发一次心跳 | 正常保活 |
| 超过约 15 秒 没收到心跳 | 标记为 不健康 |
| 再持续更长时间无心跳 | 从 可用列表剔除 |
整条链路就是:定时任务不断扫实例 → 用「当前时间 − 最后心跳时间」维护健康状态。
这和配置中心一样,本质是 状态变化驱动后续处理;实例上下线、变更通知,同样适合用事件把「检测」和「通知/剔除」拆开。
4. 为什么用 EventBus,而不是 MQ
事件驱动最大的目的是 解耦:
- 配置变了:发布者只发「配置变更」事件
- 后面有谁要处理(审计、缓存刷新、消息通知……)发布者 不关心
- 扩展时只 新增 Listener,不必改发布配置的那段代码
那为什么这里不用 RabbitMQ / Kafka 这类 MQ?
因为这些事件主要发生在 Nacos 服务进程内部,例如:
- 配置变更
- 实例变更
本质是 同一 JVM 内模块通信,不需要跨网络,也不需要消息持久化。EventBus 更轻、延迟更低;硬上 MQ 反而要付 Broker 部署、序列化、网络和运维成本。
可以记成一句话:
MQ 擅长服务间解耦;EventBus 擅长服务内解耦。
Nacos 作为跑在单个 Java 进程里的中间件,内部状态变更用 EventBus 是更自然的选择。
5. 1000 个 Listener 里有一个特别慢,会拖累别人吗?
会——要看 EventBus 是同步还是异步。
从设计意图上,EventBus 只保证「发布者与订阅者解耦」:
- 发布者不知道有哪些 Listener
- 也不关心每个 Listener 的业务细节
但「一个 Listener 慢会不会堵住后面」取决于 分发实现:
| 分发方式 | 行为 | 慢 Listener 的影响 |
|---|---|---|
同步串行(Guava EventBus 默认) | 按注册顺序依次调用 Listener | 会阻塞后续 Listener,甚至拖住发布线程 |
异步分发(如 AsyncEventBus + 线程池) | 提交到线程池执行 | Listener 之间通常互不影响(仍受线程池容量约束) |
Nacos 侧常见的 EventBus 形态基于 Guava,默认同步。
若某个 Listener 里做了重 I/O、数据库或网络调用,后面排队的 Listener 都会等它;严重时还会拖慢发布事件的那条业务线程。
工程上常见的防坑方式:
- Listener 内部把耗时逻辑丢进线程池(监听回调本身尽量短)
- 需要总线级异步时,显式用 AsyncEventBus 并配好线程池与拒绝策略
- 不要默认假设「解耦了就等于互不影响」——解耦解决的是 依赖结构,不是 执行时延隔离
6. 小结(读源码时可以带着走的问题)
- 配置中心:变更 → 事件 → EventBus → Listener / 客户端刷新缓存
- 注册中心:心跳周期扫实例 → 超时标不健康 → 更久则剔除
- EventBus vs MQ:进程内用总线,跨服务用 MQ
- 同步 EventBus:慢 Listener 会拖全局;耗时逻辑要主动异步化
下次再往下拆 Nacos 源码,可以按这条线继续追:
谁
post了配置变更事件?有哪些 Listener 注册了?同步调用栈里有没有藏着 I/O?
