zookeeper分布式锁的坑让我踩了,现场复盘并深入分析
yuyutoo 2024-10-16 15:45 4 浏览 0 评论
在分布式系统里使用分布式锁来保证 Schedule Job 的线程安全是很常见的问题,为了保证同一时刻有且只有一个服务在运行该 Job。
我有一个 通过 spring Schedule 来调度的 Job,执行频率是 1 小时一次,使用 zookeeper 做的分布式锁,服务部署了三台,但今天遇到了一个诡异的问题,这个 Job 从昨天晚上 9点 到今天上午11点 一次都没有执行。看日志发现两台服务因为没有拿到锁而跳过,而拿到锁的机器一直处于假死状态。
01 技术背景
有三台 SpringMvc 的web服务,机器分别是 meta01、meta02、meta03,借助 apache curator 实现的 zookeeper 分布式锁,zookeeper 是三节点集群也部署在meta01、meta02、meta03。
服务启动时通过 afterPropertiesSet 向 zookeeper 注册临时顺序节点,头节点则拿到锁。服务 stop 时通过 destroy 放弃锁并停止与 zookeeper 的心跳和监听。
即:在服务启动时就决定了谁持有这把 zookeeper 锁,并一直持有,除非断开心跳。
02. 问题表象
一小时执行一次的 Job 从昨天晚上 9点 到今天上午11点 一次都没有执行。
从日志上看 meta01、meta02 当天正常去调度但不是 zookeeper 头节点,meta03 调度日志停留在昨天,然后这台机器上的服务进程确实还在,查到这里我的内心不由得很兴奋,因为前段时间出现的妖怪又出现了。
到此得出第一步结论:leader 节点假死,但是在 zookeeper 上的临时顺序节点并没有删除而造成锁未释放。
为了快速解决问题,我手动把 meta03 的锁节点删除了。
03. 问题排查
节点假死的原因是什么,是不是因为 FGC?我立刻查看了 heap 占用和 GC 情况,但发现这些都很正常。并且内存、CPU使用都是比较低的。
日志没有异常、内存、CPU负载低,GC正常,基本可以断定问题出在外部。
于是找到平台的人一起来排查,果然在监控平台上看到 meta03 这台机器这台是异常的。可以看到服务指标出现了断层,这台机器上的所有服务都死了。
查看各组件日志,发现了异常日志,显示系统时间被修改了,这时我才意识到 QA 为了测试是将系统时间改到前一天,当把系统时间改正确之后系统恢复正常。
但是为什么修改了系统时间会造成所有服务假死,zookeeper 的心跳不在了为什么节点没有删除?这是 zookeeper 或者 apache curator 的 bug 吗?一台服务不可用,其他服务拿不到锁就执行不了任务,这是我们需要探究的问题。
读时钟失败,并且 session timeout
此时 meta03 还是 leader
改回系统时间之后恢复正常,有了最新日志
得出第二步结论:假死原因是修改系统时间,造成集群时间不同步。
此时作出进一步猜想:按 zk 心跳机制,meta03 无法向 server 集群发出心跳,此时该 zk 锁的临时顺序节点应该被删除。但不巧的是,假死的 meta03 也是集群的 leader,写操作又由 leader 负责,所以节点无法删除。此时 meta01、meta02 也无法跟leader 通信,应该发起了选主流程,但是都是选自己,所以一直没有产生新的 leader,就这样一直尴尬下去,一直假死下去,等待 meta03 恢复正常。
04. 对猜想的分析
▍探究 zookeeper 心跳机制
前段时间看过 zookeeper 的源码,不得不说东西太多了,只看了一点皮毛。在前面的文章《 给 gRPC 写服务发现》介绍过 zookeeper的基本原理,感兴趣的可以看一下,这次重点看了心跳机制。
谈心跳机制之前先介绍一下 zookeeper 的启动流程。在其源码里有 mainClasses 这么一个文件,里面写着
也就是说 ZooKeeperMain 是其 Client 的入口,QuorumPeerMain 是 server 的启动类。ps 可以看到进程
Client 的功能包括向 server 发心跳、创建节点等,Server 端比如数据存储、服务端数据同步、选主。zoo.cfg 是服务启动配置项
在 ZooKeeperMain 类的 connectToZK 方法创建 ,ZooKeeperAdmin 类实例,在其构造方法里创建里 ClientCnxn 的实例,并调用 clientCnxn.start() 方法。
ClientCnxn 类创建了两个线程,这两个线程就是负责从 Client 向 Server 发送心跳包的。
其中,SendThread 负责将ZooKeeper的信息封装成一个Packet,发送给 Server ,并维持同 Server 的心跳,EventThread负责解析通过 SendThread 得到的Response,之后发送给Watcher::processEvent进行详细的事件处理。
1. SendThread
SendThread 是心跳线程,run 方法核心逻辑如下
- 建立和 Server 之间的 socket 链接
- 判断链接是否超时
- 定时发送心跳任务
- 将ZooKeeper指令发送给Server
▍心跳频率
代码注释翻译:1000(1秒)是为了防止竞争条件丢失而发送第二个ping,也请确保在readTimeout很小时不要发送太多ping。
以上面的 zoo.cfg 为例,sessionTimeout = 4000,readTimeout = 2666。getIdleSend() 是距离上次心跳发送的时间(now - lastSend),可以理解为心跳间隔毫秒数,得出频率 大约每 1333 毫秒一次。
▍心跳逻辑 sendPing
往 outgoingQueue 放入心跳包 Packet
▍Client 与 Server 长连接
clientCnxnSocket 是 Client 和 Server 建立的 Socket 长连接,而这个实例是上面的 getClientCnxnSocket() 创建的。源码如下,默认选择 NIO 方式(ClientCnxnSocketNIO 类)建立 Socket 连接。
在 ClientCnxnSocketNIO connect 方法中,Client 与 Server 建立类 Socket 连接。
▍session 超时
在SendThread::run中,可以看到针对链接是否建立分别有readTimeout和connetTimeout 两种超时时间,一旦发现链接超时,则抛出异常,终止 SendThread。上面提到来 readTimeout = 2666。
在没有超时的情况下,如果判断距离上次心跳时间超过了1/2个超时时间,会再次发送心跳数据,避免访问超时。
▍doTransport
通过 ClientCnxnSocketNIO 向 Server 发送指令
sendThread.primeConnection() 核心逻辑如下,可以看到在这里并没有真正向 Server 发送,而是 先放入 Queue 异步发送的。
SendThread run 方法消费 outgoinQueue 发送的心跳
2. EventThread
EventThread 线程逻辑就简单的多,就是处理 finishPacket 放到 waitingEvents 的事件。
在EventThread中通过processEvent对队列中的事件进行消费,并分发给不同的Watcher。
EventThread 线程并非本次问题的关键点,这里不再详细分析介绍。
结论:zookeeper 的心跳机制是从 ClientCnxn 的 SendThread 线程发出去的,既然系统假死,心跳肯定是没有了。而 meta03 的 EventThread 也因假死处理不了任何事件,所以就删除不了节点。
3. 细数 zookeeper 分布式锁缺陷:
- 加锁性能低
- 锁释放惊群效应
- 多主或无主。leader 出现 FGC 或者其他假死情况时,心跳暂停触发选主,若选举出新leader,但老leader依然认为自己是leader就出现多主(脑裂)。无主即为上文的为空。
以上问题基本都是因为其严格的 CP 设计,任何一种分布式锁都有优缺点,当我们选择一款产品的时候要明确的知道,并接受它的缺点,为它的缺点负责。
公众号:看起来很美(kanqilaihenmei_)
相关推荐
- 史上最全的浏览器兼容性问题和解决方案
-
微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...
-
- 平面设计基础知识_平面设计基础知识实验收获与总结
-
CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...
-
2025-02-21 16:01 yuyutoo
- 写作排版简单三步就行-工具篇_作文排版模板
-
和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...
- 写一个2048的游戏_2048小游戏功能实现
-
1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...
- 今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……
-
最近的天气暖和得让很多小伙伴们喊“热”!!! 昨天的气温到底升得有多高呢?你家有没有榜上有名?...
- CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式
-
之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...
- 你的自我界限够强大吗?_你的自我界限够强大吗英文
-
我的结果:A、该设立新的界限...
- 行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?
-
行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...
-
- 让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
-
去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...
-
2025-02-21 16:00 yuyutoo
- 今年国家综合性消防救援队伍计划招录消防员15000名
-
记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...
- 一起盘点最新 Chrome v133 的5大主流特性 ?
-
1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...
- 竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使
-
style="text-align:center;"data-mce-style="text-align:...
- 学物理能做什么?_学物理能做什么 卢昌海
-
作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...
-
- 你不知道的关于这只眯眼兔的6个小秘密
-
在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...
-
2025-02-21 16:00 yuyutoo
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)