Skip to content

注册发现与客户端订阅

This content is not available in your language yet.

Naming 的核心状态是服务下的实例集合。客户端 API 负责把实例和订阅意图送入远程代理,服务端负责写入服务模型、同步临时状态,并把变更推回订阅者。

先给答案:Naming 的核心状态是服务下的实例集合

Section titled “先给答案:Naming 的核心状态是服务下的实例集合”

Naming 的核心状态是服务下的实例集合。客户端 API 负责把实例和订阅意图送入远程代理,服务端负责写入服务模型、同步临时状态,并把变更推回订阅者。 正文沿“注册与订阅 -> 实现拆解 -> 为什么注册和订阅要分成两个状态机”展开:先确认入口和状态归属,再跟踪控制流或数据流的推进,最后落到对外可观察的结果。

主要失效边界集中在“客户端重启、订阅回调慢、HTTP/gRPC 能力差异”这些场景。它们破坏的是容量、顺序、并发或生命周期前提;排查时应先确认状态是否仍由正确对象持有,再核对推进条件和清理路径。

NacosNamingService
├─ registerInstance -> NamingClientProxyDelegate
│ -> server InstanceOperator
└─ subscribe -> proxy -> ServiceInfo cache
▲
push / poll ----┘
heartbeat -> handleBeat -> health / expiration

NacosNamingService#registerInstance 统一参数后交给代理;服务端 InstanceOperatorClientImpl#registerInstance 再根据临时或持久语义选择操作服务。临时实例依赖心跳维持租约,持久实例则进入 CP 相关路径。

订阅方法通过 NamingClientProxyDelegate#subscribe 进入 gRPC 或 HTTP 代理。客户端缓存 ServiceInfo,收到变更后更新实例列表并通知 EventListener。查询和订阅可以同时存在,前者是一次性快照,后者建立持续状态关系。

为什么注册和订阅要分成两个状态机

Section titled “为什么注册和订阅要分成两个状态机”

替代方案:注册接口成功后自动把调用方加入所有订阅关系。 为什么不行:生产者和消费者角色不同,一个进程可能只注册不订阅,或订阅多个服务;隐式绑定会造成连接、权限和生命周期耦合。 证据:NacosNamingService 分别暴露 registerInstance、getAllInstances 和 subscribe,代理层也分别实现注册与订阅。

  • NacosNamingService#registerInstance — client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java:145
  • NacosNamingService#getAllInstances — client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java:257
  • NacosNamingService#selectInstances — client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java:313
  • NacosNamingService#selectOneHealthyInstance — client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java:457
  • NacosNamingService#subscribe — client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java:498
  • NacosNamingService#unsubscribe — client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java:550
  • NamingClientProxyDelegate#subscribe — client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java:182
  • NamingGrpcClientProxy#subscribe — client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java:419
  • InstanceOperatorClientImpl#registerInstance — naming/src/main/java/com/alibaba/nacos/naming/core/InstanceOperatorClientImpl.java:102
  • InstanceOperatorClientImpl#handleBeat — naming/src/main/java/com/alibaba/nacos/naming/core/InstanceOperatorClientImpl.java:246
  • EphemeralClientOperationServiceImpl#registerInstance — naming/src/main/java/com/alibaba/nacos/naming/core/v2/service/impl/EphemeralClientOperationServiceImpl.java:56
  • PersistentClientOperationServiceImpl#registerInstance — naming/src/main/java/com/alibaba/nacos/naming/core/v2/service/impl/PersistentClientOperationServiceImpl.java:107
场景 现象 原因 规避
客户端重启 临时实例消失后重新出现 临时实例依赖连接和心跳 将注册放入启动和重连流程
订阅回调慢 本地服务列表更新滞后 回调线程被业务逻辑占用 回调只替换快照,业务异步处理
HTTP/gRPC 能力差异 某些推送行为不同 代理协议实现不完全同构 以客户端能力协商和重连逻辑兜底

服务发现客户端最好把“远程状态”和“本地可读快照”分开。调用方读快照不必等待网络,但必须有版本、过期和重连策略,避免把缓存当作永久真相。

面试锚点

  • 临时实例和持久实例的核心差别是什么?
  • 注册成功后客户端还需要做什么?
  • 服务订阅如何避免每次调用都访问注册中心?