前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >zookeeper Watch丢通知故障的定位

zookeeper Watch丢通知故障的定位

原创
作者头像
谢盼
修改2020-03-24 17:37:51
2.8K0
修改2020-03-24 17:37:51
举报
文章被收录于专栏:视频AI

在下面的描述中,ZK指的是zookeeper,Watch丢通知故障简称为丢消息,因个人水平的原因,文章中定位出的原因,未必是真实的原因,仅供参考。

背景介绍

在我深度参与的一个计算平台项目中,团队第一次使用ZK作为配置中心,ZK的功能:(1)存储和固化配置;(2)在配置发生更新的时候,通知多个工作节点拉取新的配置。这两项是ZK作为分布式服务框架最常用的功能之一。项目使用github上开源的go-zookeeper库来实现ZK的操作,库地址为:github.com/samuel/go-zookeeper/zk

工作节点任务的升级,依赖于ZK通过Watch消息通知给客户端代理,以下简称agent,agent是由我开发维护的模块。ZK一共3个节点,按照IP最后的数字,分别命名为144、227、229。故事发生在agent和三台ZK服务器之间。

系统简化结构
系统简化结构

故障现象

用户在客户端执行一些配置更新后,经常反馈计算节点的配置没有更新成功,还在跑着旧版本。登录计算节点查看日志,可以发现在用户执行更新后的几分钟内,Agent没有进入任何通知消息的回调处理。故障的紧急恢复采用的方法是重启agent,重启后会全量拉取新的配置。另外注意到,重启后一段时间内(在几个小时到几天不等),可以正常收到ZK的消息。

定位过程

首先简单介绍代码。在zk.Connect连接上conf.ZkHost中的某一台ZK节点后,在go-zookeeper的sendLoop中会按照指定的时间间隔,由agent主动发起ping操作并等待应答。没有收到或者收到错误的应答之后,连接将被关闭,并且在一个for循环中主动去尝试conf.ZkHost中的其他节点,如果迅速恢复,使用的sessionid不发生变化。

代码语言:go
复制
//连接ZK服务器,注册回调函数
if agent.zkConn, _, err = zk.Connect(conf.ZkHost, time.Second, 
                           zk.WithEventCallback(ZkCallback), 
                           zk.WithMaxBufferSize(10*1024*1024), 
                           zk.WithMaxConnBufferSize(10*1024*1024)); 
                           err != nil {
   Logger.LogError("connect %v err, %s", conf.ZkHost, err)  
}
//ZK回调函数
func ZkCallback(ev zk.Event) {
	go agent.HandleEvent(ev)
}
//ZK消息处理函数
func (m *McAgent) HandleEvent(ev zk.Event) {	
	switch ev.Type {
	case zk.EventNodeChildrenChanged:
		//
	case zk.EventNodeDataChanged:
		//
	case zk.EventNodeDeleted:
		//	
	}
}

根据代码,结合go-zookeeper实现,发现了第一个问题:没有处理ZK的状态变化消息。在agent与ZK节点之间由于网络偶发故障或延时,导致Agent ping ZK节点失败的时候,ZK会连接其他ZK节点,这时候之前通过GetW方式注册的Watch事件会全部丢失。导致这个Agent再也收不到原先监控的ZK节点变化的消息。

针对这个故障,考虑到在网络故障的短暂时间内存在丢消息的可能,因此解决方案比较直接:

代码语言:javascript
复制
func (m *McAgent) HandleEvent(ev zk.Event) {
   
   switch ev.Type {
   case zk.EventNodeChildrenChanged:
      //
   case zk.EventNodeDataChanged:
      //
   case zk.EventNodeDeleted:
     //
   case zk.EventSession:
      if ev.State == zk.StateExpired {
        //继续使用本地缓存的数据,可能是脏的
        //打告警
      } else if ev.State == zk.StateConnected {
        //从ZK全量拉取新数据,重新注册Watch
      }
   }
}

作为一个严谨求实的程序员,本着对自己代码高度负责任的态度,我使用iptables模拟网络断开重连的情况,能及时的给出告警,并且可以自动重连,重连之后还能实时收到ZK服务器发出的事件通知。

代码语言:shell
复制
iptables -A OUTPUT -p tcp --dport 2181 -j DROP
iptables -D OUTPUT -p tcp --dport 2181 -j DROP

更新上线,平静了好几天,没人找我说怎么任务又更新失败了。完美。

在我以为这事终于消停之后,又偶尔有用户在群里怼我,说有更新失败的情况,但是我没有收到任何告警信息。感觉颜面扫地,年底可以one星走人了。不过离年底还有点时间,先再找找原因。

从故障Agent的日志看,没有任何异常,也没有任何ZK连接变化相关的日志信息。去ZK节点上捞取日志,通过一系列检索过程,发现了故障场景的共性。

故障相关的ZK节点日志
故障相关的ZK节点日志

日志不太直观,下面的图形更直观一些。简单的说,就是Agent所连接的ZK服务器,在静默的情况下,由一台(144)迅速迁移到了另一台(227),使用相同的sessionid重建与新服务器的连接。由于客户端注册的Watch在ZK服务器上是以本地存储的方式记录,并没有同步给其他机器。因此,在连接静默迁移到新的服务器之后,Watch又丢掉了。所谓静默迁移,就是agent端没有收到连接变化相关的任何回调消息。这可能是go-zookeeper的bug,但是时间精力原因,没有继续深入下去了。

故障过程描述
故障过程描述

解决方案

从ZK相关的文档可以看出,ZK消息并不保证一定送达,在网络短暂故障的重连期间,仍然可能存在消息丢失的情况,所以在ZK服务器压力不大并且数据不大的情况下,彻底的解决方案是废弃Watch机制不用,周期性全量拉取ZK数据。

使用定期全量拉取数据之后,类似问题再也没有出现过。

优化空间

在ZK节点数量比较多、节点数据比较大的情况下,大量重新拉去数据会给ZK服务器和网络带来压力。准备执行的优化是设置一个节点,存储数据版本信息,在数据版本未发生变化的时候,不执行重新拉取的操作。

还有一个方向是去调试go-zookeeper代码,比较简单的方式是在其中起连接ZK IP的监控代码,在调用Next函数的时候,使用setState方式发送一下连接变化的消息。但是由于通过网络传输的消息存在丢失的可能,这仍然不是最终的解决方法。

再次强调,相关结论存在模糊和不清楚的地方,不要轻信。有更好方法的,请留言告知。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景介绍
  • 故障现象
  • 定位过程
  • 解决方案
  • 优化空间
相关产品与服务
微服务引擎 TSE
微服务引擎(Tencent Cloud Service Engine)提供开箱即用的云上全场景微服务解决方案。支持开源增强的云原生注册配置中心(Zookeeper、Nacos 和 Apollo),北极星网格(腾讯自研并开源的 PolarisMesh)、云原生 API 网关(Kong)以及微服务应用托管的弹性微服务平台。微服务引擎完全兼容开源版本的使用方式,在功能、可用性和可运维性等多个方面进行增强。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档