rabbitmq生产环境下大量connection问题排查
背景
由于业务需要,我们在生产环境下利用rabbitmq作为消息通信的服务, 今天发现一个奇怪的现象, 服务端存在大量的Connections,同时有同等多的channels, 但是根据业务上分析, 我们完全用不到这么多的连接, 随后我们进行了排查问题的第一步:
初步思路
打开client所在的服务器
查看连接5672(amqp端口)的tcp数量以及状态
ss -aoen state established | grep 5672
ss -aoen state established | grep 5672 | wc -l
这个只是部分截图, 但是通过ss分析,可以肯定两件事情:
- 客户端维护的5672 tcp数量是很少的, 也是符合业务场景的
- 每一个tcp都维护了正常的keepalive
打开rabbitmq-server所在的服务器
查看连接5672(amqp端口)的tcp数量以及状态
ss -aoen state established | grep 5672
ss -aoen state established | grep 5672 | wc -l
通过分析发现:
- 服务端tcp数量很大
- 服务端tcp没有keepalive
进一步猜想
服务端存活着大量的dead connection
, 由于客户端没有正确的关闭, 正常情况下客户端即使没有正常断开连接, 但是应该由服务端心跳检测, 或者借助tcp keepalive
来实现服务端断开dead connection
.但是这两种方式均为生效
由此有如下猜想:
1.服务端(rabbitmq-server)没有开启tcp keepalive
?
- 客户端没有配置心跳?
证实猜想
翻阅官方文档, 阅读后, 可以总结为:
作为amqp协议, 鼓励你使用心跳来做conn的维护,客户端 捕获心跳异常来重连
, 如果正确配置了心跳,那么服务端在心跳实际发现客户端无响应, 自然会断开.
随后, 去查看php amqp的源码, 发现如下:
public function __construct(
$host,
$port,
$user,
$password,
$vhost = '/',
$insist = false,
$login_method = 'AMQPLAIN',
$login_response = null,
$locale = 'en_US',
$connection_timeout = 3.0,
$read_write_timeout = 3.0,
$context = null,
$keepalive = false,
$heartbeat = 0,
$channel_rpc_timeout = 0.0,
$ssl_protocol = null
if ($this->keepalive) {
$this->enable_keepalive();
}
protected function enable_keepalive()
{
if ($this->protocol === 'ssl') {
throw new AMQPIOException('Can not enable keepalive: ssl connection does not support keepalive (#70939)');
}
.......
.......
$socket = socket_import_stream($this->sock);
socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1);
}
由上面的源码可以分析到, php的这个lib, 在默认情况下, 完全禁用了heartbeats
以及keepalive
, 由此证明猜想.
A zero value indicates that a peer suggests disabling heartbeats entirely. To disable heartbeats, both peers have to opt in and use the value of 0. This is highly recommended against unless the environment is known to use TCP keepalives on every host.
官方文档中明确说明, 除非特殊情况, 否则强烈建议使用心跳.
遗留的疑问
- tcp keepalive 还需要服务端应用程序额外开启?
根据php的源码socket_set_option中可以看到需要客户端明确开启keepalive
,底层调用 ` socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1);
` 实现.
额外补充一个golang版本的代码:
这是客户端:
func main() {
var tcpAddr *net.TCPAddr
tcpAddr,_ = net.ResolveTCPAddr("tcp","127.0.0.1:9999")
conn,err := net.DialTCP("tcp",nil,tcpAddr)
// 这里设置和不设置影响客户端tcp keepalive
conn.SetKeepAlive(true)
通过ss查看:
上面是未设置SetKeepAlive
, 由此可以看出, 客户端需要明确指定keepalive来使用tcp keepalive机制
总结
整体写了心跳以及tcp keepalive的问题, 也发现了线上php amqplib的默认值导致的该问题, 所以这里修改php的初始化方法, 给心跳分配一个时间(golang版本默认值是15s)
tips
这里涉及到后续要删掉那些死连接, 这里提供一个小小的tips
rabbitmqctl list_connections pid port state user recv_cnt send_cnt send_pend name client_properties | more
这里可以获取client属性,然后匹配php版本,全量关闭,然后重连即可.