解Bug之路-ZooKeeper集群拒绝服务

前言

ZooKeeper作为dubbo的注册中心,可谓是重中之重,线上ZK的任何风吹草动都会牵动心弦。最近笔者就碰到线上ZK Leader宕机后,选主无法成功导致ZK集群拒绝服务的现象,于是把这个case写出来分享给大家(基于ZooKeeper 3.4.5)。

Bug现场

一天早上,突然接到电话,说是ZooKeeper物理机宕机了,而剩余几台机器状态都是

sh zkServer.sh status
it is probably not running

笔者看了下监控,物理机宕机的正好是ZK的leader。3节点的ZK,leader宕了后,其余两台一直未能成为leader,把宕机的那台紧急拉起来之后,依旧无法选主,

导致ZK集群整体拒绝服务!

业务影响

Dubbo如果连接不上ZK,其调用元信息会一直缓存着,所以并不会对请求调用造成实际影响。麻烦的是,如果在ZK拒绝服务期间,应用无法重启或者发布,一旦遇到紧急事件而重启(发布)不能,就会造成比较重大的影响。

好在我们为了高可用,做了对等机房建设,所以非常淡定的将流量切到B机房,



双机房建设就是好啊,一键切换!

切换过后就可以有充裕的时间来恢复A机房的集群了。在紧张恢复的同时,笔者也开始了分析工作。

日志表现

首先,查看日志,期间有大量的client连接报错,自然是直接过滤掉,以免干扰。

cat zookeeper.out | grep -v 'client xxx' | > /tmp/1.txt

首先看到的是下面这样的日志:

ZK-A机器日志

Zk-A机器:
2021-06-16 03:32:35 ... New election. My id=3
2021-06-16 03:32:46 ... QuoeumPeer] LEADING // 注意,这里选主成功
2021-06-16 03:32:46 ... QuoeumPeer] LEADING - LEADER ELECTION TOOK - 7878'
2021-06-16 03:32:48 ... QuoeumPeer] Reading snapshot /xxx/snapshot.xxx
2021-06-16 03:32:54 ... QuoeumPeer] Snahotting xxx to /xxx/snapshot.xxx
2021-06-16 03:33:08 ... Follower sid ZK-B.IP
2021-06-16 03:33:08 ... Unexpected exception causing shutdown while sock still open
java.io.EOFException
at java.io.DataInputStream.readInt
......
at quorum.LearnerHandler.run
2021-06-16 03:33:08 ******* GOODBYE ZK-B.IP *******
2021-06-16 03:33:27 Shutting down

这段日志看上去像选主成功了,但是和其它机器的通信出问题了,导致Shutdown然后重新选举。

ZK-B机器日志

2021-06-16 03:32:48 New election. My id=2
2021-06-16 03:32:48 QuoeumPeer] FOLLOWING
2021-06-16 03:32:48 QuoeumPeer] FOLLOWING - LEADER ELECTION TOOK - 222
2021-06-16 03:33:08.833 QuoeumPeer] Exception when following the leader
java.net.SocketTimeoutException: Read time out
at java.net.SocketInputStream.socketRead0
......
at org.apache.zookeeper.server.quorum.Follower.followLeader
2021-06-16 03:33:08.380 Shutting down

这段日志也表明选主成功了,而且自己是Following状态,只不过Leader迟迟不返回,导致超时进而Shutdown

时序图

笔者将上面的日志画成时序图,以便分析:



从ZK-B的日志可以看出,其在成为follower之后,一直等待leader,知道Read time out。

从ZK-A的日志可以看出,其在成为LEADING后,在33:08,803才收到Follower也就是ZK-B发出的包。而这时,ZK-B已经在33:08,301的时候Read timed out了。

首先分析follower(ZK-B)的情况

我们知道其在03:32:48成为follower,然后在03:33:08出错Read time out,其间正好是20s。于是笔者先从Zookeeper源码中找下其设置Read time out是多长时间。

Learner
protected void connectToLeader(InetSocketAddress addr) {
......
sock = new Socket()
// self.tockTime 2000 self.initLimit 10
sock.setSoTimeout(self.tickTime * self.initLimit);
......
}

其Read time out是按照zoo.cfg中的配置项而设置:

tickTime=2000 self.tickTime
initLimit=10 self.initLimit
syncLimit=5

很明显的,ZK-B在成为follower后,由于某种原因leader在20s后才响应。那么接下来对leader进行分析。

对leader(ZK-A)进行分析

首先我们先看下Leader的初始化逻辑:

quorumPeer
|->打印 LEADING
|->makeLeader
|-> new ServerSocket listen and bind
|->leader.lead()
|->打印 LEADER ELECTION TOOK
|->loadData
|->loadDataBase
|->resore 打印Reading snapshot
|->takeSnapshot
|->save 打印Snapshotting
|->cnxAcceptor 处理请求Accept

可以看到,在我们的ZK启动监听端口到正式处理请求之间,还有Reading Snapshot和Snapshotting(写)动作。从日志可以看出一个花了6s多,一个花了14s多。然后就有20s的处理空档期。如下图所示:



由于在socket listen 20s之后才开始处理数据,所以ZK-B建立成功的连接实际还放在tcp的内核全连接队列(backlog)里面,由于在内核看来三次握手是成功的,所以能够正常接收ZK-B发送的follower ZK-B数据。在20s,ZK-A真正处理后,从buffer里面拿出来20s前ZK-B发送的数据,处理完回包的时候,发现ZK-B连接已经断开。

同样的,另一台follower(这时候我们已经把宕机的拉起来了,所以是3台)也是由于此原因gg,而leader迟迟收不到其它机器的响应,认为自己的leader没有达到1/2的票数,而Shutdown重新选举。

Snapshot耗时

那么是什么导致Snapshotting读写这么耗时呢?笔者查看了下Snapshot文件大小,有将近一个G左右。

调大initLimit

针对这种情况,其实我们只要调大initLimit,应该就可以越过这道坎。

zoo.cfg
tickTime=2000 // 这个不要动,因为和ZK心跳机制有关
initLimit=100 // 直接调成100,200s!

这么巧就20s么?

难道就这么巧,每次选举流程都刚好卡在20s不过?反复选举了好多次,应该有一次要<20s成功把,不然运气也太差了。如果是每次需要处理Snapshot 30s也就算了,但这个20s太接近极限值了,是否还有其它因素导致选主不成功?

第二种情况

于是笔者翻了下日志,还真有!这次leader这边处理Snapshot快了,但是follower又拉跨了!日志如下:

leader(ZK-A)第二种情况

2021-06-16 03:38:03 New election. My id= 3
2021-06-16 03:38:22 QuorumPeer] LEADING
2021-06-16 03:38:22 QuorumPeer] LEADING - LEADER ELECTION TOOK 25703
2021-06-16 03:38:22 QuorumPeer] Reading snapshot
2021-06-16 03:38:29 QuorumPeer] Snapshotting
2021-06-16 03:38:42 LearnerHandler] Follower sid 1
2021-06-16 03:38:42 LearnerHandler] Follower sid 3
2021-06-16 03:38:42 LearnerHandler] Sending DIFF
2021-06-16 03:38:42 LearnerHandler] Sending DIFF
2021-06-16 03:38:54 LearnerHandler] Have quorum of supporters
2021-06-16 03:38:55 client attempting to establsh new session 到这开始接收client请求了
......
2021-06-16 03:38:58 Shutdown callsed
java.lang.Exception: shutdown Leader! reason: Only 1 followers,need 1
at org.apache.zookeeper.server.quorum.Leader.shutdown

从日志中我们可以看到选举是成功了的,毕竟处理Snapshot只处理了13s(可能是pagecache的原因处理变快)。其它两个follower顺利连接,同时给他们发送DIFF包,但是情况没好多久,又爆了一个follower不够的报错,这里的报错信息比较迷惑。

我们看下代码:

Leader.lead
void lead() {
while(true){
Thread.sleep(self.tickTime/2);
......
syncedSet.add(self.getId())
for(LearnerHandler f:getLearners()){
if(f.synced() && f.getLearnerType()==LearnerType.PARTICIPANT){
syncedSet.add(f.getSid());
}
f.ping();
}
// syncedSet只有1个也就是自身,不符合>1/2的条件,报错并跳出
if (!tickSkip && !self.getQuorumVerifier().containsQuorum(syncedSet)) {
shutdown("Only" + syncedSet.size() + " followers, need" + (self.getVotingView().size()/2));
return;
}
}
}

报错的实质就是和leader同步的syncedSet小于固定的1/2集群,所以shutdown了。同时在代码里面我们又可以看到syncedSet的判定是通过learnerHander.synced()来决定。我们继续看下代码:

LearnerHandler
public boolean synced(){
// 这边isAlive是线程的isAlive
return isAlive() && tickOfLastAck >= leader.self.tick - leader.self.syncLimit;
}

很明显的,follower和leader的同步时间超过了leader.self.syncLimit也就是5 * 2 = 10s

zoo.cfg
tickTime = 2000
syncLimit = 5

那么我们的tick是怎么更新的呢,答案是在follower响应UPTODATE包,也就是已经和leader同步后,follower每个包过来就更新一次,在此之前并不更新。



进一步推理,也就是我们的follower处理leader的包超过了10s,导致tick未及时更新,进而syncedSet小于数量,导致leader shutdown。

follower(ZK-B)第二种情况

带着这个结论,笔者去翻了follower(ZK-B)的日志(注:ZK-C也是如此)

2021-06-16 03:38:24 New election. My id = 3
2021-06-16 03:38:24 FOLLOWING
2021-06-16 03:38:24 FOLLOWING - LEADER ELECTION TOOK - 8004
2021-06-16 03:38:42 Getting a diff from the leader
2021-06-16 03:38:42 Snapshotting
2021-06-16 03:38:57 Snapshotting
2021-06-16 03:39:12 Got zxid xxx
2021-06-16 03:39:12 Exception when following the leader
java.net.SocketException: Broken pipe

又是Snapshot,这次我们可以看到每次Snapshot会花15s左右,远超了syncLimit。

从源码中我们可以得知,每次Snapshot之后都会立马writePacket(即响应),但是第一次回包有由于不是处理的UPTODATE包,所以并不会更新Leader端对应的tick:

learner:
proteced void syncWithLeader(...){
outerloop:
while(self.isRunning()){
readPacket(qp);
switch(qp.getType()){
case Leader.UPTODATE
if(!snapshotTaken){
zk.takeSnapshot();
......
}
break outerloop;
}
case Leader.NEWLEADER:
zk.takeSnapshot();
......
writePacket(......) // leader收到后会更新tick
break;
}
......
writePacket(ack,True); // leader收到后会更新tick
}

注意,ZK-B的日志里面表明会两次Snapshotting。至于为什么两次,应该是一个微妙的Bug,(在3.4.5的官方注释里面做了fix,但看日志依旧打了两次),笔者并没有深究。好了,整个时序图就如下所示:



好了,第二种情况也gg了。这一次时间就不是刚刚好出在边缘了,得将近30s才能Okay, 而synedSet只有10s(2*5)。ZK集群就在这两种情况中反复选举,直到人工介入。

调大syncLimit

针对这种情况,其实我们只要调大syncLimit,应该就可以越过这道坎。

zoo.cfg
tickTime=2000 // 这个不要动,因为和ZK心跳机制有关
syncLimit=50 // 直接调成50,100s!

线下复现

当然了,有了分析还是不够的。我们还需要通过测试去复现并验证我们的结论。我们在线下构造了一个1024G Snapshot的ZookKeeper进行测试,在initLimit=10以及syncLimit=5的情况下确实和线上出现一模一样的那两种现象。在笔者将参数调整后:

zoo.cfg
tickTime=2000
initLimit=100 // 200s
syncLimit=50 // 100s

Zookeeper集群终于正常了。

线下用新版本3.4.13尝试复现

我们在线下还用比较新的版本3.4.13尝试复现,发现Zookeeper在不调整参数的情况下,很快的就选主成功并正常提供服务了。笔者翻了翻源码,发现其直接在Leader.lead()阶段和SyncWithLeader阶段(如果是用Diff的话)将takeSnapshot去掉了。这也就避免了处理snapshot时间过长导致无法提供服务的现象。

Zookeeper 3.4.13

ZookeeperServer.java
public void loadData(){
...
// takeSnapshot() 删掉了最后一行的takeSnapshot
} learner.java
protected void syncWithLeader(...){
boolean snapshotNeeded=true
if(qp.getType() == Leader.DIFF){
......
snapshotNeeded = false
}
......
if(snapshotNeeded){
zk.takeSnapshot();
}
......
}

还是升级到高版本靠谱呀,这个版本的代码顺带把那个迷惑性的日志也改了!

为何Dubbo-ZK有那么多的数据

最后的问题就是一个dubbo相关的ZK为什么有那么多数据了!笔者利用ZK使用的

org.apache.zookeeper.server.SnapshotFormatter

工具dump出来并用shell(awk|unique)聚合了一把,发现dubbo的数据只占了其中的1/4。

有1/2是Solar的Zookeeper(已经迁移掉,遗留在上面的)。还有1/4是由于某个系统的分布式锁Bug不停的写入进去并且不删除的(已让他们修改)。所以将dubbo-zk和其它ZK数据分离是多么的重要!随便滥用就有可能导致重大事件!

总结

Zookeeper作为重要的元数据管理系统,其无法提供服务有可能会带来不可估量的影响。感谢双机房建设让我们有充足的时间和轻松的心态处理此问题。另外,虽然ZK选举虽然复杂,但是只要沉下心来慢慢分析,总归能够发现蛛丝马迹,进而找到突破口!

解Bug之路-ZooKeeper集群拒绝服务的更多相关文章

  1. java架构之路-(分布式zookeeper)zookeeper集群配置和选举机制详解

    上次博客我们说了一下zookeeper的配置文件,以及命令的使用https://www.cnblogs.com/cxiaocai/p/11597465.html.我们这次来说一下我们的zookeepe ...

  2. HUE配置文件hue.ini 的zookeeper模块详解(图文详解)(分HA集群)

    不多说,直接上干货! 我的集群机器情况是 bigdatamaster(192.168.80.10).bigdataslave1(192.168.80.11)和bigdataslave2(192.168 ...

  3. 【图文详解】Zookeeper集群搭建(CentOs6.3)

    Zookeeper简介: Zookeeper是一个分布式协调服务,就是为用户的分布式应用程序提供协调服务的. A.zookeeper是为别的分布式程序服务的 B.Zookeeper本身就是一个分布式程 ...

  4. zookeeper集群安装及使用详解

    1. Zookeeper简介 ZooKeeper是一个开源的分布式框架,提供了协调分布式应用的基本服务.它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronizatio ...

  5. Zookeeper集群安装详解

    Zookeeper的角色   Zookeeper集群搭建 要求:服务器集群规模不小于3个节点,各服务器之间系统时间要保持一致! 安装步骤 1.在h1节点解压,目录改名. tar –zxvf zooke ...

  6. 解Bug之路-记一次中间件导致的慢SQL排查过程

    解Bug之路-记一次中间件导致的慢SQL排查过程 前言 最近发现线上出现一个奇葩的问题,这问题让笔者定位了好长时间,期间排查问题的过程还是挺有意思的,正好博客也好久不更新了,就以此为素材写出了本篇文章 ...

  7. 解Bug之路-记一次存储故障的排查过程

    解Bug之路-记一次存储故障的排查过程 高可用真是一丝细节都不得马虎.平时跑的好好的系统,在相应硬件出现故障时就会引发出潜在的Bug.偏偏这些故障在应用层的表现稀奇古怪,很难让人联想到是硬件出了问题, ...

  8. 解Bug之路-串包Bug

    解Bug之路-串包Bug 笔者很热衷于解决Bug,同时比较擅长(网络/协议)部分,所以经常被唤去解决一些网络IO方面的Bug.现在就挑一个案例出来,写出分析思路,以飨读者,希望读者在以后的工作中能够少 ...

  9. 解Bug之路-NAT引发的性能瓶颈

    解Bug之路-NAT引发的性能瓶颈 笔者最近解决了一个非常曲折的问题,从抓包开始一路排查到不同内核版本间的细微差异,最后才完美解释了所有的现象.在这里将整个过程写成博文记录下来,希望能够对读者有所帮助 ...

随机推荐

  1. NABCD-name not found

    项目 内容 课程 2020春季计算机学院软件工程(罗杰 任健) 作业要求 团队项目选择 项目名称 FOTT 项目内容 在OCR-Form-Tools开源项目的基础上,扩展功能,支持演示更多的API,例 ...

  2. CRM应用中可能发生的问题

    CRM系统是公认的提升企业竞争力的强大工具.它既是以客户为中心的思想,又是一种企业管理方案.当然,它还是一种管理软件.在国外,CRM使企业运营得风生水起,但在我国的企业应用中,还是有着很高的失败率和使 ...

  3. SQL中那么多函数,Java8为什么还要提供重复的Stream方法,多此一举?

    有个同学提出一个这样的疑问; 在业务系统中,数据一般都从sql中查询,类似使用where,order by,limit,聚合函数等,为什么还要用java8的Stream方法? 对这个问题,大家有什么见 ...

  4. 使用基于centos7 dbus问题总结

    Authorization not available. Check if polkit Authorization not available. Check if polkit service is ...

  5. [刷题] 112 Path Sum

    要求 给出一个二叉树及数字sum,判断是否存在一条从根到叶子的路径,路径上的所有节点和为sum 实现 转化为寻找左右子树上和为 sum-root 的路径,到达叶子节点时递归终止 注意只有一个孩子时,根 ...

  6. node.js module初步理解-(转载)

    在开发一个复杂的应用程序的时候,我们需要把各个功能拆分.封装到不同的文件,在需要的时候引用该文件.没人会写一个几万行代码的文件,这样在可读性.复用性和维护性上都很差,几乎所有的编程语言都有自己的模块组 ...

  7. web技术开发桌面应用

    三款前端工程师桌面应用开发神器! http://mini.eastday.com/a/160630023107669-2.html 如果我告诉你,以后你们可以用前端语言(html5+css3+js)开 ...

  8. 友盟+U-APM应用性能报告:Android崩溃率达0.32%,OPPO 、华为、VIVO 崩溃表现良好

    ​随着信息技术高速发展,移动互联几乎已成为了一种生活方式的代名词,在全民上网的数字热潮中,如何能最大程度保障产品服务的稳定性,提供良好的用户体验,是当前企业都需要思考和亟待解决的问题.App的应用性能 ...

  9. unity texture 占用内存大小对比

    打包多种类型的项目,空项目和10张放在Resources文件夹中的图为比较案例.以下是比较数据. IPHONE: 1.空项目----空间占用量42.3MB----IPA大小10MB 2.10张1200 ...

  10. Docker学习(9) Docker守护进程的配置和操作