记一次 zk session 超时异常

  一次诡异的 zk session 超时异常分析

Posted by mzl on April 1, 2018

排查 zk session 超时的异常

zk session 超时

上周四,性能测试童鞋告诉我,说我们后端的大数据应用报错。都是跑了一会儿才报错,刚开始是好的。

那个应用是个 tomcat 应用,提供了几个 web service 接口,主要作用是通过 es 来查询数据,然后通过 spark app 去 hbase 里拉取数据返回给平台。由于任务执行过程可能超过 10s,所以不能马上返回,会把任务结果缓存到本地缓存 ehcache 里,然后平台不断尝试调接口去获取执行结果。每条 hbase 记录都有100多个字段,还算比较大的。

另外,这个应用可能部署多个节点,主节点信息保存在 zk 上,如果当前节点不是主节点,会转发到主节点上去。 而主节点切换时,会把原来主节点上的 spark sc 给干掉,免得浪费 yarn 上的资源。

我听了没放在心上,毕竟功能比较简单。于是慢悠悠听完演讲,才去看这个问题。基本猜测是性能测试,压力过大造成的, 毕竟之前测试的时候没发现这个问题。

回来看了一把,报的错是 zookeeper 的 session 超时,造成主节点切换,结果 sc 被干掉了。问题是,这个环境是单机环境, 主节点只能是当前节点,所以就必须找到为什么会 zookeeper session 超时了。

排除压力大的原因

由于是性能测试中爆出来的问题,所以强烈怀疑是压力大造成的,而且本司的大数据服务器基本都是超负荷跑的。那么就先复现一把吧。

测试童鞋不辞劳苦地又复现了一把,果然,重启后跑了一个小时不到,又复现了。同样的情况,是在全接口测试的最大压力下复现的。 一般 zk session 超时都是压力过大造成的,去年本司还把 zk 安装到了独立的 SSD 盘上,理论上不应该出现呀,难道还有这个问题?

看来得监控一把系统压力了。系统压力监控,我也没有好的办法,同时要监控硬盘/内存/CPU/网络四项,还比较麻烦。 最后决定,硬盘监控用命令:iotop; 内存监控用命令:sar; CPU 监控命令同样是 sar; 网络连接监控用 lsof.

结果辛苦了一天多,唯一发现的价值就是:zk session 超时异常和 CPU 使用率强相关。但理论上讲,后者应该是前者的结果, 而不应该是造成前者的原因呀。

由于本司条件比较差,所有组件都是放在一起,比如 es/hadoop/spark/hbase/kafka 等等,全放一块儿,简单粗爆。之前就发现 es 是 抢占 CPU 资源的神器,会不会是 es 抢占 CPU 造成的 zk session 超时呢?尽管发现用户进程占 CPU 时间的 90% 以上,但CPU是时间片轮询的,再怎么样,10s的时间,怎么样都能完成一次 zk client 和 zk server 的心跳了。 所以个人觉得这个可能性不大。但总得验证一下。

于是把全接口中的查询/检索接口停了,于是CPU压力瞬间就降下来了,而内存使用率也不那么高。

诡异的事情来了,跑了半个多小时,zk session 超时问题又出现了!这说明绝对不可能是压力问题造成的,方向错了!

迷茫

不是压力问题,那会是什么呢?没头绪了。

迷茫了半天多,会不会是某些系统资源比较少,比如网络连接/文件打开数什么的? 为了能监控更多的东西,我决定找个成熟的系统监控工具。找了半天监控系统的工具,发现网上说 spotlight on unix 挺好用的! 像发现新大陆一下,搞下来试试。结果人家跑的好好的,在我这儿,估计是系统不兼容的缘故,spotlignt 一直报连不上服务器,说 ssh 密码不对,事实上我拿相同的密码连服务器一点儿问题没有。

快放弃了,继续迷茫。。。

由于只有这个应用报了 zk session 超时问题,其它组件都没有报,说明是这个应用的问题,不是压力问题,不是 zk 的问题。 这时候,同事建议我看下 jvm,会不会是 gc 造成的。但既然不是压力,不应该有 gc 呀。

现在已经没有其它方向了,我决定不抱希望的试下 gc 这个想法。

初步发现原因

配置好 jstatd 的配置文件,在本地跑起来 VisualVM,又把全接口跑起来,结果发现这个应用的 tomcat 的 jvm 堆使用量一直在上升, 从不下降,我也没有太留意,毕竟我不知道这个应用的 jvm 堆大小是多大(汗!)。

大约跑了半个小时后,突然,zk session 超时的异常出现了,看了下此时的 jvm 堆信息,老生代堆 14.5g,全部用掉了,发生 Full gc 时, 完全没有释放任何空间!原因找到了,gc 造成的!!!

突然想来一句国骂(我是文明人,真的!)。

jmap/jhat 踩坑

找到了 gc 的原因,可是我们的 tomcat 启动时,没配置 -XX:+HeapDumpOnOutOfMemoryError 这一项,所以就手动用 jmap 把 jvm 堆信息 导出来,然后用 jhat 分析吧。

重启后导出来 jvm 堆文件, 我的命名是:heap.bin. 导出的有点儿晚,导出来时,已经有 4.1G 了。我传到本地,尝试用 jhat 去分析。 想当然的以为 2048M 就够了,毕竟谁会一次性把 4.1G 全读进来,然后再分析啊。

注意,坑来了! jhat 分析命令是: jhat -J-Xmx2048M ./heap.bin.

  1. 命令开始执行后,我 16g 的电脑就开始卡,大约卡了 10 分钟,报错了:Out of heap space. F*ck!
  2. 换成 4096M,再执行,还错!这次卡了 20分钟。
  3. 换成 8096M,我就不信了,结果还错!这次卡了半个小时。
  4. 再换成 10240M,再执行,还错!

想要放弃了。。。我就改个bug,咋就这么难呢。。。

最后拿我司的大数据服务器来分析,命令是:jhat -J-d64 -J-mx50g heap.bin 这次终于搞定了,感谢天,感谢地啊!

定位原因

从本地访问 ip:7000, 发现内存中最大的是类是:[C、java.util.HashMap$Entry、java.lang.String 这三个。这个 [C 是什么鬼。。。 而且,这个应用里,没多少 HashMap 和 String 呀,一脸蒙逼。

翻了半天代码,也没找到原因,又不死心地看看 [C 和 HashMap$Entry,什么发现也没有,最后偶尔点进排第三的 String,奇怪的事情来了, String 里大量的都是数据,这些数据应该都是数据库里的,或缓存里的,难道是代码某个地方没释放?不应该呀!最后怀疑这些东西是缓存 里的数据,问题在 ehcache.

于是修改了一下 ehcache 的配置文件,把 maxElementsInMemory 从 1000 改为 100(之前手贱改成了1000,说多了都是泪呀)。 重启一把再试试,果然这次 tomcat jvm 保持在 3-4g,上不到 5g 了。

总结

一次请求大约有 3000 条记录,每个记录大约 120 个字段;原来内存中保存了 1000 次请求的结果。最后在内存里就要保存近 360000000 个字符串。 这 15g 哪儿放得下啊。。。就是十分之一,也勉强啊!