Dubbo 从 Zookeeper 迁到 Nacos 后,运维少记了 3 个端口号:两个注册中心的 4 笔账

Dubbo 从 Zookeeper 迁到 Nacos 后,运维少记了 3 个端口号:两个注册中心的 4 笔账


一个 Dubbo 项目,两台 Zookeeper 服务器,五个端口号

2018 年进公司接手的第一个 Dubbo 项目。注册中心用的 Zookeeper。运维给了我一张纸条,上面写着五个端口号:

  • 2181:ZK 客户端连接
  • 2888:ZK 集群内部通信(Leader ↔ Follower)
  • 3888:ZK Leader 选举
  • 20880:Dubbo 服务端口
  • 22222:Dubbo 监控端口

我说"记不住"。运维说"习惯了就好"。

后来切到 Nacos。纸条上的端口号减到两个:8848(HTTP)和 9848(gRPC)。Dubbo 的服务端口不用和注册中心耦合了------注册信息通过 Nacos 的 API 透传,不是像 ZK 那样把 Dubbo URL 直接写进 ZNode 的 data 里。

这篇文章不是要证明 Nacos 比 Zookeeper 好。Zookeeper 当了八年 Dubbo 默认注册中心,它在一致性上的能力至今没人能替代。但注册中心的角色正在被重新定义------不再是"一个分布式协调器",而是"服务治理平台的一个模块"。Nacos 对这个定义的理解,恰好踩中了 Dubbo 3.x 时代的需求。

⚠️ 本文对比基于 Zookeeper 3.8.x 和 Nacos 2.3.x(2024年12月发布)。如果你还在用 ZK 3.4.x,本文的 ZooDefs 和 session 机制描述仍然适用。


全景:四个维度,两笔旧账

#mermaid-svg-aJcR4doDD45t888p{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aJcR4doDD45t888p .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aJcR4doDD45t888p .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aJcR4doDD45t888p .error-icon{fill:#552222;}#mermaid-svg-aJcR4doDD45t888p .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aJcR4doDD45t888p .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aJcR4doDD45t888p .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aJcR4doDD45t888p .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aJcR4doDD45t888p .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aJcR4doDD45t888p .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aJcR4doDD45t888p .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aJcR4doDD45t888p .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aJcR4doDD45t888p .marker.cross{stroke:#333333;}#mermaid-svg-aJcR4doDD45t888p svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aJcR4doDD45t888p p{margin:0;}#mermaid-svg-aJcR4doDD45t888p .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-aJcR4doDD45t888p .cluster-label text{fill:#333;}#mermaid-svg-aJcR4doDD45t888p .cluster-label span{color:#333;}#mermaid-svg-aJcR4doDD45t888p .cluster-label span p{background-color:transparent;}#mermaid-svg-aJcR4doDD45t888p .label text,#mermaid-svg-aJcR4doDD45t888p span{fill:#333;color:#333;}#mermaid-svg-aJcR4doDD45t888p .node rect,#mermaid-svg-aJcR4doDD45t888p .node circle,#mermaid-svg-aJcR4doDD45t888p .node ellipse,#mermaid-svg-aJcR4doDD45t888p .node polygon,#mermaid-svg-aJcR4doDD45t888p .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aJcR4doDD45t888p .rough-node .label text,#mermaid-svg-aJcR4doDD45t888p .node .label text,#mermaid-svg-aJcR4doDD45t888p .image-shape .label,#mermaid-svg-aJcR4doDD45t888p .icon-shape .label{text-anchor:middle;}#mermaid-svg-aJcR4doDD45t888p .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-aJcR4doDD45t888p .rough-node .label,#mermaid-svg-aJcR4doDD45t888p .node .label,#mermaid-svg-aJcR4doDD45t888p .image-shape .label,#mermaid-svg-aJcR4doDD45t888p .icon-shape .label{text-align:center;}#mermaid-svg-aJcR4doDD45t888p .node.clickable{cursor:pointer;}#mermaid-svg-aJcR4doDD45t888p .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-aJcR4doDD45t888p .arrowheadPath{fill:#333333;}#mermaid-svg-aJcR4doDD45t888p .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aJcR4doDD45t888p .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aJcR4doDD45t888p .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aJcR4doDD45t888p .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-aJcR4doDD45t888p .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aJcR4doDD45t888p .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-aJcR4doDD45t888p .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aJcR4doDD45t888p .cluster text{fill:#333;}#mermaid-svg-aJcR4doDD45t888p .cluster span{color:#333;}#mermaid-svg-aJcR4doDD45t888p div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aJcR4doDD45t888p .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-aJcR4doDD45t888p rect.text{fill:none;stroke-width:0;}#mermaid-svg-aJcR4doDD45t888p .icon-shape,#mermaid-svg-aJcR4doDD45t888p .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aJcR4doDD45t888p .icon-shape p,#mermaid-svg-aJcR4doDD45t888p .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-aJcR4doDD45t888p .icon-shape .label rect,#mermaid-svg-aJcR4doDD45t888p .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aJcR4doDD45t888p .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-aJcR4doDD45t888p .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-aJcR4doDD45t888p :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Nacos
AP+CP 可切换
服务模型

Namespace→Group→Service
显式心跳 + 服务端探测
注册+配置+治理

一个控制台
Zookeeper
CP,Zab 协议
树状存储模型
Session 心跳
只做协调

不做治理

两个系统在注册中心这个角色上的根本分歧:Zookeeper 是一棵分布式协调树,Nacos 是一个服务治理平台。前者精确但窄,后者宽但运维重。
🔗 前面拆过 Nacos 的 AP/CP 分叉源码:临时实例走左边,持久化走右边------拆开那 3 行 if


账本一:数据模型------树 vs 服务

ZK 的存储模型是一棵树。每个节点叫 ZNode,有路径、有数据、有子节点。

Dubbo 把服务注册信息存成这样的结构:

复制代码
/dubbo/com.example.OrderService/providers/
    ├── dubbo://10.0.1.10:20880/com.example.OrderService?...
    ├── dubbo://10.0.1.11:20880/com.example.OrderService?...

每一个 provider URL 是一个临时 ZNode。客户端会话断了,ZK 自动删掉这个 ZNode,消费者 watcher 收到通知,更新本地列表。

这套机制的问题是:ZNode 里存的是 Dubbo URL,不是服务实例。 你想在注册中心层面做权重调整、流量路由、元数据过滤,必须先解析 URL 字符串,改它的参数,再写回 ZNode。没有控制台能帮你做这件事。

Nacos 的存储模型是服务→实例:

复制代码
Namespace: prod
├── Group: DEFAULT_GROUP
│   ├── Service: com.example.OrderService
│   │   ├── Cluster: DEFAULT
│   │   │   ├── Instance: 10.0.1.10:20880
│   │   │   │   ├── weight: 100
│   │   │   │   ├── healthy: true
│   │   │   │   ├── metadata: {"version": "v1"}
│   │   │   ├── Instance: 10.0.1.11:20880

这是一个专门为服务发现设计的数据模型。实例有独立的属性字段(权重、健康状态、元数据),不是塞在一个 URL 字符串里。你可以在 Nacos 控制台直接调整某个实例的权重------不用解析 URL、不用重写 ZNode。


账本二:一致性------CP 的代价

ZK 用 Zab 协议保强一致。每条写入都要过半节点确认。

这在"注册中心"场景下带来三个实际问题:

问题一:写不可用。 3 节点的 ZK 集群挂 2 台,剩余的节点进入只读模式------不能注册新服务,不能下线实例。Dubbo 的注册写入直接失败,抛 KeeperException.ConnectionLossException

问题二:Session 过期风暴。 ZK 的心跳是 Session 机制。客户端和服务端之间有一个 sessionTimeout(默认 40 秒)。如果网络闪断超过 40 秒,ZK 认为客户端挂了,删掉它名下所有临时 ZNode。Dubbo 侧表现为"这个客户端注册的所有服务同时消失"------然后网络恢复,所有服务同时重新注册。集群短时间内承受大量写入。

问题三:Watcher 是一次性的。 ZK 的 watcher 触发一次后就失效了。Dubbo 客户端每次收到通知后要重新注册 watcher。大量实例变更时,watcher 的注册和触发会产生雪崩效应。Dubbo 2.7 之后做了 watcher 的批量处理和重试机制来缓解这个问题,但机制本身的"一次性"特点没变。

Nacos 2.x 对这三个问题的处理方式不同:

  • 临时实例走 Distro(AP),3 节点挂 2 台仍可写入(上一篇 5.4 里有那 3 行 if)。
  • 心跳是显式上报(客户端主动发),不是 Session 机制。超时时间默认 15 秒,可以按服务调。
  • 订阅是持续监听(gRPC 双向流),不是一次性 watcher。不用每次重新注册。

账本三:配置管理------ZK 能做,但像用螺丝刀切菜

ZK 最早被用作配置中心------ZNode 存配置内容,客户端 watch ZNode,变更后收到通知。阿里巴巴早期的 Diamond 配置中心就是基于这个思路。

但 ZK 做配置管理有几个硬伤:

  • 没有版本历史。 改完就覆盖了,回滚只能靠外部备份。
  • 没有灰度发布。 所有 watch 该 ZNode 的客户端同时收到变更通知,不能分批推。
  • 没有审计追踪。 谁在什么时间改了什么、改之前的值是什么------查不到。
  • 大数据量不适合 ZK 的 ZNode 模型。 ZK 的单个 ZNode 数据量上限是 1MB。超过 1MB 的配置文件(比如一个巨大的 JSON 或 YAML)要拆成多个 ZNode。

Nacos 的配置管理是独立模块。版本历史、一键回滚、Group 分层灰度、变更审计------这些在 ZK 那边需要二次开发的功能,在 Nacos 这边是开箱即用的。


账本四:运维------少记 3 个端口,多维护 1 个 MySQL

运维动作 Zookeeper Nacos(基于 2.3.x)
部署复杂度 1 个 tar.gz,解压即用 需要 MySQL(cluster 模式),多一个 gRPC 端口 9848
集群搭建 zkServer.sh start + zoo.cfg 配 peer cluster.conf 手动配置 + MySQL 初始化
JVM 调优 堆内存 + GC 调优(ZK 对 GC 敏感) 堆内存 + gRPC 线程池
控制台 ❌ 无内置(需 zkUI / zkWeb) ✅ 完整服务+配置管理控制台
端口数 3 个(2181/2888/3888) 3 个(8848/9848/9849)
外部依赖 MySQL

运维成本其实是 Nacos 输掉的一笔账。ZK 零外部依赖------一个 JVM 就能跑,集群搭建基本没有前置步骤。Nacos 需要 MySQL,cluster 模式下还要初始化和维护。

但 Dubbo 用户迁移到 Nacos 后,运维总成本通常是下降的。 原因是:用 ZK 做注册中心意味着你要另外选型配置中心(Apollo / Consul / 自研),选型消息队列、选型监控平台。每多一个组件,运维多维护一套。Nacos 把注册和配置合并后,虽然 Nacos 本身运维比 ZK 重,但省掉了配置中心那套环境。

⚠️ 适用边界:如果你的团队已经有成熟的配置中心(比如 Apollo)且没有迁移计划,Zookeeper + Apollo 是稳定的组合,不需要为了统一而推倒重来。


一张图带走:选 ZK 还是 Nacos

#mermaid-svg-pViSZ3Ca9B7TsDin{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pViSZ3Ca9B7TsDin .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pViSZ3Ca9B7TsDin .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pViSZ3Ca9B7TsDin .error-icon{fill:#552222;}#mermaid-svg-pViSZ3Ca9B7TsDin .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pViSZ3Ca9B7TsDin .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pViSZ3Ca9B7TsDin .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pViSZ3Ca9B7TsDin .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pViSZ3Ca9B7TsDin .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pViSZ3Ca9B7TsDin .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pViSZ3Ca9B7TsDin .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pViSZ3Ca9B7TsDin .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pViSZ3Ca9B7TsDin .marker.cross{stroke:#333333;}#mermaid-svg-pViSZ3Ca9B7TsDin svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pViSZ3Ca9B7TsDin p{margin:0;}#mermaid-svg-pViSZ3Ca9B7TsDin .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pViSZ3Ca9B7TsDin .cluster-label text{fill:#333;}#mermaid-svg-pViSZ3Ca9B7TsDin .cluster-label span{color:#333;}#mermaid-svg-pViSZ3Ca9B7TsDin .cluster-label span p{background-color:transparent;}#mermaid-svg-pViSZ3Ca9B7TsDin .label text,#mermaid-svg-pViSZ3Ca9B7TsDin span{fill:#333;color:#333;}#mermaid-svg-pViSZ3Ca9B7TsDin .node rect,#mermaid-svg-pViSZ3Ca9B7TsDin .node circle,#mermaid-svg-pViSZ3Ca9B7TsDin .node ellipse,#mermaid-svg-pViSZ3Ca9B7TsDin .node polygon,#mermaid-svg-pViSZ3Ca9B7TsDin .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pViSZ3Ca9B7TsDin .rough-node .label text,#mermaid-svg-pViSZ3Ca9B7TsDin .node .label text,#mermaid-svg-pViSZ3Ca9B7TsDin .image-shape .label,#mermaid-svg-pViSZ3Ca9B7TsDin .icon-shape .label{text-anchor:middle;}#mermaid-svg-pViSZ3Ca9B7TsDin .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pViSZ3Ca9B7TsDin .rough-node .label,#mermaid-svg-pViSZ3Ca9B7TsDin .node .label,#mermaid-svg-pViSZ3Ca9B7TsDin .image-shape .label,#mermaid-svg-pViSZ3Ca9B7TsDin .icon-shape .label{text-align:center;}#mermaid-svg-pViSZ3Ca9B7TsDin .node.clickable{cursor:pointer;}#mermaid-svg-pViSZ3Ca9B7TsDin .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pViSZ3Ca9B7TsDin .arrowheadPath{fill:#333333;}#mermaid-svg-pViSZ3Ca9B7TsDin .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pViSZ3Ca9B7TsDin .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pViSZ3Ca9B7TsDin .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pViSZ3Ca9B7TsDin .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pViSZ3Ca9B7TsDin .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pViSZ3Ca9B7TsDin .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pViSZ3Ca9B7TsDin .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pViSZ3Ca9B7TsDin .cluster text{fill:#333;}#mermaid-svg-pViSZ3Ca9B7TsDin .cluster span{color:#333;}#mermaid-svg-pViSZ3Ca9B7TsDin div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pViSZ3Ca9B7TsDin .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pViSZ3Ca9B7TsDin rect.text{fill:none;stroke-width:0;}#mermaid-svg-pViSZ3Ca9B7TsDin .icon-shape,#mermaid-svg-pViSZ3Ca9B7TsDin .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pViSZ3Ca9B7TsDin .icon-shape p,#mermaid-svg-pViSZ3Ca9B7TsDin .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pViSZ3Ca9B7TsDin .icon-shape .label rect,#mermaid-svg-pViSZ3Ca9B7TsDin .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pViSZ3Ca9B7TsDin .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pViSZ3Ca9B7TsDin .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pViSZ3Ca9B7TsDin :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Dubbo 2.7 以下

只需注册中心
Dubbo 3.x

只需注册中心
Dubbo 任意版本

需要注册+配置
Spring Cloud 体系
你用什么框架?

有什么需求?
Zookeeper

稳定可靠

零外部依赖
Nacos 或 ZK

都可,Nacos 原生适配更好
Nacos

唯一选择

ZK 不适合做配置中心
Nacos

Spring Cloud Alibaba

默认选择
⚠️ ZK 3.8.x

sessionTimeout 设为 40s+

开启 reconfig 功能
⚠️ 需要额外维护 MySQL

端口 8848+9848+9849

截图保存。三条判断标准:Dubbo 版本、是否需要配置中心、是不是 Spring Cloud 体系。每条路径下面附了运维注意事项。


不适用 ZK→Nacos 迁移的两种场景

场景一:集群体量巨大但只做注册,已有成熟的配置管理。 如果你的 ZK 集群已经稳定跑了三年、部署了几百个 Dubbo 服务、配置中心用 Apollo 且满意------不需要迁。迁移的风险(数据不一致、中间状态、回滚验证)大于收益。

场景二:对 ZK 的强一致性有强依赖。 如果你的 Dubbo 服务注册必须强一致(不能容忍任何节点间数据不一致),Nacos 的临时实例(Distro AP 模式)不适合。持久化实例可以用 Raft(CP),但 Nacos 的 Raft 实现只用于持久化实例和服务元数据,不像 ZK 那样所有写入都过 Zab。


你们 Dubbo 项目用什么注册中心?评论区留数字:1=Zookeeper 稳如老狗 2=Zookeeper→正在迁 Nacos 3=已切 Nacos 4=Nacos + Zookeeper 双注册过渡中。顺便说说迁移过程中最让你头疼的那一步。