注册中心zookeeper重启恢复后,线上微服务却全部掉线了,怎么回事?!
邢台县网站制作公司哪家好,找创新互联!从网页设计、网站建设、微信开发、APP开发、响应式网站等网站项目制作,到程序开发,运营维护。创新互联2013年开创至今到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选创新互联。
最近因为一次错误的运维操作,导致线上注册中心zk被重启。而zk重启后发现所有线上微服务开始不断掉线,造成了持续30分钟的P0故障。
整体排查过程深入学习了 zookeeper的session机制,以及在这种异常情况下,RPC框架应该如何处理。
好了,一起来回顾下这次线上故障吧,最佳实践总结放在最后,千万不要错过。
某天晚上19:43分左右,误操作将线上zk集群下线(stop),总共7台节点,下线了6台,导致zk停止工作。
在发现节点下掉后,于19:51分左右将所有zk节点进行重启(start),期间服务正常运行,没有收到批量业务调用的报错和客诉。
直到19:56分,开始收到大面积调用失败的警报和客诉,我们尝试着依赖自研RPC框架与zk间重连后的「自动恢复」机制,希望能够在短时间内批量恢复。
但是很不幸,过了接近8分钟,没有任何大面积恢复的迹象。
结合zk znode节点数上升非常缓慢的情况,于是我们采取了应急措施,将所有微服务的pod原地重启,执行重启后效果显著,大面积服务在短时间内逐步恢复。
我们自研的RPC框架采用典型的 注册中心+provider+consumer 的模式,通过zk临时节点的方式做服务的注册发现,如下图所示。
结合故障期间发生的现象,我们初步分析:
这里存在一个问题:
为什么zk集群恢复后,provider客户端「自动重连」注册中心的机制没有生效?导致consumer被推送了空地址列表后,没有再收到重新的provider注册节点信息了。
根据大量测试,我们找到了稳定复现本次问题的方法:
zk session过期包括 「服务端过期」 和 「客户端过期」,在「客户端过期」情况下恢复zk集群,会导致「临时节点」丢失,且无法自动恢复的情况。
1)在集群重启恢复后,RPC框架客户端立刻就与zk集群取得重连,将保存在本地内存待注册的providers节点 + 待订阅的consumers节点 进行重建。
2)但是zk集群此时根据snapshot恢复的「临时节点」(包括provider和consumer) 都还在,因此重建操作返回NodeExist异常,重建失败了。(问题1:为什么没有重试?)
3)在集群重启恢复40s后,将过期Session相关的 临时节点全都移除了。(问题2:为什么要移除?)
4)consumer监听到 节点移除 的空列表,清空了本地provider列表。故障发生了。
基于这个分析,我们需要进一步围绕2个问题进行源码的定位:
通过源码分析,我们看到,RPC框架客户端与服务端取得重连后,会将内存里老的临时节点进行重新创建。
这段逻辑看来没有什么问题,doRegister成功之后才会将该节点从失败列表中移除,否则将继续定时去重试创建。
继续往下走,关键点来了:
这里我们可以看到,在创建临时节点时,吞掉了服务端返回的NodeExistsException,使整个外层的doRegister和doSubscribe(订阅)方法在这种情况下都被认为是重新创建成功,所以只创建了一次。
正如上面分析的,其实正常情况下,这里对NodeExistsException不做处理是没有问题的,就是节点已经存在不用再添加了,也不需要再重试了,但是伴随服务端后续踢出老sessionId同时删除了相关临时节点,就引起了故障。
众所周知,zk session管理在客户端、服务端都有实现,并且两者通过心跳进行交互。
在发送心跳包时,客户端会携带自己的sessionId,服务端收到请求,检查sessionId确认存活后再发送返回结果给客户端。
如果客户端发送了一个服务端并不知道的sessionId,那么服务端会生成一个新的sessionId颁布给客户端,客户端收到后本地进行sessionid的刷新。
当客户端(curator)本地sessionTimeout超时时,会进行本地zk对象的重建(reset),我们从源码可以看到默认将本地的sessionId重置为0了。
zk服务端后续收到这个为“0”sessionId,认为是一个未知的session需要创建,接着就为客户端创建了一个新的sessionId。
服务端(zookeeper) sessionTimeout的管理,是在zk会话管理器中看到一个线程任务,不断判断管理的session是否有超时(获取下一个过期时间点nextExpirationTime已经超时的会话),并进行会话的清理。
我们继续往下走,关键点来了,在清理session的过程中,除了将sessionId从本地expiryMap中清除外,还进行了临时节点的清理:
原来zkserver端是将sessionId和它所创建的临时节点进行了绑定。伴随着服务端sessionId的过期,绑定的所有临时节点也会随之删除。
因此,zk集群恢复后40s,zk服务端session超时,删除了过期session的所有相关临时节点。
1)zk集群恢复的第一时间,对zk的snapshot文件进行了读取并初始化zk数据,取到了老session,进行了create session的操作,完成了一次老session的续约(重置40s)。
集群恢复关键入口-重新加载snapshot:
进行session恢复(创建)操作,默认session timeout 40s:
2)而此时客户端session早已经过期,带着空sessionid 0x0进行重连,获得新sessionId。但是此时RPC框架在临时节点注册失败后吞掉了服务端返回的NodeExistsException,被认为是重新创建成功,所以只创建了一次。
3)zk集群恢复后经过40s最终因为服务端session过期,将过期sessionId和及其绑定的临时节点进行了清除。
4)consumer监听到 节点移除 的空列表,清空了本地provider列表。故障发生了。
经过上面的源码分析,解决方案有两种:
于是我们调研了一下业界使用zk的开源微服务框架是否支持自愈,以及如何实现的:
dubbo采用了方案2。
注释也写得非常清楚:
“ZNode路径已经存在,因为我们只会在会话过期时尝试重新创建节点,所以这种重复可能是由zk服务器的删除延迟引起的,这意味着旧的过期会话可能仍然保存着这个ZNode,而服务器只是没有时间进行删除。在这种情况下,我们可以尝试删除并再次创建。”
看来dubbo确实后续也考虑到这个边界场景,防止踩坑。
所以最后我们的解决方案也是借鉴dubbo fix的逻辑,进行节点的替换:先deletePath再createPath,这么做的原因是将zk服务端内存维护的过期sessionId替换新的sessionId,避免后续zk清理老sessionId时将所有绑定的节点删除。
回顾整个故障,我们其实还忽略了一点最佳实践。
除了优化对异常的捕获处理外,RPC框架对注册中心的空地址推送也应该做特殊判断,用业界的专业名词来说,就是「推空保护」。
所谓「推空保护」,就是在服务发现监听获取空节点列表时,维持本地服务发现列表缓存,而不是清空处理。
这样可以完全避免类似问题。
当前标题:Zookeeper恢复了,线上微服务却全部掉线了,怎么回事?
浏览路径:http://www.gawzjz.com/qtweb/news41/206591.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联