首页 > > > 灵异事件的始作俑者:Reset数据包(上)
“可怜无定河边骨,犹是春闺梦里人”,意思是说这些早已经在河边化为累累白骨的亡魂,其实还是远方家乡中妻子日夜盼归的丈夫。这首诗写出了战争的残酷性,让人感觉悲惨,禁不住让人潸然泪下。工作以后,没有想到类似问题发生在了我身上,到底发生了什么TCP相关的问题呢?
有一天,业务开发和DBA运维跑过来,说凌晨调用Cetus(数据库中间件)的定时脚本(脚本作为客户端,调用Cetus服务器)出问题了,Cetus没有响应回来。当听到这个噩耗,我一脸懵逼。查看了Cetus的错误日志,结果没有发现任何有价值的线索。于是我问开发能不能把问题重现一下,因为只要能够重现,问题就容易解决。开发回去试验了多次,问题没有重现,不过开发有了新的发现:执行相同的SQL语句,白天SQL的响应时间跟凌晨的响应时间不一样。开发认为SQL响应很慢的时候,Cetus会阻塞住会话,不返回结果给客户端。于是请DBA运维修改脚本中的SQL去模拟SQL响应慢的场景,结果Cetus返回了结果,没有出现凌晨的死等现象。
一时找不到根本原因,开发发现了Cetus有功能方面的问题。因此,开发和DBA运维更加坚信是Cetus迟迟没有返回响应。事实上,这些问题与Cetus响应并不相关。
从第一天的情况来看,问题确实出现了,每一个相关的人都想找到问题原因,会做各种猜测,但并没有找到真正的原因。
第二天,开发反馈半夜的脚本问题又出现了,但白天还是无法重现。DBA运维反馈说白天试验偶尔会出现,第一次执行会出现,但后续几天DBA运维又进行了多次试验,问题再也没有重现。开发着急的抱怨很快就要使用脚本了,我只能建议开发白天使用脚本,避免凌晨出现的问题。因为所有怀疑都聚焦在Cetus上面,很难从其它角度去分析问题。
作为Cetus开发人员,这么诡异的问题不能轻易放过,否则会影响Cetus的后续发展,而且领导也要求问题必须尽快解决。我首先想到的是能不能部署一套tcpcopy测试环境,因为如果是Cetus程序的问题,复制线上流量到测试环境,可以在测试环境的Cetus端输出详细的debug日志,用来分析没有返回响应给客户端脚本的原因。这种方案需要搭建一套配套的测试环境,成本太高,最终没有被采用。最终决定用低成本抓包分析的方案,即在凌晨脚本执行的时候,在服务器端进行抓包,分析当时发生了什么,看到问题的真相:要么Cetus一直没有发送响应,要么Cetus返回了响应,客户端脚本没有收到。只要确认Cetus发送了响应,就不是Cetus的问题。
第三天,开发反馈凌晨问题没有再次出现,抓包分析也确认问题没出现。经过深入思考,如果是Cetus程序的问题,应该不会如此这么诡异:在凌晨多次出现,在白天却很难出现。只能继续等问题再出现,进行抓包分析。
第四天,问题还是没有出现,我忍,继续等。
第五天,问题终于出现,有希望了。
抓包文件很多,首先让开发给出问题出现的时间点,在大量的抓包信息里面找出出现问题的SQL语句,最终找到的结果如下:

图1
从上面的抓包文件(服务器抓取)来看,发送SQL时间为凌晨3点,Cetus服务过了630秒(03:10:30.899249 - 03:00:00.353157)返回SQL响应信息给客户端,这说明Cetus是返回SQL响应的,但过了仅仅238微秒(03:10:30.899487 - 03:10:30.899249),服务器的TCP层就接收到了reset数据包,这么快就返回,reset数据包非常可疑。需要注意的是,这reset数据包,不能直接认为是客户端发的。我们首先要确认reset数据包是谁发的,要么是客户端发的,要么是中途设备发的。图2展示了这个TCP会话的其它时刻的来回往返时间,也即rtt时间:

图2
在No.5901 Cetus返回给客户端Rsponse
OK,而在No.5907就捕获到了ACK,时间差是214微秒(02:31:00.073946 - 02:31:00.073732),跟reset数据包响应速度差不多,都是在200多毫秒。很可惜,通过时间差来判断没有意义,200多微秒时间太短,客户端发送reset是可能的 ,中途设备发送也有可能(一般内网调用都在这个量级左右)。
因为抓包只在服务器端进行,客户端数据包情况并不了解。我们尝试通过分析服务器端的抓包文件,进行逻辑推理找出问题根源。假设客户端发送了reset,那意味着客户端TCP层已经不存在这个连接的TCP状态了,TCP状态从有到无,会通知客户端应用连接异常,客户端脚本在收到这个通知后,会立即报错,但现实是客户端仍然在等待响应回来,因此假设不成立,客户端没有发送reset。客户端连接还活着,但服务器这边的相应连接已经被reset干掉了。“可怜无定河边骨,犹是春闺梦里人”正反映了这个现象:服务器端连接已经不存在了,而客户端连接还在坚守着等待服务器响应的归来。
reset到底是谁发的呢?最大怀疑对象是亚马逊的云环境。DBA运维根据这个抓包分析结果询问亚马逊客服,得到如下信息:

图3
客服给出的答复与我们分析的结果一致,是亚马逊的elb(类似lvs负载均衡器)强制干扰了TCP会话。图3中的回复指出,如果响应超过了350秒阈值(抓包显示为630秒),亚马逊的elb设备就会发送reset给响应的一方(本案例里是发送给服务器)。开发部署的客户端脚本,被忽视了,并没有接收到reset,仍认为服务器连接仍然存活着。官方对此类问题的建议是,利用TCP
keepalive机制来规避这方面的问题(后续课程会详细讲述)。
得到官方答复以后,问题算是彻底解决。
作为Cetus开发,不应该受到其他人判断的干扰,而应该从用户反馈的现象去推理分析问题。在MySQL响应时间差不多的情况下,问题在凌晨多次出现,而在其它时间不出现,这时就应该怀疑问题跟Cetus关系不大,当机立断采用抓包分析排除问题。
在只有单方面的抓包数据的情况下,可以利用已知的信息去推理出更多的隐含信息。
为什么凌晨有时候出现问题,有时候不出现?这是因为出问题的SQL是统计类SQL,会统计前一天的信息,而每一天的数据量是不一样的,因此执行的SQL时间是不一样的,有时候超过了亚马逊的350秒阈值,有时候没有超过,所以凌晨有时候出现,有时候不出现。为什么会在凌晨出现?这是因为凌晨执行的时候,数据都是重新加载到内存的,速度慢,而第一次执行完以后,数据已经在内存中了,继续执行就会很快,达不到亚马逊的时间阈值。
为什么白天执行没有出现问题?因为白天的时候,开发和DBA运维是直接访问Cetus,并没有通过亚马逊的elb设备访问,所以永远也不会出现问题。DBA运维反馈的偶尔白天出现是误判,干扰了问题的解决。
这个案例充分说明了reset数据包发送不当会带来的问题,后续课程会继续讲述reset的深度内容。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69935868/viewspace-3124853/ ,如需转载,请注明出处,否则将追究法律责任。