ARTICLE DETAIL

资讯详情

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

Zookeeper四种Server状态解析:从状态机流转到选举与监控实践

Zookeeper四种Server状态解析:从状态机流转到选举与监控实践 前阵子帮朋友做模拟面试对方把Zookeeper的四种Server状态——LOOKING、FOLLOWING、LEADING、OBSERVING——背得很流利结果面试官追问了一句“LOOKING期间客户端请求到底去哪了”他当场卡壳。这个场景我见过太多次了。很多人对Zookeeper状态的理解停留在“背出四个英文单词”的层面但真正的面试追问和线上排障考的全是状态背后的状态机流转、选举逻辑、以及生产环境里的表现。这篇文章就把这四种状态彻底拆开从状态机的角度、选举的细节、Observer的特殊性、再到监控和故障判读适合准备Zookeeper面试的同学也适合正在维护ZK集群的SRE和架构师参考。1. 状态机里的四宫格LOOKING、FOLLOWING、LEADING、OBSERVING各自承担什么1.1 一张表先分清四个角色关于Zookeeper集群有一个最常见的误解以为服务器状态只有“Leader”和“Follower”两种。实际上Zookeeper官方把Server状态划分为四种而且这四种状态并不完全对等。我先用一张表把它们的定位说清楚Server状态角色定位是否参与投票是否处理写请求转发是否同步数据典型场景LOOKING选举中是投出自己的票否否等待新Leader产生刚启动、Leader失联、集群重建FOLLOWING跟随者是是转发给Leader是正常集群中的非Leader节点LEADING领导者是是直接处理是主导数据同步正常集群中唯一的LeaderOBSERVING观察者否是转发给Leader是单向接收读扩展、跨机房容灾、不下线扩集群这张表里最值得玩味的是LOOKING。其他三种状态都代表“集群里已经有Leader、服务在正常运行”只有LOOKING是一个“不稳定状态”——节点正在寻找或者参与选举一个新的Leader。虽然Zookeeper的ServerState枚举里LOOKING排在第一个但它绝不是简单的“刚启动”而是集群可用性最脆弱的时期。面试官最爱的追问点就在这里LOOKING状态有没有对应的“正常”阶段答案是没有。Zookeeper集群一旦出现长时间处于LOOKING的节点说明它已经脱离正常服务状态要么集群正在发生Leader切换要么集群已经不满足“过半存活”的容错条件。1.2 状态转换的触发条件比单纯背状态更值钱理解了四宫格的定位下一步要看它们之间如何流转。这部分是很多面试解析一笔带过、但实际考察频率非常高的地方。正常运行的节点只有三种终点初始启动进入选举、享受稳定服务、异常后回到选举。展开来看分为几条路径启动链路服务端进程启动后先加载本地快照snapshot和事务日志txnlog把数据恢复到内存然后自然进入LOOKING参与集群选举。选举完成后胜出的节点进入LEADING其余节点进入FOLLOWING或OBSERVING。Follower失联链路FOLLOWING节点与Leader之间靠心跳维持关系一旦超过阈值没有收到Leader的有效消息Follower会自己进入LOOKING重新发起选举而不是原地等待。Leader降级链路LEADING节点如果发现自己已经失去多数Follower的支持比如网络分区半数以上投票节点联系不上了它会主动放弃Leader身份进入LOOKING避免出现“自己也联系不上多数却还强行对外提供写服务”的脑裂状态。Observer的特例OBSERVING节点不参与选举所以Leader失联时它不会进入LOOKING而是一直跟随当前集群状态直到新Leader诞生后继续同步数据。这里有一个容易踩坑的地方很多人以为心跳超时就是tickTime实际上Zookeeper把心跳超时拆成了两个参数。initLimit用于Follower启动时与Leader完成首次数据同步的上限syncLimit用于运行过程中Leader与Follower之间心跳和同步的容忍上限。生产环境中这两个参数如果设置得不合理会直接影响状态切换的触发时间。比如网络抖动频繁的机房syncLimit按默认值2倍tickTime往往不够容易导致Follower误判Leader失联反复进入LOOKING。1.3 服务端启动时那个“隐藏的LOADING阶段”说四种状态其实严格是指QuorumPeer.ServerState枚举里的四个值。但在真实服务端启动流程里还有一个不在枚举中却客观存在的阶段——数据加载阶段有些资料和源码注释里也会把它描述为LOADING。节点在进入LOOKING之前需要把本地磁盘上的快照文件和增量事务日志加载进内存这个过程可能花费数百毫秒甚至更久取决于数据量大小。这个阶段节点虽然端口已经监听但它不参与选举、不处理请求对外表现和“还没准备好”是一样的。面试时如果能把“在进入LOOKING之前还有一个数据加载阶段所以伪集群中即使所有节点同时启动也不是所有节点立刻都在LOOKING状态”这句话说出来会明显比同龄候选人更有区分度。我见过有人用“四字命令ruok”去判断节点是否在LOOKING结果发现进程明明活着、命令返回imok但集群就是选不出Leader。原因就是只看到了进程存活没有看状态机的实际阶段。这个问题后面监控部分会详细展开。2. LOOKING阶段才是一切的开始Leader选举中的投票与收敛逻辑2.1 选举投票究竟比的是什么epoch、zxid、myid当节点进入LOOKING后会触发Fast Leader ElectionFLE算法。这个算法的核心不是“谁先喊谁是Leader”而是所有LOOKING节点各自投票然后相互交换投票结果按照一套全局统一的比较规则收敛。这套规则值得一个单独的H2因为面试官追问的“隐藏细节”基本都藏在这里。每张投票里其实包含了三个关键信息逻辑时钟epoch、事务IDzxid、服务器IDmyid。比较规则优先级如下先比epochepoch越大说明这个节点经历过的Leader任期越新选它epoch相同时比zxidzxid越大说明节点上数据越完整选它epoch和zxid都相同时比myidmyid越大选它。为什么要把zxid放在myid前面因为zxid里不只是简单的递增数字。高32位是Leader任期编号低32位是该任期内的事务序号。这意味着zxid越大代表不仅事务序号新还代表这个节点完整保留了最近一个Leader任期内所有已提交操作。如果让一个zxid小的节点当选它后面还得向其他节点拉数据麻烦得多。举个实际例子三节点集群A的zxid是0x300000002B的zxid是0x200000005C的zxid是0x300000003。A和C都在epoch为3的任期内有事务B还停留在epoch为2的旧任期。那么A和B比较时A胜出A和C比较时C胜出最终C会被选为Leader因为它在最新任期内的zxid更大。这里有个特别容易被忽略的细节Zookeeper的选举并不是“一轮定生死”。每个节点在自己的逻辑时钟周期内只能投出一张有效票一旦收到更好的票就更新自己的投票并重新广播。这个“不断比较、不断更新、直到某个节点发现自己获得了超过半数的选票”的过程就是FLE的收敛机制。收敛时间取决于网络延迟和投票跳数这也是为什么生产环境要注意不同机房节点之间的RTT过高的网络延迟会成倍拉长LOOKING持续时间。2.2 半数机制与脑裂防线为什么偶数节点反而是浪费关于半数机制网上已经讲烂了但和Server状态结合起来的追问其实很有杀伤力。Zookeeper集群中判定一个Leader是否合法不是看“得到票数最多”而是看获得票数是否超过参与投票节点总数的一半。这个设计直接决定了集群容错能力。假设集群有N个参与投票的节点那么可以宕机存活的上限是floor((N-1)/2)。举例来说3台投票节点最多挂1台剩下2台过半仍能选出Leader4台投票节点过半门槛是3台最多也只能挂1台因为挂2台后只剩2台存活没过半5台投票节点最多挂2台剩下3台过半6台投票节点过半门槛是4台最多挂2台挂3台后只剩3台同样不可用。所以4台和3台的容错能力完全一样都是最多挂1台。但4台集群需要维护更多节点、更多连接、更多同步开销唯一好处是数据副本数量多一份在某些读多场景下有一点点并列读的收益。真正合理的做法是如果一定要凑4台把第4台配成Observer不参与投票这样核心投票集合仍然是3台既保持同样的容错又额外获得一个只做数据同步的读扩展节点。那脑裂是怎么被防住的设想一个5节点集群被网络切成了23两个分区。2节点分区里它们各自投票发现无论如何都凑不到3票于是一直处于LOOKING3节点分区里能凑到3票选出一个合法Leader继续服务。最终集群只存在一个对外可写的Leader。反过来如果允许“超过自身分区内多数”就能成为Leader两个分区会各自选出Leader数据就会彻底分叉。这就是为什么LOOKING状态可以看作脑裂防线的“哨兵”——节点在凑不齐多数时宁可一直挂在LOOKING里不提供服务也不要让自己变成被分区抛弃的伪Leader。2.3 LOOKING期间对外表现端口通、功能不可用把LOOKING和“宕机”划等号不对把LOOKING当成“正常运行之一”也不对。真实表现介于两者之间网络层仍然在监听客户端端口TCP连接可以建立但整个集群尚未产生Leader所以任何写请求都找不到处理者。这个细节在面试和排障中都很关键。客户端连接串里通常会配置多台Zookeeper地址如果客户端正好连到了一个处于LOOKING阶段的节点它的读取请求也会被卡住因为节点要等选举结束后才能确定自己是否具备对外服务资格。很多人会觉得“Zookeeper读请求是本地快照读应该不受选举影响”这个认知在集群正常时成立但LOOKING阶段是例外节点此时连自己的角色都没定自然不会把本地数据直接提供给客户端。我在生产环境里遇到过一类典型故障某台机器因为磁盘IO卡顿被误判失联触发Leader切换整个集群进入恢复期。期间所有客户端报连接断开、请求超时虽然另外两台节点还在但它们在完成新Leader选举和数据同步之前客户端体验就是“短暂不可用”。这不是Zookeeper设计缺陷而是CAP里一致性和可用性的必然取舍。恢复耗时通常在几百毫秒到几秒之间如果超过这个量级还停在LOOKING那就要按故障处理了。3. OBSERVING是被低估的状态不投票的节点为什么能撑起读扩展3.1 设计根源所有Follower都投票会拖慢写路径Observer可能是四种状态里最被低估的。很多初学者以为它就是个“只读的Follower”其实它和Follower的差别不只是“不能当Leader”这么简单而是从设计根源上就不参与写投票。Zookeeper的写请求走的是ZAB协议Leader收到写请求后要广播给所有Follower等大多数投票节点确认ACK后才向客户端返回成功。注意这里的“大多数”只计算参与投票的节点。如果集群里只有3台Follower写确认只需要2台反馈如果扩到10台Follower写确认则需要6台反馈。哪怕只多了一个参与投票的节点整个写路径的广播成本和等待时间都会上升而且Leader还要多做一份事务日志同步。Observer的诞生就是为了解决这个矛盾既想扩充读能力又不愿意让写路径变重。Observer同样从Leader同步全部数据但它不对写请求做ACK确认也不参与选举投票。对Leader来说Observer就像是一个“只听广播、不举手表决”的旁观者。因此就算集群里挂了几个Observer只要参与投票节点还满足过半条件写路径完全不受影响。有人可能问那是不是可以把Observer加得越多越好也不行。Observer同步数据仍然要占Leader的网络带宽和内存Leader向外推送数据要遍历所有节点Observer数量过大一样会拖垮Leader的网络IO。所以Observer适合“几十个以内”的读扩展而不是无限横向扩容。3.2 读写分流的真实表现与一致性边界生产环境中Observer最常见的用途就是“读流量扩展”和“跨机房就近读”。客户端连接Observer写请求会被Observer原样转发给Leader读请求则直接从Observer本地返回。因为Observer持续从Leader同步数据所以大部分读请求能在本地命中。但要特别强调一个一致性边界Observer的本地读是最终一致的。Zookeeper的同步是异步的Observer落后Leader一小段时间是完全正常的。如果你在Observer上发起一个紧跟写操作的读请求很可能读到旧值。这一点在很多面试场景里都容易被追问。如果想在Observer上拿到相对新一点的数据可以调用Zookeeper客户端的sync()接口它会强制客户端与Leader做一次同步让后续读请求能读到同步点之后的数据。不过sync()并不是“强一致读”的银弹它只是把读取位置推进到接近最新实际延迟还是受网络影响。所以严谨地说Zookeeper的读请求从来就不是严格线性一致的只是顺序一致性被保证在写路径上。如果业务对读一致性要求很高方案应该是全部走Leader读代价是牺牲扩展性或者用Zookeeper之外的一致性缓存层。这是另一个话题但面试时能把“Observer适合最终一致读模型不适合强一致读模型”这个边界说清楚就已经比大多数人强了。3.3 Observer的配置方式与生产坑Observer的配置非常简单核心是在配置文件里给目标节点声明角色。比如一个节点在配置文件里的格式是server.1zk1:2888:3888 server.2zk2:2888:3888 server.3zk3:2888:3888 server.4zk4:2888:3888:observer关键点在第4台后面的:observer后缀。没有这个后缀它就是普通的参与投票节点加上这个后缀这台机器才会以Observer身份加入集群。注意这里需要说明的是Observer为什么仍然需要2888和3888两个端口因为2888用于和Leader之间同步数据3888用于参与集群内部通信虽然不投票但也要接收选举广播结果。生产环境中配置Observer常见两个坑第一个坑把Observer和参与投票节点混淆导致容错预期错误。比如一个5台集群里配了2台Observer实际投票节点只有3台容错上限仍然是挂1台。如果挂了2台投票节点即便另外3台Observer都活着集群也选不出 Leader整体不可用。第二个坑忘记Observer也参与四字命令监控范围。stat、mntr输出里Observer的Mode或zk_server_state显示为observer如果监控脚本只认 leader/follower 两种状态就会把Observer误判为异常。另外一旦节点配置为Observer它就永远不会主动成为Leader。哪怕其他所有节点都宕机了只剩一个Observer和一个Follower这个Observer也只是等待Follower那边选出状态自己不会去抢Leader。这个特性在脑裂和降级场景中一定要想清楚别把“Observer存活”当作“选举还能正常进行”的依据。4. 生产环境怎么盯状态四字命令、日志与监控指标的实际读法4.1 stat/srvr/mntr的输出怎么读前面讲的都是原理落地到运维环节最重要的就是“能看见状态”。Zookeeper提供了一组四字命令最常用的三个是stat、srvr、mntr。每台Zookeeper节点上都可以这样执行echo mntr | nc 127.0.0.1 2181输出大致长这样不同版本字段略有差异zk_version 3.9.1 zk_server_state leader zk_znode_count 1234 zk_watch_count 8 zk_ephemerals_count 5 zk_approximate_data_size 54321关键字段就是zk_server_state只可能出现leader、follower、observer或者启动初期的looking几种值。stat和srvr的输出里也有一个Mode字段含义相同但srvr更精简适合快速确认当前节点角色。这里有一个很常见的坑较新版本的Zookeeper默认没有开放全部四字命令需要在配置里加白名单否则执行echo mntr | nc会直接返回空或xxx is not executed because it is not in the whitelist。要放行什么命令可以自己按需配置4lw.commands.whiteliststat,srvr,mntr,ruok,conf要注意ruok这个命令非常具有迷惑性。它只判断“当前进程是否活着”能正常返回imok只代表JVM没挂不代表节点已经完成选举或者数据已同步。生产环境里用它做健康检查不如看mntr里的zk_server_state更可靠。读这些输出时我的习惯是做一个汇总表命令关键输出用途statMode、Zxid、Connections快速排查节点角色和连接数srvrMode、Sent/Received、Pending Syncs判断节点同步压力mntrzk_server_state、磁盘使用率等指标批量监控和采集ruokimok只适合进程存活探测不适合状态机判断confserverId、tickTime、dataDir核对配置是否生效4.2 日志里的状态变化线索四字命令只能看到“当下状态”想看状态变化的完整过程还得结合日志。Zookeeper服务端的日志里状态切换是有明确标志的。节点启动后会打印加载快照和事务日志的信息加载完成进入选举阶段时会有类似LOOKING的日志关键字。之后胜选节点会打印类似LEADING - LEADER ELECTION TOOK - 17ms的信息表示它成为Leader并计算了选举耗时Follower节点则会出现FOLLOWING相关日志Observer节点会体现为以Observer身份连接Leader。对于排障来说日志最大的价值在“状态来回跳”的场景。如果日志里频繁出现FOLLOWING然后很快又出现LOOKING说明节点反复与Leader失联又恢复这通常和网络抖动、负载过高、或者syncLimit设置过小有关。我曾经在排查一个集群Leader频繁切换的故障时就是靠搜索日志里所有LEADER ELECTION TOOK的时间间隔发现每10分钟左右就触发一次重新选举再配合系统监控定位到是某台机器上的GC暂停导致心跳超时。建议每个Zookeeper集群都接日志采集至少把*.log里包含LOOKING、LEADING、FOLLOWING、OBSERVING四个关键字的行上报到监控平台一旦出现同一节点在短时间内多次LOOKING立刻触发告警。4.3 结合Hadoop HA看真实场景讲完命令和日志用一个真实场景把它们串起来Hadoop的高可用HA架构这是Zookeeper在实际业务中最大的应用之一。HDFS的NameNode高可用通过ZKFCZooKeeper Failover Controller实现。每个NameNode会有一个ZKFC进程它会在Zookeeper集群里创建临时节点。正常情况下只有Active NameNode对应的ZKFC持有这个临时节点Standby NameNode的ZKFC在另一边持续监控它。一旦Active NameNode异常Zookeeper中的临时节点消失Standby NameNode立即接管并尝试成为新的Active。这套机制里Zookeeper节点自身的Server状态决定了它能对外提供多可靠的服务。如果Zookeeper集群里某台节点进入了LOOKING对外表现就是临时节点可能在短时间内不可访问ZKFC会重试连接。如果整个Zookeeper集群因为投票节点不足而陷入长时间LOOKINGNameNode的自动故障转移就会失败HDFS只能进入不可用状态。所以监控Zookeeper状态时“当前没有Leader的秒数”是最核心的健康指标。可以写一个简单的状态轮询脚本每5秒对所有节点执行一次mntr统计zk_server_state等于looking的节点数量只要这个数量不为0就说明集群正在经历选举或已经异常。如果持续超过设定阈值还没有恢复就触发告警。这个脚本逻辑简单但比单纯检查进程存活有效得多也是我在实际运维里非常推荐的做法。5. 从四种状态延伸到面试必杀的追问链会话、请求与故障判读5.1 状态切换对客户端会话和请求的影响面试官问完四种状态是什么紧接着一定会问“状态切换时客户端会怎样”这个问题的答案藏着理解Zookeeper一致性和容错机制的关键。先看写请求。客户端发起一个写操作如果连接的是Leader服务端直接走ZAB广播如果连接的是Follower或Observer请求会被转发到Leader。当集群发生Leader切换时处于FOLLOWING状态的旧节点发现自己联系不上Leader后会进入LOOKING。它不会继续接收新请求已经接收但还没提交的事务会暂时挂起。客户端这边看到的表现通常是ConnectionLoss异常需要在客户端代码里做重试。但注意Zookeeper的写请求重试不保证“恰好一次”也就是说同一个写操作可能因为超时被客户端重试服务端可能已经执行了导致重复提交。这需要业务层自行保证幂等。再看会话。Zookeeper客户端和服务端之间是通过Session会话维系的Session信息主要保存在Leader和Follower上。Leader切换后新Leader会从多数节点中选择数据最完整的那个作为基准未提交的事务会被丢弃或回滚但已提交的事务必须保留。客户端如果配置了多个Zookeeper地址在重连时会被引导到新Leader上只要Session仍然有效就能继续使用。这里有个细节Session过期时间一般由minSessionTimeout和maxSessionTimeout框定生产环境如果设置过短切换期间稍有停顿就会导致会话过期客户端大量报错。所以面试时这个问题可以这样分三层回答连接层会出现ConnectionLoss数据层旧Leader上未提交的事务会被ZAB协议丢弃会话层只要客户端重连及时会话可以保留但重试需要考虑幂等。能说到这个程度基本就覆盖了面试官想听的核心。5.2 故障判读速查长时间LOOKING说明了什么最后聊一个运维中特别实用的场景什么时候可以判定Zookeeper集群已经“病入膏肓”我给的指标是大多数投票节点长时间处于LOOKING并且无法选出一个Leader。导致这种局面的常见原因有三类。第一类是投票节点数量不足比如5台投票节点挂了3台剩下2台永远凑不够3票第二类是网络分区所有节点各自为战任何一方的存活节点都凑不够多数第三类是磁盘或负载异常导致节点反复崩溃始终无法完成数据加载和选举。排查链路我建议按这个顺序来对每台节点执行mntr收集所有节点的zk_server_state画出当前状态分布搜索所有节点日志中的LOOKING和LEADER ELECTION TOOK观察选举是否反复发生检查网络层看投票节点之间的2888端口是否是通的延迟是否稳定查看磁盘空间、IO负载和JVM监控排除节点自身“假死”导致的心跳失联。这里我特别想分享一个真实教训一套5台Zookeeper集群里配了2台Observer某次机房割接后有2台投票节点和1台Observer之间的网络出现了严重丢包。我一开始只盯着observer节点的状态看发现它是looking还是正常结果迟迟找不到根因。后来才意识到我一直忽略了一个基础事实——Observer不参与投票它的存活完全不能弥补投票节点不足的问题。那次割接里恰好宕掉2台投票节点剩下的3台中又有2台处于网络隔离区凑不够3票整个集群陷入不可用。事后复盘监控上其实早就报警了但那个“无Leader持续秒数”的指标当时没有配置导致故障被延迟发现。现在我的标准配置是任何Zookeeper集群都必须监控“无Leader秒数”和“投票节点存活数”这两个指标前者反映选举是否完成后者反映容错余量。Observer的监控单独做只关注同步延迟和读负载不要混进投票健康度里。这四种状态背后的状态机、选举协议和一致性模型单独拆开其实都不难但它们组合起来就是Zookeeper面试的完整考察面。我面过不少人也带过几个新同学最后发现能把OBSERVING的投票边界和启动时那个隐藏加载阶段主动讲清楚的人才是真正摸过源码、跑过集群的人。建议你自己搭一个三节点伪集群手动kill掉Leader看一遍状态切换的完整日志再结合四字命令观察每个阶段的变化这一遍下来比背十遍知识点都管用。
返回列表