注册发现与客户端订阅
Naming 的核心状态是服务下的实例集合。客户端 API 负责把实例和订阅意图送入远程代理,服务端负责写入服务模型、同步临时状态,并把变更推回订阅者。
先给答案:Naming 的核心状态是服务下的实例集合
Section titled “先给答案:Naming 的核心状态是服务下的实例集合”Naming 的核心状态是服务下的实例集合。客户端 API 负责把实例和订阅意图送入远程代理,服务端负责写入服务模型、同步临时状态,并把变更推回订阅者。 正文沿“注册与订阅 -> 实现拆解 -> 为什么注册和订阅要分成两个状态机”展开:先确认入口和状态归属,再跟踪控制流或数据流的推进,最后落到对外可观察的结果。
主要失效边界集中在“客户端重启、订阅回调慢、HTTP/gRPC 能力差异”这些场景。它们破坏的是容量、顺序、并发或生命周期前提;排查时应先确认状态是否仍由正确对象持有,再核对推进条件和清理路径。
NacosNamingService ├─ registerInstance -> NamingClientProxyDelegate │ -> server InstanceOperator └─ subscribe -> proxy -> ServiceInfo cache ▲ push / poll ----┘ heartbeat -> handleBeat -> health / expirationNacosNamingService#registerInstance 统一参数后交给代理;服务端 InstanceOperatorClientImpl#registerInstance 再根据临时或持久语义选择操作服务。临时实例依赖心跳维持租约,持久实例则进入 CP 相关路径。
订阅方法通过 NamingClientProxyDelegate#subscribe 进入 gRPC 或 HTTP 代理。客户端缓存 ServiceInfo,收到变更后更新实例列表并通知 EventListener。查询和订阅可以同时存在,前者是一次性快照,后者建立持续状态关系。
为什么注册和订阅要分成两个状态机
Section titled “为什么注册和订阅要分成两个状态机”替代方案:注册接口成功后自动把调用方加入所有订阅关系。
为什么不行:生产者和消费者角色不同,一个进程可能只注册不订阅,或订阅多个服务;隐式绑定会造成连接、权限和生命周期耦合。
证据:NacosNamingService 分别暴露 registerInstance、getAllInstances 和 subscribe,代理层也分别实现注册与订阅。
源码坐标索引
Section titled “源码坐标索引”NacosNamingService#registerInstance—client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java:145NacosNamingService#getAllInstances—client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java:257NacosNamingService#selectInstances—client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java:313NacosNamingService#selectOneHealthyInstance—client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java:457NacosNamingService#subscribe—client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java:498NacosNamingService#unsubscribe—client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java:550NamingClientProxyDelegate#subscribe—client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java:182NamingGrpcClientProxy#subscribe—client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java:419InstanceOperatorClientImpl#registerInstance—naming/src/main/java/com/alibaba/nacos/naming/core/InstanceOperatorClientImpl.java:102InstanceOperatorClientImpl#handleBeat—naming/src/main/java/com/alibaba/nacos/naming/core/InstanceOperatorClientImpl.java:246EphemeralClientOperationServiceImpl#registerInstance—naming/src/main/java/com/alibaba/nacos/naming/core/v2/service/impl/EphemeralClientOperationServiceImpl.java:56PersistentClientOperationServiceImpl#registerInstance—naming/src/main/java/com/alibaba/nacos/naming/core/v2/service/impl/PersistentClientOperationServiceImpl.java:107
| 场景 | 现象 | 原因 | 规避 |
|---|---|---|---|
| 客户端重启 | 临时实例消失后重新出现 | 临时实例依赖连接和心跳 | 将注册放入启动和重连流程 |
| 订阅回调慢 | 本地服务列表更新滞后 | 回调线程被业务逻辑占用 | 回调只替换快照,业务异步处理 |
| HTTP/gRPC 能力差异 | 某些推送行为不同 | 代理协议实现不完全同构 | 以客户端能力协商和重连逻辑兜底 |
服务发现客户端最好把“远程状态”和“本地可读快照”分开。调用方读快照不必等待网络,但必须有版本、过期和重连策略,避免把缓存当作永久真相。
面试锚点
- 临时实例和持久实例的核心差别是什么?
- 注册成功后客户端还需要做什么?
- 服务订阅如何避免每次调用都访问注册中心?