zoco

cmd=NULL空连接的排查方式

2019-02-18


现象

dba收到连接数过多的报警,在redis上client list看有哪些连接,如下图,发现大多数都是cmd=NULL的连接,cmd=NULL一般是客户端建立了连接,但是没有任何操作。

NuTA3R.png

初步排查

要么是redis问题,要么是业务问题。

登上php机器,找一个端口查看连接的建立情况,如下图:

NuTtDf.png

然后持续的看同一个端口的建联情况,发现过一会儿就没有这个连接了。

目前为止得到的结论:

连接是会断开的,现在的问题是一直有新连接在建立,但是建立完没有任何行为。 这么来看大概率是业务代码的问题。

对业务进行排查

发现php代码里面有

public function __construct($ctx){
	$this->ctx = $ctx;
    $this->expireRedis = $this->ctx->expireRedis;
}

在构造方法里创建了一个redis对象的实例,有些php的开发人员为了context的写法简单会使用这种方式。 但是该class下有非常多的方法,如果有的请求不用这个redis,而只是用到了class中的其他方法,就会导致白白创建了一个连接。

看一下创建的时候都做了什么

public function getExpireRedis() {
    return $this->ctx->redis->getClient('host', 'port');
}

public function getClient($host, $port, $time_out = 3) {
	return new Redis_Client($host, $port, $time_out);
}

Redis_Client在构造方法里就会进行对host和port的connect,所以一旦创建了redis的实例,那么就会进行连接,如果之后对这个redis实例什么都不做的话,那么就会在一段时间内,redis server就会存在一条cmd=NULL的连接。

复现

为了证实上述的说法,我们进行一下简单的复现

php-fpm的方式下,对下述代码进行请求:

$redis = new Redis();

for ($i = 1; $i < 10000;$i++) {
    $redis = new Redis();
    $redis->pconnect('127.0.0.1', 6379, 3000);
}

会多出来一条,验证我们的猜想。

NuTWaF.png

解决并验证

为了解决线上的问题,我们先将构造方法中的创建实例删掉,看一下是否解决线上的问题。

发布了api最大的业务之后,连接数降了接近三分之一,得已验证。

NuT5G9.png

之后如何避免

php业务代码不能为了简单在构造方法中进行创建,一个连接的建立是比较昂贵的行为,可以考虑使用的时候进行创建,也可以想一下其他更好的方法。