
手上有套 ZooKeeper 集群要扩容3 节点想加到 5 节点。查资料的时候发现网上说法两极分化一边说 ZooKeeper 集群压根不支持动态加服务器必须滚动重启另一边说可以直接执行 reconfig 命令。我按后者试了一遍结果第一次就报错。后来把官方文档翻了个底朝天才搞清楚来龙去脉——ZooKeeper 从 3.5.0 开始确实支持动态重新配置Dynamic Reconfiguration但默认关闭而且很多人被旧版本的静态配置思维带偏了。这篇文章整理一下我的实际操作和踩坑过程给刚好要做集群扩容的人一个参考。1. 为什么 3.5.0 之前加一台服务器要重启整个集群先说结论ZooKeeper 集群支持动态添加服务器但只支持 3.5.0 及以后版本并且需要显式开启 reconfigEnabledtrue。我身边不少运维朋友第一反应都是“不能动态加只能全部重启”这个印象大概率来自 3.5.0 之前的版本。1.1 静态配置模式下成员的固化方式在旧版本里集群成员列表是写死在 zoo.cfg 里的典型长这样server.1192.168.1.101:2888:3888 server.2192.168.1.102:2888:3888 server.3192.168.1.103:2888:3888每个节点启动的时候把这份配置读进内存之后整个集群的投票集合voting set就固化了。ZooKeeper 的 Zab 协议靠这个集合计算法定人数quorum任何一次 leader 选举、事务提交都要围绕这个集合来运作。这时候你想加一台 server.4光改一台机器的 zoo.cfg 没有意义因为另外三台机器根本不认识 server.4它们投票时不会把它算进去。新节点即使启动了也永远进不了 quorum。唯一的办法是先停掉整个集群全部机器改用包含 server.4 的新配置文件然后再逐台启动。1.2 滚动重启的实际代价生产环境里大家通常不会停集群而是采用滚动重启一台一台重启每台都加载新的配置。但这套操作看着温柔实际风险不小每次重启都会触发该节点与集群的重新同步join 过程中如果有两台节点同时不可用就可能触发 leader 选举。滚动重启期间连接全部断掉重连客户端会出现短暂的 ConnectionLoss 异常对依赖 ZooKeeper 做分布式锁、配置中心、服务注册的系统影响尤其明显。如果某台节点重启后因为配置错误没能加入集群现场就变成“部分节点配置不一致”排查起来非常头大。这也是为什么 3.5.0 之前扩容一台机器运维都会安排在凌晨窗口期操作。但现在有了动态重配机制这个痛点基本被解决了。1.3 静态配置与动态配置的关键差异我整理了一张对比表可以直观看出两种模式的差别对比项静态配置模式3.4.x 及之前动态重配模式3.5.0成员列表存放位置zoo.cfg动态配置文件如 zoo.cfg.dynamic修改成员方式改配置 重启集群reconfig 命令在线修改是否影响对外服务有滚定重启窗口无感知配置版本管理无有配置事务版本号递增是否需要额外开启不需要需要 reconfigEnabledtrue2. 动态重配的底层机制配置事务与法定人数校验理解动态重配机制不需要把 Zab 协议啃一遍但几个核心概念必须搞清楚否则连 reconfig 命令为什么有的成功有的失败都看不明白。2.1 这份配置不是写在文件里而是作为事务提交的动态重配的核心思路是把配置当成一条特殊的事务来提交。ZooKeeper 内部维护了一个配置节点 /zookeeper/config这个节点里保存着当前集群的成员列表、角色信息以及配置版本号。当你执行 reconfig 命令时流程是这样的你连接任意一个节点发送 reconfig 请求。如果连的是 Follower该节点会把请求转发给 Leader。Leader 校验新配置是否合法比如法定人数是否满足校验通过后把新配置封装成一个 configuration transaction。配置事务通过 Zab 协议提交到整个集群所有节点应用后新配置正式生效。看到没有这里的“配置文件”不是某台机器本地那个 zoo.cfg而是 ZooKeeper 集群内部的一条分布式日志记录。节点本地文件只是这个日志在磁盘上的一个投影。正因如此某个节点本地配置写错了也没关系只要它能连上集群里的其他节点就能拿到最新的配置数据。2.2 法定人数校验为什么能防止“自杀式”删节点reconfig 不是随便删成员都能成功的。Leader 在提交配置事务之前会检查新配置中的法定人数。怎么理解这个校验呢假设集群里有 3 个 participant 节点法定人数是 2多数派。如果你执行一条 reconfig 同时删掉 2 个节点新配置里只有 1 个节点那这个集群无论如何都不可能达到法定人数后续所有事务都无法提交整个集群也就瘫痪了。这种“自杀式”操作会被直接拒绝。但如果是从 5 节点删 2 个还剩 3 个法定人数是 3仍然满足多数派要求操作就会被接受。所以大家在批量缩容的时候心里要先算一笔账删完之后剩下的 participant 数量是否仍然能凑齐多数派。2.3 配置版本号与并发修改配置事务每次提交都会让 /zookeeper/config 的版本号递增。你可以在执行 reconfig 时通过 -v 参数指定期望的版本号。如果版本号对不上说明配置已经被别人改过操作就会失败。这类似乐观锁的机制防止两个管理员同时执行 reconfig结果把集群配置改成一个互相覆盖的混乱状态。我在测试环境试过一次两个会话同时向集群发起 reconfig一个加节点一个改端口。结果只有一个成功另一个报了 VERSION_MISMATCH。看到这个报错别慌重新查一下当前配置把 -v 参数改成最新版本再执行就行。2.4 动态配置文件与 zoo.cfg 的分工启用动态重配之后zoo.cfg 里就不是直接写 server.1、server.2 这种了而是通过 dynamicConfigFile 参数指向一个独立的动态配置文件比如# zoo.cfg tickTime2000 initLimit10 syncLimit5 dataDir/var/lib/zookeeper clientPort2181 reconfigEnabledtrue dynamicConfigFile/data/zookeeper/conf/zoo.cfg.dynamic动态配置文件 zoo.cfg.dynamic 里才是真正的成员列表server.1192.168.1.101:2888:3888:participant;2181 server.2192.168.1.102:2888:3888:participant;2181 server.3192.168.1.103:2888:3888:participant;2181注意几个细节zoo.cfg 里保留了 tickTime、initLimit、dataDir、clientPort 这些基础参数这些属于静态配置不能通过 reconfig 修改。server 列表挪到了动态配置文件里成员、角色、端口等信息可以动态调整。server.X 的格式扩展成了host:quorumPort:electionPort:role;clientPort相比旧版多了 role 和 clientPort。role 可以是 participant投票节点或 observer观察者节点不参与投票。3. 在线扩容实操加一台新节点的完整步骤这节直接上实操。我用的环境是 3 个旧节点加 1 个新节点ZooKeeper 版本 3.6.3操作系统 CentOS 7。这里说明一下ZooKeeper 3.5.0 以上才有动态重配功能3.4.x 及更低版本不要尝试不支持就是不支持。3.1 在旧节点上开启 reconfig 开关这是最关键的一步也是我一开始踩坑的地方。默认情况下即使你用的是 3.5.0 版本reconfig 也是关闭的。你需要先在每台旧节点的 zoo.cfg 里加一行reconfigEnabledtrue然后滚动重启这三个节点。注意这次重启是因为要用新配置启动集群之后再加节点就再也不需要重启了。如果不加这个配置执行 reconfig 会直接报错错误信息类似Reconfig is not enabled我当时就是漏了这一步盯着报错看了半天才反应过来。3.2 把 server 列表迁移到动态配置文件如果旧节点之前用的是静态配置模式server 列表直接写在 zoo.cfg 里需要先把这个列表挪到动态配置文件里。具体操作在每台旧节点的数据目录下新建动态配置文件我习惯放在/data/zookeeper/conf/zoo.cfg.dynamic然后把 server 列表写进去server.1192.168.1.101:2888:3888:participant;2181 server.2192.168.1.102:2888:3888:participant;2181 server.3192.168.1.103:2888:3888:participant;2181同时把 zoo.cfg 里原来的 server.1、server.2、server.3 那几行删掉改为dynamicConfigFile/data/zookeeper/conf/zoo.cfg.dynamic然后重启三个节点让集群切到动态配置模式。这里我踩过一个小坑如果 zoo.cfg 里仍然保留旧的 server 列表而 dynamicConfigFile 里也有一份两个列表内容不一致时节点会以动态配置文件为准但日志里会不停刷 warning容易干扰排查。3.3 新节点的基础准备新节点 192.168.1.104 要做的事和普通 ZooKeeper 节点初始化一样创建数据目录、配置文件目录生成 myid 文件mkdir -p /data/zookeeper/data mkdir -p /data/zookeeper/logs mkdir -p /data/zookeeper/conf echo 4 /data/zookeeper/data/myid新节点的 zoo.cfg 同样要指定动态配置文件路径这个动态配置文件里可以只写一个已知节点的信息# 新节点 zoo.cfg tickTime2000 initLimit10 syncLimit5 dataDir/data/zookeeper/data clientPort2181 reconfigEnabledtrue dynamicConfigFile/data/zookeeper/conf/zoo.cfg.dynamiczoo.cfg.dynamic 里先写server.1192.168.1.101:2888:3888:participant;2181这里只需要认识一个已知节点就够了不需要把整个集群的列表都写上。新节点启动后会通过这个地址连接集群向对方请求最新的配置数据。3.4 执行 reconfig 添加新节点新节点启动后它会尝试连接 192.168.1.101 并等待加入。这里先不用管它在日志里报什么连接不上之类的信息因为此时集群配置里还没有它。接下来在任意一个旧节点上执行 reconfig 命令即可。推荐先用 zkCli 连接集群bin/zkCli.sh -server 192.168.1.101:2181然后在 zkCli 交互界面执行reconfig -add server.4192.168.1.104:2888:3888:participant;2181只要返回成功配置事务就会在整个集群生效。新节点收到新配置后会自动进入 quorum 并成为 participant。验证方式也很简单。在 zkCli 里执行config能看到当前配置列表已经包含 server.4。再执行stat /返回信息里可以确认新节点的角色。如果想更直观也可以逐台在旧节点上运行bin/zkServer.sh status看到输出 Mode: follower 就说明新节点已经正常参与投票了。3.5 扩容到 5 节点时的重复操作如果是从 3 节点扩到 5 节点重复执行第 3.3 和 3.4 步即可把 192.168.1.104 换成 192.168.1.105myid 写成 5reconfig 参数对应改成 server.5。整个集群的对外服务从头到尾不会中断。我实测下来加节点的过程耗时大概就是一次配置事务提交的时间毫秒级完成。相比以前滚动重启动辄十几分钟的服务中断窗口体验差别非常大。4. 动态重配的高频踩坑点与边界条件这节把我在测试和生产环境遇到的各种边界情况集中整理一下大家对照排查会省很多时间。4.1 reconfigEnabled 没有配置导致功能不可用这是最常见的坑。3.5.0 之后即使版本满足要求没有设置 reconfigEnabledtruereconfig 命令仍然不可用。建议从一开始装集群就加上这个配置避免以后扩容时记不起来这回事。4.2 新节点配置里必须有能连上的已知节点新节点启动时动态配置文件里如果完全没写任何已知节点的地址它会直接起不来日志会提示无法找到可连接的集群成员。这个列表不需要写全但至少要有一个当前集群在线的节点。我见过有人在生产环境里把新节点的动态配置文件和旧节点完全一致然后启动失败排查了半天才发现是防火墙把 quorum 端口2888 和 3888挡了。所以看到连接失败先别怀疑配置先确认网络通不通、端口通不通。4.3 删节点时法定人数校验删除节点同样通过 reconfig 完成reconfig -remove 4但要注意一次删太多会导致新配置无法形成法定人数直接失败。比如 3 节点集群一次删 2 个5 节点集群一次删 3 个。如果确实要缩容到很小的规模建议分批操作先把节点减到多数派允许的数字再继续减。另外删除的如果是当前 leader也不影响操作本身——leader 会先把配置事务提交然后新 leader 会在剩余节点中选举产生。4.4 Observer 角色与动态重配的关系Observer 是 ZooKeeper 里的特殊角色它不参与投票只同步数据并提供读取服务。在动态重配中Observer 完全可以当作普通成员添加和删除命令格式一样只是角色字段写 observerreconfig -add server.4192.168.1.104:2888:3888:observer;2181实际生产经验里扩容时先把新节点加为 observer观察一段时间确认节点稳定、数据同步正常之后再通过 reconfig 把角色改成 participant是一个比较稳妥的扩容姿势。这可以避免新节点刚加入就参与投票万一它在选举时出现异常影响面会小很多。4.5 ACL 权限导致 reconfig 被拒如果集群开启了 ACL权限控制reconfig 默认需要超级用户或具有 reconfig 权限的账户才能执行。用普通客户端执行时可能会看到无权限的报错。解决办法有两个设置超级用户摘要在 zoo.cfg 里配置zookeeper.DigestAuthenticationProvider.superDigest。提前为可信任的账号授权 reconfig 权限。如果你没配 ACL默认是无鉴权模式任意客户端都可以执行 reconfig。这就有个安全隐患网络里任何能连上 2181 端口的人都可以改动集群成员列表。生产环境还是建议把 ACL 配上别偷懒。4.6 老版本和新版本之间的兼容问题3.5.0 刚引入动态重配时我记得 3.5.0 到 3.5.3 之间有几个和 reconfig 相关的 bug。所以建议直接用 3.6 版本。如果当前集群还是 3.4.x那就别想着用 reconfig 了老老实实走停集群扩容的流程。反过来如果集群用了 3.5.0 并开启了动态重配就不能通过直接改 zoo.cfg 里动态配置文件内容的方式去加节点因为集群内部配置事务记录可能和文件不一致重启后被文件覆盖回旧状态让人一头雾水。5. reconfig 的进阶用法与工程化建议reconfig 不止能用来加节点角色切换、批量修改端口都是它的能力范围。这节说几个工程里用得上的进阶玩法。5.1 全量替换与增量修改两种模式reconfig 命令支持两种传参方式增量模式-add和-remove适合每次只改少量成员。全量模式-members一次性传入整份成员列表适合批量调整。全量模式示例reconfig -members server.1192.168.1.101:2888:3888:participant;2181,server.2192.168.1.102:2888:3888:participant;2181,server.3192.168.1.103:2888:3888:participant;2181,server.4192.168.1.104:2888:3888:participant;2181全量模式如果写漏了现有节点相当于一次性删掉所有没写的节点执行前务必反复核对。5.2 通过 Java API 调用 reconfig自动化运维的时候用 zkCli 手工执行命令不是长久之计。ZooKeeper 的 Java 客户端 API 里提供了 reconfig 方法ZooKeeper zk new ZooKeeper(192.168.1.101:2181, 5000, null); String config server.4192.168.1.104:2888:3888:participant;2181; byte[] newConfig zk.reconfig(config.getBytes(), -1, null); // 返回的 newConfig 就是生效后的最新配置内容 System.out.println(new String(newConfig)); zk.close();reconfig 方法还有一个重载版本可以指定期望的版本号。自动化脚本里建议都用带版本号的版本避免并发修改问题。5.3 用四字命令快速确认配置状态ZooKeeper 的四字命令four letter words里config命令可以直接输出当前配置不需要进入 zkCliecho config | nc 192.168.1.101 2181输出内容就是动态配置文件里的成员列表。这个命令很适合写进巡检脚本定时检查集群成员是否和预期一致。5.4 从 observer 提升为 participant 的命令写法刚才提到先加 observer 再提升 participant具体命令是reconfig -add server.4192.168.1.104:2888:3888:participant;2181当 server.4 已经以 observer 身份存在于配置里时直接重新 add 一次角色就会从 observer 变成 participant。这点实测有效配置事务会覆盖旧的 server.4 行。6. 我的实测心得与建议最后说几点个人体会。动态重配确实把 ZooKeeper 集群扩容从“停服操作”变成了“在线操作”但不要因为不需要重启就掉以轻心。我强烈建议在测试环境先把整套流程走一遍尤其是 3 到 5 节点这种常见扩容路径确认 reconfigEnabledtrue、动态配置文件路径、ACL 权限三个关键点都没问题再去动生产。扩容完成后有个小习惯建议保持把动态配置文件备份一份放到安全的配置管理仓库里。虽然 ZooKeeper 内部有配置事务记录但毕竟多一份外部备份哪天集群被误删节点能快速恢复。我见过有人用 reconfig 全量模式改配置结果把现有节点写漏了瞬间把 7 节点集群砍成 3 节点还好配置备份在手配合版本号参数快速加回来了。实际处理时还有一个细节reconfig 成功后建议在每台节点上手动核对一下echo config | nc localhost 2181的输出确认所有机器上的配置一致。虽然配置事务本身有原子性保证但多看一眼总是稳妥的。毕竟集群这种事任何“我感觉没问题”都是不负责任的。