ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Zookeeper集群平滑迁移实战:从ZAB协议到客户端切换的完整指南

Zookeeper集群平滑迁移实战:从ZAB协议到客户端切换的完整指南 1. 什么时候需要动Zookeeper集群迁移场景与前置条件先说个我自己的真实经历。前两年我们负责的一个核心交易系统Zookeeper集群跑了快四年版本停留在3.4.x所在的物理机也到了退役年限。供应商通知说底层硬件要下架留给我们的迁移窗口只有一个月。当时我脑子里第一反应不是怎么迁而是千万别在迁移过程中把线上搞挂。Zookeeper这东西平时没人注意它但它一旦出问题整个分布式协调链路、Dubbo服务发现、Kafka的Controller选举、HBase的Region分配全都会跟着遭殃。很多人对Zookeeper迁移的第一印象是把数据拷过去、把客户端切过去不就完了。真这么干过的人都知道问题远没有那么简单。Zookeeper虽然是一个协调组件但它的状态不只是数据本身还包括会话状态、临时节点、监听器、事务日志的连续性。把这些东西整体搬家还要保证上层业务无感知这才是平滑二字的真正含义。在进入具体方案之前先帮大家梳理一下到底哪些场景会逼着你做Zookeeper迁移这一步决定了后续方案怎么设计。1.1 触发迁移的常见原因版本EOL3.4.x系列早已停止维护4.x和3.5.x之后才有TLS支持、动态重配置等能力。如果还在跑老版本安全漏洞和bug修复都跟不上等出问题再迁移就晚了。基础设施调整机房裁撤、物理机下架、容器化改造这类原因最被动时间窗口不由你定。容量瓶颈单集群znode数量过大、事务日志增长过快、连接数逼近上限。Zookeeper是单写模型写入能力受限于leader节点的磁盘IO和网络带宽扩不了太狠。架构整改多环境隔离、多机房容灾、权限体系升级需要把现有集群拆成多个逻辑集群。1.2 动手之前必须盘清楚的底账不管什么原因迁移之前一定要先摸清几件事客户端使用清单谁在连这个集群用了什么客户端原生Zookeeper API、Curator、Kazoo、ZkClient连接字符串是写死在配置文件里还是走DNS、域名、VIP还是通过配置中心下发数据规模当前znode总量多少单节点数据量多大超过1GB就要特别小心事务日志目录多大节点角色集群是3节点还是5节点是否有Observer节点当前leader在哪台机器上业务低峰期什么时候读写量最低大促、压测、月末结算这类特殊时间点必须避开。这里我把经验总结成一张自查表方便你归档检查项具体内容影响程度客户端连接方式直连IP / 域名 / 配置中心决定切换策略客户端版本Curator版本过低会导致兼容性问题高临时节点规模服务注册类节点切集群后需要重建高Watcher监听器是否有大量持久监听中数据一致性要求是否存在跨集群双写需求高这块盘不清楚后面所有方案都是空中楼阁。尤其是客户端连接方式我见过不止一个团队把连接串直接硬编码在业务代码里迁移时只能靠发版解决那还谈什么平滑。2. 平滑迁移的底层逻辑ZAB协议给了我们什么底气很多人在设计迁移方案时忽略了一个关键点Zookeeper本身是支持动态扩缩容的而且它的数据同步机制天然适合新旧集群并行这种过渡状态。搞懂ZAB协议你就知道哪些事情能做、哪些事情不能做。2.1 ZAB协议的核心特征Zookeeper使用ZABZookeeper Atomic Broadcast协议保证数据一致性。简单来说所有写请求都由Leader节点接收通过原子广播同步给所有Follower节点超过半数节点确认后写才成功。这个机制决定了三件事新节点加入集群后可以持续从Leader同步增量数据这是以扩代迁策略的基础。只要多数节点存活集群就能继续对外服务迁移过程中不用停写。事务IDzxid是全局严格递增的快照加上事务日志可以完整恢复到任意时间点。2.2 为什么扩容-切换-缩容是一条可行路径平滑迁移的核心思路不是搬家而是扩容新集群灰度切换流量再缩容旧集群。新集群先作为老集群的影子存在数据逐渐追平然后逐步把客户端切到新集群最后让老集群退出服务。这个思路的底气在于Zookeeper的数据同步机制新集群的节点启动时把自己注册到老集群的节点列表里从老集群同步全量快照再追平增量事务日志。数据追平后新老集群的数据是完全一致的在最后一个事务提交点上。客户端切到新集群后重新建立会话、重新注册临时节点、重新挂载监听器一切重新开始。但是这里有个容易忽略的细节ZAB协议保证的是单集群内的一致性不是双集群之间的一致性。新老集群并行期间如果两边都有写入数据就会分叉。所以一个核心原则是迁移过程中同一时间只允许一个集群接受写入。具体操作上就是先让新集群只同步数据不接业务流量等客户端全部切过去之后新集群才正式对外提供写服务。2.3 一个必须理解的约束Session与临时节点Zookeeper的临时节点和会话绑定会话结束临时节点就消失。这意味着客户端切换到新集群后必须重新创建会话所有临时节点都要重新注册。如果你迁移的是Dubbo服务注册中心切换过程中可能出现短暂的服务列表为空的情况因为老集群里注册的服务节点已经在新集群不存在了而新集群里的服务节点还没注册上来。解决这个问题不能靠Zookeeper本身它是无状态的而是要靠客户端的重连重试机制以及迁移窗口的选择。比如先切一批客户端等这一批全部重新注册成功、服务列表稳定之后再切下一批。这个我在后面的实操链路里细讲。3. 新老集群并行迁移的完整实操链路我自己总结了一套经过验证的迁移步骤按顺序执行可以把风险控制在比较低的水平。整个过程可以概括为建新集群、数据对齐、灰度切换、旧集群下线、验证归档五个阶段。3.1 第一步新集群初始化与参数对齐先把新集群的配置和老集群对齐不要在这里引入额外变量。需要重点核对的有tickTime默认2000ms这个参数决定了会话超时、leader选举超时的基础单位新旧集群必须一致。initLimitFollower启动后同步数据的超时时间单位是tickTime数据量大的话要适当调大建议至少10。syncLimitLeader与Follower心跳超时建议5。maxClientCnxns单IP最大连接数老集群如果设了新集群保持一致。dataDir和dataLogDir建议分开目录事务日志单独放一块磁盘否则IO竞争会拖垮写入性能。JVM堆内存Zookeeper默认堆内存只有512MB或1GBznode数量多、单节点数据量大的场景建议直接给到4GB以上。还要确认Zookeeper版本。如果老集群是3.4.x新集群建议直接上3.9.x或当前稳定版本不要从3.4一步跨到老掉牙的3.5.x中间版本。新版本配置项有变化比如4lw.command.whitelist四字母命令白名单默认不开会导致很多运维命令不可用需要显式配置。顺便说一句强烈建议新集群就启用TLS。Zookeeper的明文通信端口在跨机房、跨网络环境下裸奔是很危险的既然迁移了顺手把安全问题解决了。3.2 第二步新集群怎么获得数据这是整个迁移中最关键的一步有两种做法我分别说方案A把新节点临时挂到老集群上追数据以扩代迁把新集群的节点以扩容节点的形式逐个加入老集群。老集群3节点先加1个新节点变成4节点数据自动同步等数据追平后再把这个新节点从老集群中摘除单独组建新集群。优点是不需要手动拷贝数据文件ZAB协议自动完成同步。缺点有两条老集群节点数从3变成4选举机制要求多数派假设老集群本来容忍1台故障变4节点后还是容忍1台故障没变差但也没变好。新节点追数据时如果老集群写入量很大新节点可能需要几个小时才能追上期间不能对外服务。这个方案需要把新旧节点的myid错开一个集群内的myid不能重复。假设老集群myid是1、2、3新集群节点先以myid4、5、6的身份加入老集群同步完成后再把配置改回新集群的规划。方案B离线拷贝快照事务日志老集群选一个低峰期在某个Follower节点的dataDir/dataLogDir所在磁盘上做快照备份。具体做法是先停掉该Follower的对外流量可以通过将其从配置中临时摘除或者直接使用zkServer.sh stop来停止进程然后打包dataDir下的snapshot文件和dataLogDir下的事务日志拷贝到新集群的所有节点上。优点是可以完全离线操作不干扰老集群运行。缺点是需要保证拷贝期间老集群的事务日志都已经完整落盘并且新集群启动后要从快照事务日志恢复数据——如果拷贝时老集群还有新的写请求进来这部分增量数据会丢失。我个人的建议是生产环境优先用方案A因为ZAB协议的同步机制最可靠不会出现人为拷贝导致的数据缺失。方案B更适合数据量特别大几十GB、上百GB的场景因为全量追数据太慢。如果要用方案B务必在老集群上开启快照和日志的自动清理策略确认拷贝出来的快照是完整可用的。还有一种组合做法方案B把快照拷到新集群节点启动新集群后再把新集群节点挂到老集群上追增量。这样数据量大的环境也能在几分钟内完成全量增量对齐。3.3 第三步三种客户端切换方式按优先级选数据对齐后开始切客户端流量。根据客户端连接字符串的配置方式有三种切换路径走VIP/DNS的场景最省事。新集群挂到同一个VIP的后面通过修改VIP的后端列表把老集群节点摘掉、新集群节点加上。切换粒度可控可以做到单台、分批切换。前提是客户端没有缓存DNS解析结果且VIP的健康检查能正确判断Zookeeper节点的存活状态。走配置中心的场景把连接字符串做成配置项下发通过配置中心广播修改。这种方式切换最快但风险也最大——所有客户端同时拿到新配置同时断开老集群连接同时重连新集群瞬间的惊群效应会把新集群打爆。所以配置中心的变更一定要配合灰度发布能力或者把客户端的重连间隔拉长。走配置文件直连的场景只能在客户端所在机器上逐个修改配置重启客户端进程。最原始也最稳妥。如果你是这种场景迁移方案里一定要预留足够长的窗口期。这里有一个很多团队会忽略的点不要试图让新旧集群同时承担读写流量。并行期结束前新集群只接受读流量前提是客户端支持只读模式或者干脆只同步数据不接流量。一旦两边都接受写入数据分叉后整个集群不可信。如果你一定要让两个集群同时服务那必须在应用层自己实现双写但Zookeeper场景下我基本不推荐。3.4 第四步分批切换的节奏与控制假设有100个业务应用需要切换不要一次性切完。建议按以下顺序第一批非核心应用比如数据清洗任务、离线计算任务。这批挂了影响最小可以用来验证新集群的稳定性。第二批只读应用比如管理后台、监控系统。验证新集群的读性能、连接数、watcher数量是否在预期范围内。第三批核心写入应用比如交易主链路、配置下发系统。切之前要确认新集群的磁盘IO、CPU、网络包量都处于健康水位留足余量。第四批依赖服务发现的框架组件比如Dubbo/Kafka等。这些组件涉及大量的临时节点反复注册、注销切换时产生的瞬时连接风暴最明显放最后一批。每批切换之间建议至少观察15-30分钟重点关注新集群的Leader节点CPU/内存/磁盘读写新集群的连接数、znode数量、watcher数量是否在持续增长客户端日志里有没有Reconnect、Session expired之类告警上层业务是否有注册失败、配置获取失败、锁获取超时等异常3.5 第五步旧集群下线前先隔离再处置所有客户端都切换到新集群之后不要急着把老集群关机。先做隔离操作把老集群从VIP/DNS后端列表里摘掉在配置中心里删除老集群连接串。在防火墙层面对老集群的2181端口做限制只允许运维网段的机器访问业务机器禁止连接。观察24-48小时确认没有客户端还在连老集群可以通过echo mntr | nc old_zookeeper_ip 2181查看连接数归零后再进行下一步。停止老集群节点进程保留数据目录至少一周方便事后追查问题。4. 迁移过程中最容易翻车的三个环节Session、临时节点与Watcher这一节值得单独拿出来讲。很多人做Zookeeper迁移失败不是死在数据迁移上而是死在会话语义上。服务器之间拷数据很简单但客户端进程的会话状态是没法拷的。4.1 Session失效引发的问题客户端连接到新集群后原来的Session ID已经失效需要重新建立会话。对于Curator客户端来说默认的会话超时时间是40秒可配置。在会话重建期间客户端处于没有会话的状态此时如果有业务线程在调用Zookeeper API会抛出ConnectionLoss或SessionExpiredException。常见的影响是分布式锁。你用Zookeeper实现分布式锁时如果是通过临时节点Watcher的方式实现的那么切集群后必须重新获取锁。如果业务代码对锁获取失败的处理不当比如直接抛异常而非阻塞重试切换瞬间就会出现大量报错。解决方案是在客户端封装一层重试机制。以Curator为例RetryPolicy retryPolicy new ExponentialBackoffRetry(1000, 10, 5000); CuratorFramework client CuratorFrameworkFactory.builder() .connectString(newConnectString) .sessionTimeoutMs(20000) .connectionTimeoutMs(10000) .retryPolicy(retryPolicy) .build();只要重试策略配置合理大部分切换过程中的瞬断都是可以自愈的。但要注意不要把重试次数调到几十次、上百次否则切换瞬间所有客户端同时疯狂重试新集群会直接被重试风暴打死。4.2 临时节点重建的时序问题服务注册场景下客户端切到新集群后必须重新注册临时节点。这里有一个先有鸡还是先有蛋的问题新集群里还没有服务节点注册信息如果此时有消费方来查询服务列表查到的可能是空的或者不完整的。Dubbo框架本身就处理了这个问题它的注册中心断连后会走本地缓存的服务列表。所以切换过程中短暂的服务列表为空对Dubbo的消费者来说通常不致命。但如果是你自己写的服务发现代码没有本地缓存机制那就危险了。解决思路是在切换前先手动在新集群里把已知的永久节点比如接口路径、分组信息创建好临时节点通过客户端重新注册自然恢复。至少保证新集群的znode树结构是完整的临时节点数量从0开始逐步恢复。4.3 Watcher丢失与监听器失效Watcher是Zookeeper里最容易被忽视的机制。客户端可以通过Watcher监听节点的数据变化、子节点列表变化、节点删除等事件。问题是Watcher只对建立Watcher的那个会话有效会话断开后Watcher就丢失了。迁移期间如果客户端断线重连之前注册的Watcher全部失效。对于依赖Watcher做配置动态更新的场景比如监听某个配置节点的数据变化客户端切到新集群后必须重新注册Watcher。如果你的业务代码只在初始化时注册了一次Watcher没有做重连后的重新注册那么切换后配置变更就永远收不到通知了。Curator的NodeCache、PathChildrenCache这些工具类自带重新注册机制会好很多。原生API的Watcher必须自己在连接状态恢复时重新注册这一点一定要在迁移前的代码Review中检查到位。5. 回滚方案与事后验证清单5.1 什么时候触发回滚不管方案设计得多周密都要预留回滚能力。我建议把回滚触发条件写成明确的检查项切换后新集群的Leader节点CPU持续超过80%连接数超过新集群最大连接数的70%客户端日志中出现大量SessionExpired且无法自愈服务注册的成功率低于99.9%核心业务链路出现Zookeeper相关的超时告警。只要触发以上任意一条立刻停止后续批次的切换已经切换的客户端按原路径切回。这里的关键是按原路径切回所以你在配置中心或DNS上做的每一次变更都要有记录方便回滚时精确撤销。5.2 回滚的操作动作如果是VIP切换把VIP的后端列表改回老集群节点。如果是配置中心下发把连接字符串改回老集群发布一次全量配置。如果是直连IP需要回滚所有已改配置的服务器。回滚后不要马上关掉新集群让它继续运行观察一段时间。回滚本身也可能引发第二次瞬断要确保客户端能再次重连成功。5.3 迁移完成后的验证清单全部切换完成后我习惯按以下清单做验收验证项方法预期结果znode数据一致性对关键业务节点执行get对比新旧集群关键数据完全一致连接数核对mntr查询集群各节点连接数与迁移前的总量基本一致临时节点数量统计服务注册相关路径的znode数量与迁移前一致事务日志连续性检查新集群的zxid大小大于老集群的最后一个zxid上层业务巡检抽查Dubbo服务发现、分布式锁等核心场景无异常告警这里补充一个经验对比新旧集群的数据重点不是对比所有znode而是对比有状态的核心节点比如分布式锁的持有者、配置版本号、Master选举的结果。这些节点数据错了才是大问题纯静态配置错了改起来也容易。6. 实战排错经验同事们在Zookeeper迁移中踩过的坑6.1 坑一myid配置错误导致节点起不来新集群节点myid写错是最低级的错误但也是最常见的。我见过有人在迁移时直接拷贝了老集群的myid文件到新节点导致新集群节点启动时在选举阶段互相争执。这里提醒一句每个节点dataDir下的myid文件只能有一个数字而且是唯一的必须仔细核对。6.2 坑二JVM堆内存不足导致Full GCZookeeper默认的堆内存设置很小很多发行版启动脚本里只给了512MB。迁移时如果误把大量持久节点拷贝到新集群数据加载阶段会发生频繁Full GC表现为客户端连接结果持续变慢、Leader选举超时。这一步务必在迁移前就调好堆内存并且做好监控。6.3 坑三磁盘IO不一致导致事务日志跟不上Zookeeper是单写模型所有事务日志都要落盘。如果把dataLogDir放在根磁盘上和系统日志、业务程序抢IO事务日志写入延迟会飙升。迁移时有人图省事新集群所有目录统统放一块盘结果切完核心业务后发现broker频繁卡顿。事务日志必须用独立磁盘SSD优于HDD这一点不要妥协。6.4 坑四只迁移了数据没有迁移ACL和QuotaZookeeper的znode可以设置ACL权限控制可以设置Quota配额。部分业务方的代码里可能会在运行期创建节点并设置ACL如果你只拷贝了数据目录没有检查老集群的ACL配置和Quota设置新集群上创建同名的节点会因为权限不足而失败。迁移前可以用getAcl把关键节点的ACL打一份快照迁移后抽查比对。6.5 坑五低峰期选择不当有人把迁移窗口定在工作日中午觉得点击量低就行。但Zookeeper的写入量和业务点击量不是一回事很多后台任务定时作业、数据同步任务在整点或半点集中执行定时任务一跑写请求会瞬间暴涨。真正的低峰期要看Zookeeper自身的监控曲线最好是观察一周后确定稳定低谷通常是在凌晨三点到五点之间。6.6 一点点个人体会最后说一点不太容易被写进文档里的体会。Zookeeper平滑迁移这件事最难的不是技术方案而是过程中如何让上层业务团队放心。我在迁移前花了大量时间给业务方解释你会看到什么样的告警、客户端会不会自动重连、如果挂了怎么回滚。把这些沟通做到位迁移过程中才不会被连环夺命call打乱节奏。还有一个土办法很管用先搭一套和线上配置完全一致的新集群跑几天压测和混沌演练演练完了不删留着当天做切换的备用方案。这套预演环境不仅能验证技术方案还能让整个团队提前熟悉操作流程。真正到了迁移那天大家按演练过的动作走心里就有底得多。
返回列表