这是我领导于2016-12-8写的问题调查报告。好文共欣赏。       

一. 问题和现象简述:

从11月22日起到12月1日,Thread日志中频繁出现Jgroup相关的报警(warning 级别)

  1. 11.24日上午,11.30日下午分别出现两次172.16.2.113/92服务器向172.16.2.106服务器传送大量数据的问题,以至于把网络占满,系统无法对外提供服务
  2. 从11月25日开始,Thread模块频繁出现内存溢出问题
  3. 从11月29日开始,Thread模块频繁遇到启动hang住问题
  4. 在12月1日晚发现Linux Kenel网络Socket缓冲区设置很小(Jgroup默认5M,Linux OS默认131k),当晚修改了三台机器Kenel参数
  5. 从12.1日晚至今,上述问题都没有再出现,JGroup集群表现非常稳定,再没有报任何异常。  

二. 初步调查

   

服务器分布情况

- 106在一个机柜A,机柜上有交换机A - 113和92在一个机柜B,机柜上有交换机B - A和B通过第三个机柜上的交换机C连通  

Jgroup版本

  • 3.6.4.Final(发布于2015.06)
  • 最新稳定版:3.6.11.Final(发布于2016.09)  

日志分析

查询近6个月日志,从11.18日开始(11.18日只有几条),出现JGroup相关的warning信息,主要有几类:

  • server-172-16-2-113-63346: creating the client socket to /172.16.2.106:25436 failed: Connection refused(连不上端口)
  • JGRP000011: server-172-16-2-113-23585: dropped message 110 from non-member server-172-16-2-92-10055(收到了一个不在集群中的节点发过来的消息,丢弃)
  • server-172-16-2-92-10055: not member of view [server-172-16-2-106-7543|17]; discarding it(收到了不在集群中的节点发过来的配置同步信息,丢弃)
  • server-172-16-2-92-10055: failed to create view from delta-view;(无法同步配置信息)
  • server-172-16-2-106-37680: JOIN(server-172-16-2-106-37680) sent to server-172-16-2-113-24325 timed out (after 2000 ms)(申请加群超时)   这些日志基本表明JGroup集群节点之间组网遇到了问题,也就是说正常情况下,在生产环境有17个Thread模块节点,这些节点应该组成一个JGroup消息群,但是现在有些节点不在群里了。  

JGroup实时内存分析

11月26-30期间多次查看JVM内存,通过分析JGroup JavaBean内存数据,可以看出17个节点组成的JGroup集群是非常不稳定的:

  1. 大部分时候组不成一个群,有时组成3-4个群,有时两三个
  2. 由于JGroup有自动侦听、广播消息、恢复功能,所以有时能自动恢复(几个群自动合并成一个群),但绝大多数时候无法恢复,甚至由两个群分裂为更多群,有的一个群中只有一个节点。
  3. 每次观测,106上的10节点总是能组成一个群,而113/92上的节点有时能加进来,有时则不能。  

初步结论

通过以上调查,可以确认 Thread节点间的JGroup集群处在一个非常不稳定的状态,并且组群状态看起来和网络数据传输有关系。    

三. JGroup工作机制研究

 

  • JGroup支持多种网络协议来传输数据(TCP/UDP/TUNNEL等),Thread应用中配置的是UDP协议

  • UDP协议是不可靠数据传输方式,因此JGroup做了额外的工作来保证数据传输的可靠性

    • NAKACK2是JGroup中负责接收和发送数据的模块,发送的消息会被按顺序编号
    • NAKACK2模块用“被动确认”的机制来重发数据,比如S向R发送消息(序号为0-100),当R收到1-10,接着收到20-30的数据时,会先缓存消息20-30,并向S申请重发序号11-19的消息,直到收到后才会把消息11-30一起向上传给应用层
    • STABLE模块的作用是定时在集群中广播本节点接收到的消息摘要,包括确认收到的消息最大编号,在上述例子中,1-10号消息是确认收到的,最大编号是10.
    • 为了保证消息能够被重发,在Sender节点中,NAKACK2模块会缓存已经发出去的消息,直到它接收到Receiver节点广播出来的消息摘要,把小于对方确认收到的消息编号的消息删除。在上面例子中,即把1-10号消息从发送队列缓存中删除。
  • Jgroup集群中的各节点是怎么发现并组群的?

    • JGroup支持很多方法来互相发现:PING(不是网络命令PING)、TCPPING、FILEPING、JDBCPING等等。
    • Thread项目中使用的默认PING方式
    • PING方法是用IP组播来发送一个“发现请求”(底层使用UDP协议),然后等待回应。收到请求的每个节点都会把当前的集群信息发回去,然后新节点再发起“申请加入”的请求。  
  • JGroup集群中如何检测节点离开或存活?

    • JGroup有很多检测节点存活的方法:FD,FD_ALL, FD_SOCKET等。Thread中同时用了FD_ALL和 FD_SOCKET.
    • FD_ALL方式:
      • i. 集群中每个节点定时广播自己的状态_心跳消息(每8秒);
      • ii. 每个节点收到节点N的心跳消息的节点,更新自己保存的节点N的最新心跳时间
      • iii. 每个节点定时(每2秒)检查自己的保存的其他节点N的最新心跳时间,如果超时(40秒),则广播N节点可疑死亡的消息
      • iv. 集群Coordinator节点发消息给节点N,确实是否存活,如果否,则发起重建集群的消息,将N从集群中移除。
    • FD_SOCKET方式:
      • i. 在集群节点间互相建立TCP链接,组成环状链接,相互检测存活(N1—>N2—>N3—>N1)
      • ii. 当N2连接N3的 TCP Socket断开时,N2广播N3的可疑消息
      • iii. N1收到消息后会发消息给N3确认
      • iv. 一旦N1收不到确认回复,则会发起消息重建集群,将N3移除集群。
      • v. 这种方式探测节点出问题的速度非常快,通常在毫秒级完成。
      • vi. 弊端是当服务器Hang住或者路由器崩溃时,Socket连接可能不会被关闭,导致探测失败。  
  • JGroup怎么做流量控制,即当接收端处理速度慢于发送端时,怎么避免发送端一直发送导致数据丢失或溢出?

    • JGroup FC模块使用了类似点数的机制,即初始时发送端S和接收端R都有默认的点数(2M),
    • 当S向R发送消息时,S和R端的点数都会减少(发送1k数据,点数就减少1k),直到当点数少于一定比例(0.25, 2M*0.25=0.5M)时,R端会再向S端发送消息补充点数(0.5M,S端点数变为1M)。
    • 如果R端处理的很慢,一直不向S发送补充点数消息,S端点数会变为0,这时S端就会停止发送数据。所以消息的发送和接收端实际上构成了一种流量协商的机制。
    • 由于流量控制,集群通过组播方式传递消息时,传递消息的速度被那个处理最慢的节点所决定。  

四. 问题分析和解释

  通过调查,我们发现当前环境配置JGroup节点之间消息丢失的可能性极高:

1) 根据https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Web_Platform/5/html/Administration_And_Configuration_Guide/jgroups-perf-udpbuffer.html, https://developer.jboss.org/wiki/PerfTuning,https://segmentfault.com/a/1190000000473365, 当OS Socket缓冲区较小、网络延迟较大、发送方数据传输较多时,就会发生数据包丢失现象。

2) 在我们系统中,Jgroup默认组播发送和接收Buffer 5M,而实际OS层默认131k,差异巨大,所以丢数据的风险很高。   JGroup节点之间丢消息,基本可以认定是集群不稳定、启动Hang住、内存溢出的根源。  

JGroup集群不稳定问题分析

  1. 当集群节点之间发送的“Discover”消息丢失时,就会导致节点发现不了集群或者加不进集群。
  2. 当集群节点之间探测存活的心跳消息丢失时,就会导致集群节点时进时出,集群中的节点数量在动态变化。  

应用启动Hang住的问题分析

  1. 通过日志发现,每次Hang住时的日志停止在:

GMS: address=server-172-16-2-113-34057, cluster=EH_CACHE, physical address=172.16.2.113:51465 这个日志标明JGroup 节点正在广播自己的消息,等待集群中节点回应

  1. 当有其他节点重启时,Hang住的节点常常立刻就继续并启动了。
  2. 在消息丢包严重、集群很不稳定的情况下,很有可能导致新节点加群出问题。  

应用内存溢出问题分析

  1. 分析内存溢出时JVM Dump文件,看出在Jgroup NAKACK2 模块,占用了近2G内存
  2. 内存溢出时的日志标明,问题发生在NAKACK2 模块试图重发消息时:  
  3. NAKACK2 模块在JGroup中负责发送和接收数据,如上文所述,NAKACK2会缓存发送数据,只有接收到其他节点发送的STABLE消息摘要,确认消息已被收到时才会清除,所以当消息传递不稳定时,缓存数据迟迟得不到清除,越积越多,就会导致内存溢出。
  4. 在网络传输丢数据的情况下,发送端S发送1M数据,接收端R只收到500K,由于流量控制机制, R返还给S的点数只有500K,久而久之就会导致S端点数降为0得不到补充,无法继续发送消息,这也可能导致S端数据缓存过多而内存溢出。

网络流量异常问题分析:

  1. 具体现象:
    1. 11.24日上午出现(10:40-11:10左右),主要症状是92/113服务器流量出,106服务器流量入,瞬时流量最高接近1G/s。持续半小时后自动恢复正常。
    2. 11.30日下午出现(14:55-15:30左右),症状同上,由于通知机房在15:30左右重启了113/92,所以不确定是自动恢复正常还是由于重启。
    3. 11.30日下午故障阶段,106服务器是数据接收方,怀疑是Thread模块问题,当把所有Thread进程都Kill(15:15)了之后,仍然继续大量接收数据(直到15:30)
    4. 11.30日下午故障阶段,为了避免113往106发流量,争取106能用,紧急在106上把113/92加到防火墙黑名单,但没有作用,仍然观测到大量数据流入。
    5. 在网络流量异常时段,106服务器上Thread Tomcat服务占用内存/CPU 均正常。
  2. 可以肯定的是,由于网络传输丢包,需要重传数据,肯定带来了额外的不必要的网络流量,增加了网络负载。
  3. 但是高达1G/s的流量,是否都是JGroup产生的,则存在疑问:
    1. Jgroup本身有流量控制,如果S向R发送数据,R没收到,则S点数很快用完不能再发;如果R收到了,则不会要求重发,而正常的EHCache数据同步根本达不到1G/s的量。
    2. 在106上Thread程序被Kill以后,还有15分钟流量异常,但按照JGroup原理,集群中节点会互相探测心跳,收不到回复就会重新组群,不会再向没有节点的机器上发数据。
    3. 106上面有10个节点,1G/s 平均到每个节点的流量是100M/s,10秒钟就是1个G,但是问题时段观测106上各Thread应用CPU/内存/日志均无明显异常。
  4. 网络调查:
    1. 网络上极少类似报告,只查到一个类似:i. https://developer.jboss.org/thread/201544 ii. JGroup开发者针对问题的建议是:检查网络是否有回路,版本太老需要升级 b) 现有版本,JGroup Bug库中并没有相关问题报告

结论

  1. 可以确定数据传输丢包会引起JGroup重传数据,增加数据传输量,但由于其流量控制机制,不应该占满带宽。
  2. 不排除JGroup有未知Bug引起问题,但是没有明显证据。JGroup框架自2003年发布至今,一直在稳定更新,被应用于近20个世界知名项目,包括JBoss集群、Autodesk、Jetty 会话复制、Tomcat会话复制等,其质量应该是较为可靠的。
  3. 如果网络基础设置有问题(以前出现过网口虚接的问题),在广播报文明显增多的情况下,也可能会触发“网络风暴”: i. http://www.360doc.com/content/11/0330/20/5179015_105969937.shtml ii. http://lbzxy.blog.51cto.com/497155/124316 iii. http://www.programgo.com/article/34214135910/
  4. 本着对问题负责的态度,我们会把JGroup升级到最新稳定版本  

五. 开发团队反思

这次解决问题、调查问题花了很长时间,反映了工作上有需要优化的地方:

  1. 开发团队成员普遍对JGroup技术不熟悉,缺乏技术储备。我们平时对底层技术缺乏重视,绝大部分时间用于开发应用功能,对用到的技术、工具和框架仅限于使用,对其原理缺乏了解,遇到疑难杂症时就得临时抱佛脚。对策:

    开发团队要投入更多资源,对所用到的技术、工具、框架有深入了解和掌握,做到知其然并知其所以然。包括但不限于:学习、演练、调试,要掌握原理和了解各种配置参数。

  2. 开发团队对生产环境不熟悉、缺乏权限,导致一开始调查时无从下手。对策:

    开发团队和运维沟通,获取更多权限;全面了解目前生产环境的部署情况。      

参考资料:

JGroup技术手册:http://www.jgroups.org/manual/index.html JGroup官方系列培训资料:https://github.com/belaban/workshop/blob/master/slides/toc.adoc JGroup官方入门教程:http://www.jgroups.org/tutorial/index.html JGroup源码 Google查询