cmd=NULL空连接的排查方式
2019-02-18
现象
dba收到连接数过多的报警,在redis上client list看有哪些连接,如下图,发现大多数都是cmd=NULL的连接,cmd=NULL一般是客户端建立了连接,但是没有任何操作。
初步排查
要么是redis问题,要么是业务问题。
登上php机器,找一个端口查看连接的建立情况,如下图:
然后持续的看同一个端口的建联情况,发现过一会儿就没有这个连接了。
目前为止得到的结论:
连接是会断开的,现在的问题是一直有新连接在建立,但是建立完没有任何行为。 这么来看大概率是业务代码的问题。
对业务进行排查
发现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);
}
会多出来一条,验证我们的猜想。
解决并验证
为了解决线上的问题,我们先将构造方法中的创建实例删掉,看一下是否解决线上的问题。
发布了api最大的业务之后,连接数降了接近三分之一,得已验证。
之后如何避免
php业务代码不能为了简单在构造方法中进行创建,一个连接的建立是比较昂贵的行为,可以考虑使用的时候进行创建,也可以想一下其他更好的方法。