ZooKeeper-3.4.6集群选举Bug踩坑与恢复记录

1 问题背景

我司缓存Codis依赖的ZooKeeper服务,在探索异地机房数据库高可用方案时,期望通过添加observer的方式实现异地容灾,但在方案实施阶段出现了添加observer节点失败,并发现选举端口不通的日志记录,复现状态如下:

:???,先telnet一下leader的3888端口试试

:3888 真的不通了?!!新节点不能加入集群!!!但是老集群读写状态是正常的,这情况太离谱了......

:重启老节点,集群应该会重新发现节点,顺便恢复选举端口

......

:完了...老节点端口通了但是也回不到集群了??!

捋清操作时间线,同步ZK集群现状,向各位同事大佬求助,尝试将集群恢复到正常状态。

为了填平赌上职业生涯重启ZK节点的坑,开启了本篇的探索之旅。

2 探索

2.1 各节点端口的连通性统计

2.2 未重启节点状态检查

close_wait的来源都是安全扫描的IP地址

但是根本没有成功创建连接

2.3 通过日志文件寻找线索失败

因为使用的默认配置启动服务,ZK日志没有有效的切割和生命周期管理,仅使用 ">" 清空处理过,当前最大的日志已经堆积了20G之多,遗憾的是最后通过排查也没有找到有效信息。

2.4 线上ZK节点添加端口探测监控

监控添加后大约1个小时左右,重启的新节点3888选举端口又不通了!!

2.5 柳暗花明

通过ITCP架构群发出issue,得到了去哪儿网小伙伴的积极回应,对比两方的jstack,发现我们的ZK缺少了QuorumCnxManager$Listener线程!!!

这个线程是负责监听3888选举端口,并accept选举请求的。我们未重启节点里这个线程都没了!!!

由此一切都解释通了,多数节点的选举功能失效,当有节点想加入集群时,当然不可能通过。

具体的排查验证过程可查阅往期精品文章 ↓↓↓

如果你也在用ZK,那这个导致集群挂掉的坑一定得注意!

至于这个issue问题的修复也在 ZooKeeper-3.4.7版本完成,使用高版本的读者朋友们可以放心了~

3 集群恢复方案制定

3.1 测试环境还原线上集群

测试环境使用线上snapshot恢复搭建 ZooKeeper-3.4.6 版本5节点集群,myid 和 角色完全与线上一致,然后通过telnet -1 将所有节点3888选举端口打挂掉,重启测试集群6号/8号两节点形成以下状态。

3.2 测试Codis集群和demo服务连接到测试ZK集群

确认Codis-Proxy在测试ZK集群内注册了临时节点;

确认服务在使用不同版本客户端时,可以从测试ZK集群获取到Codis-Proxy地址信息创建连接且读写操作正常。

3.3 关键要素测试

3.3.1 ZooKeeper 版本升级

ZooKeeper-3.4.6以后版本,使用版本号3.4.13,是否完美向下兼容,镜像恢复是否可行,操作流程顺序是什么?

3.3.2 Codis-Proxy 临时节点注册功能

在ZK集群和ZK节点服务均正常情况下,Codis-Proxy连接到ZK集群会向 /jodis/{CodisProductName}/ 写入 proxy-{proxyToken} 临时节点信息,ZK集群恢复的判断条件之一必须是Codis-Proxy临时节点注册没有异常。

3.3.3 多个版本的Codis客户端表现

服务启动后客户端会缓存ZK节点域名解析地址(形如codiszk.domain.com:2181)无法变更,只能原地操作集群恢复;

客户端连接到ZK集群后,能否获取到Codis-Proxy临时节点,并完成Codis集群数据的读写功能。

3.3.4 日志切割

指定存储路径,按日切割,如何建立完整的日志滚动生命周期管理规则。

3.4 方案选择

关键要素经过多轮测试,汇总,总结以下两套备选方案

3.4.1 原地滚动升级

准备新的ZooKeeper-3.4.13工作目录,老集群写入Codis-Proxy永久节点数据,将6号和8号老节点停服,在新目录使用老集群最新镜像启动新服务,然后老集群内存活的3节点顺序关闭的同时启动新版本ZK节点,形成3.4.13版本新集群。

此方案优点:

  1. 操作简单,理解容易,连续性强;

  2. 创建新工作目录后,复制老集群leader数据目录,日志目录,配置文件过来,更新 myid 即可。

缺点:

  1. 一定会引起ZK服务中断(老集群3节点任意关闭一个则整体不可用并触发重新选举)。

3.4.2 引入新集群然后恢复到5节点集群

由于6号/8号两节点已经不提供服务,老集群数据短时间内也不会有变动,索性重新搭建一套3节点集群,数据与线上一致,然后重新扩容恢复到5节点集群!

准备新的ZooKeeper-3.4.13工作目录,老集群写入Codis-Proxy永久节点数据,将6号和8号老节点停服并引入一个新节点,在新节点工作目录使用老集群最新镜像启动服务,然后两个离线节点机器与临时新节点组成3节点小集群,此时小集群的数据与线上一致且刷入了Codis-Porxy永久节点数据,已经可以提供服务,后面只需要尽快把老集群节点关闭转为高版本新节点重新加入新集群就好了。

此方案优点:

  1. 规避了ZK服务中断的情况,老集群节点全部离线后,客户端可以向小集群重新建连。

缺点:

  1. 主动分裂形成了两个集群,有导致数据不一致的风险,必须尽快完成集群的恢复。

4 方案落地

人生真是寂寞如雪呀...

2024年的1月都比平时更冷了一些...

某天后半夜工位周围聚集了一圈大佬,最后确认了以下恢复流程。

4.1 日志滚动调整配置文件

log4j被爆出过安全性漏洞,ZooKeeper-3.4.13使用了log4j 1.2.17,在没有明确引用SocketServer创建监听服务时,安全性较高,如果想完全解决需要升级log4j 2.x版本或者升级到ZooKeeper更高版本。

4.1.1 环境配置文件bin/zkEnv.sh

  1. 修改默认日志文件路径到 logs下,增加日志配置 按日滚动规则

4.1.2 节点配置文件conf/log4j.properties

  1. 增加滚动升级配置方案支持和统一启动文件日志名称
  1. 修改滚动升级方案到按日切割

4.2 准备3节点小集群

老ZK集群节点6号/7号/8号/9号/10号都必须在实例本机ZooKeeper-3.4.13版本新工作路径操作节点替换,业务客户端缓存的ZK节点DNS解析IP地址不能变。

  1. 从配置文件conf/zoo.cfg确认dataDir(镜像保存路径)和 dataLogDir(事务日志保存路径);

  2. {dataDir}路径更改myid: 老集群6号节点替换为2号,老集群8号节点替换为3号,1号为临时引入实例,最后清理掉;

  3. 配置文件conf/zoo.cfg注释线上7号/9号/10号/添加1号,关闭6号/8号实例,删除{dataDir}/version-2 和 {dataLogDir}/version-2 目录;

  4. 确保线上ZK集群已经刷了全量Codis-Proxy永久节点数据到/jodis下,从10号(leader)拷贝dataDir和 dataLogDir完整目录到 1号 ZK工作路径下,启动1号,再启动2号/3号节点(2号/3号仍在接收线上请求,后启动直接恢复全量数据即可接收连接提供服务)。

4.3 老节点工作路径配置调整与滚动操作

同样在实例本机ZooKeeper-3.4.13版本工作路径操作,各节点先提前备好server信息:

  1. 7号实例: 调整myid为4号,conf/zoo.cfg 调整实例信息为server 1234,4条;

  2. 9号实例: 调整myid为5号,conf/zoo.cfg 调整实例信息为server 12345, 5条;

  3. 10号实例: 调整myid为6号,conf/zoo.cfg 调整实例信息为server 123456, 6条;

  4. 关闭 7号/9号/10号 全部老实例,切换到ZooKeeper-3.4.13工作目录操作(关闭任意实例后,Codis-proxy即断连并向新集群注册);

  5. 启动4号新实例,加入新集群,调整123配置文件,添加4号server信息,然后顺序重启实例231(1号为leader,最后重启),此时4号为leader;

  1. 启动5号新实例,加入新集群,调整1234配置文件,添加5号server信息,然后顺序重启实例1234,此时5号为leader;
  1. 启动6号新实例,加入新集群,调整12345配置文件,添加6号server信息,然后顺序重启实例12345,此时6号为leader;
  1. 关闭1号实例,调整23456配置文件,注释1号server信息,然后顺序重启实例23456,集群恢复,此时5号为leader。

操作期间反复的确认各ZK节点的连接情况,服务状态,集群状态,日志信息

最终...

csharp 复制代码
[root()@XShellTerminal bin]#echo mntr |nc localhost 2181   

zk_server_state leader   

zk_synced_followers 4.0   

......

ZK集群恢复完成,升级完成,日志切割完成!!

5 总结

  • ZooKeeper的端口探测功能需要健全,比如服务端口/选举端口/同步端口,本次踩坑经历本可以尽早发现并及时处理;

  • 不要纠结于当前的Bug问题,小版本升级让你无后顾之忧;

  • 方案的落地需要经过前期严格周密的关键目标测试,尽量做到一切符合"预期";

  • 做好兜底策略,只有一万,没有万一。


关于作者

王嗣鑫,DBA,转转缓存/运维平台开发负责人,平平无奇的一个踩坑奇才。

markdown 复制代码
> 转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。 
> 关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~
相关推荐
油头少年_w19 小时前
ZooKeeper和Hadoop高可用(主备切换)
zookeeper
运维&陈同学21 小时前
【zookeeper04】消息队列与微服务之zookeeper客户端访问
linux·后端·微服务·zookeeper·云原生·消息队列·云计算
_Matthew2 天前
学习Zookeeper
分布式·zookeeper·云原生
运维&陈同学2 天前
【zookeeper02】消息队列与微服务之zookeeper单机部署
linux·服务器·分布式·微服务·zookeeper·云原生·消息队列·云计算
运维&陈同学4 天前
【zookeeper03】消息队列与微服务之zookeeper集群部署
linux·微服务·zookeeper·云原生·消息队列·云计算·java-zookeeper
运维&陈同学4 天前
【zookeeper01】消息队列与微服务之zookeeper工作原理
运维·分布式·微服务·zookeeper·云原生·架构·消息队列
谭震鸿5 天前
Zookeeper集群搭建Centos环境下
分布式·zookeeper·centos
求积分不加C6 天前
zookeeper is not a recognized option--解决方案
分布式·zookeeper·云原生
Karoku0668 天前
【企业级分布式系统】ZooKeeper集群
linux·运维·数据库·分布式·zookeeper·云原生
fengqing55788 天前
zookeeper安装教程
分布式·zookeeper·云原生