工作

rabbitmq遇到的一次tcp半打开的问题

问题描述

业务中有一个测试服务器,里面运行了好几个任务队列。但是自从将任务队列从redis迁移到rabbitmq上后,一直会在运行一段时间后停止运行。一般这种情况下,要么是进程退出了,要么是连接断开了。但是检查后发现,进程是正常运行的,并且通过netstat发现,连接也一直存在。如果是连接断开,我的代码中也做了断线重连的机制。

问题排查

一开始以为是php-amqplib这个库的问题,就去找它的issue,看看有没有类似的问题。这里没有找到类似的bug。

然后去复现了一个最小的demo,这个问题一直都是出现在运行相当长的时间之后。于是决定对测试服务器上已经出现问题的代码进行抓包。新push的任务,任务队列的进程就是接收不到,但是netstat结果,任务队列到rabbit服务的连接又确实是存在的。

没有办法就去随便翻翻《UNIX网络编程》的TCP章节,然后想到TCP的全双工特性。全双工特性就是说在一个给定的连接上应用可以在任何时候在进出两个方向上既发送数据又接收数据。建立一个全双工连接后,需要的话可以把它转换成一个单工连接。于是用netstat检查rabbit服务的连接,发现是没有到任务队列的连接的。所以问题就是出现在这里。**但是仔细思考一下,这里的问题并不能用全双工特性去解释,因为全双工转成单工是tcp的一个特性,但是在这个问题中,应该是一个异常情况。全双工是需要双端协商的,而我这里的问题应该是:

服务器关闭了连接,但是任务队列却没有收到关闭的Fin报文,很多时候被称为半打开

问题解决

解决这个问题很简单,启用php-amqplib的心跳包机制即可。

更多的思考

1.半连接,半打开,半关闭(以下用A,B代表连接的两端)

半连接:出现在tcp的三次握手阶段。A发送syn,B响应ack,syn后,此时处于半连接状态。如果A不发送ack,B将会一直为这个半连接分配一段内存空间。因此可以使用这个特点对B进行半连接攻击

半打开(half-open):A断开连接但是却没有发送Fin报文,导致B不知道。在维基百科上半打开和半连接是相同的。

半关闭:在关闭的4次挥手阶段,A端发送Fin,B端ack但是不发送Fin。

半连接、半关闭都是正常出现的情况。半打开则是不正常的状态,一个Unix进程无论自愿地(调用exit或是从main函数中返回)还是非自愿地(收到一个终止本进程的信号)终止时,所有打开的描述符都被关闭,这也导致仍然打开的任何TCP连接上也发出一个FIN。也就是说,只有当服务器断电等这种非正常关闭的情况下才会出现半连接,否则对端都应该收到Fin报文,然后关闭连接。

1.什么情况导致了半打开?

服务器断电这类情况肯定是没有出现的,所以一定是其他地方有问题导致了这个情况。因为这个问题只在测试服务器上出现,生产服务器上并没有。所以有点难推测。

2.双向连接(bidirectional)和全双工(full-duplex)

在我的理解中,双向连接指的是A端确认了到B端的连接,B端也确认了到A端的连接。全双工则指可以同时发送和接收,互不干扰。

3.心跳机制是如何避免这种情况的?

心跳机制一般都是隔一段时间主动发送一个消息给对端来确认连接是否存活。如果连接丢失,则必然不会收到对端的响应。这样在响应超时后重新发起连接即可。

其实tcp也有一个keep-alive机制。与心跳包作用类似,但是一是检查的周期长,二是一旦启用,机器上所有的连接都会启用这个机制,导致资源浪费。

参考

TCP half-open
半连接、半打开、半关闭

8 comments

我也遇到相同的问题 我设置了php-amqplib里面的心跳时间间隔 但是无法通过tcp去监听请求 或者说是监听的不对 因为我设置心跳时间为15秒 但是我检测到的都是60秒的请求。或者说我关掉客户端还是有相同的tcp请求。不知道仁兄是如何处理的

我不是太清楚你的问题在哪。但是正常使用下,就是默认启用心跳包即可,不需要额外的设置。心跳包只是为了防止意外断开连接,程序却不知道对端已经离线。

$this->connection = new AMQPStreamConnection(
            $GLOBALS['rabbitmq_ip'],
            $GLOBALS['rabbitmq_port'],
            $GLOBALS['rabbitmq_user'],
            $GLOBALS['rabbitmq_pass'],
            '/',false,'AMQPLAIN',null,'en_US',60,60,null,true,30
        );

另外你需要处理好断线重连。

你好, 我是看了这个文章 进行了网络抓包 原文连接http://www.pandan.xyz/2017/03/19/rabbitmq%20%E5%AE%9A%E4%BD%8D%E5%AE%A2%E6%88%B7%E7%AB%AFconnection%E7%BB%8F%E5%B8%B8%E6%96%AD%E5%BC%80%E9%97%AE%E9%A2%98/

在这段文章的末尾一图中有抓包到heartbeat请求信息。 还有一个疑问就是你的这个请求连接配置参数 是设置倒数第二位为 $keepalive = true? 不是 $heartbeat=0 就是不启用吗? 不是应该heartbeat= 大于0的时间值吗?
谢谢你

好的,那请问一下 你有试过抓包看过心跳数据吗?我在官方rabbitmq的官方网站看 和我实际的数据看着有出入显示的内容,比如说协议 以及 显示的具体信息 Info 和官网的不一样 我的这边抓到的协议都是tcp 根本没有 amqp的协议 难道是语言的关系? 官方链接:https://www.rabbitmq.com/amqp-wireshark.html。谢谢

amqp是tcp的上层协议,底层还是tcp。这一点是没有问题的。官方的图应该是wireshark对amqp进行了识别并标注了。包括info信息也是wireshark做了标注。

我这边抓包看了心跳数据,我设置的是30秒的心跳。这样的话,差不多是每15秒可以抓到一次包。这里是验证过了的。

(我用的是linux系统。并且我的wireshark也不标注amqp以及heartbeat等信息)

谢谢。
我这边也是centos 的 我也有用tcpdump -i eth0 -nn ‘src host 192.168.100.123’ 这段命令去抓包 但是 抓到的数据是如下格式的
18:01:42.129339 IP 192.168.100.123.50481 > 192.168.100.1.53: 6132+ A? rabbitmq. (26)
18:01:42.130020 IP 192.168.100.123.50481 > 192.168.100.1.53: 6132+ A? rabbitmq. (26)
18:01:47.013976 ARP, Reply 192.168.100.123 is-at 00:0c:29:1e:6e:b5, length 28
18:02:42.140496 IP 192.168.100.123.57689 > 192.168.100.1.53: 34799+ A? rabbitmq. (26)
18:02:42.141049 IP 192.168.100.123.57689 > 192.168.100.1.53: 34799+ A? rabbitmq. (26)
18:02:47.013607 ARP, Reply 192.168.100.123 is-at 00:0c:29:1e:6e:b5, length 28
18:02:47.140292 ARP, Request who-has 192.168.100.1 tell 192.168.100.123, length 28
18:03:42.145155 IP 192.168.100.123.34278 > 192.168.100.1.53: 7664+ A? rabbitmq. (26)
18:03:42.145871 IP 192.168.100.123.34278 > 192.168.100.1.53: 7664+ A? rabbitmq. (26)
18:03:47.013814 ARP, Reply 192.168.100.123 is-at 00:0c:29:1e:6e:b5, length 28
18:03:47.144743 ARP, Request who-has 192.168.100.1 tell 192.168.100.123, length 28

rabbitmq 里面是如下格式的:
Name User name State SSL / TLS Protocol Channels From client To client Heartbeat
192.168.100.123:59932 admin running ○ AMQP 0-9-1 1 0B/s 0B/s 30s
我不太清楚到底问题出在那里 看rabbitmq 是有正常设置Heartbeat 的时间为30s. 但是从抓包的数据看 都是1分钟才会有一次请求。
非常感谢你帮我分析

你这个抓包有一个问题。每次连接的端口号都发生了改变。最起码能说明连接有重连。每次都重连了才会导致端口号发生变化。

Leave a Comment

电子邮件地址不会被公开。 必填项已用*标注