php-resque 长时间运行 redis cpu占用过高的问题
前些天,公司有台队列服务器,用的是php-resque方案,redis进程cpu占用居高不下,后来到一个核心跑满,系统负载报警多了起来。
本来以为是用这个队列服务的业务大增导致的性能问题,好在能水平拓展,加了一台服务器来一起扛。
神奇的是,新加的服务器cpu使用率在3%以内徘徊,老的服务器cpu使用率依然在50%左右,而访问已经平均分流了。
区别就是老的服务器redis服务跑了大半年了,新的是刚装的。
查看老的redis服务,发现数据居然有好几G,不太正常,因为redis只是用来做存队列数据的,队列完成之后数据应该是删除的,应该只有很小的数据,
进入redis-cli 执行keys * 发现不断跳出类似resque:job:2fee271769ffdca0715f3b45d6ec1005:status的key
这是php-resque用来保存队列任务状态的key,查看php-resque源代码
lib/Resque/Job/Status.php
/**
* Update the status indicator for the current job with a new status.
*
* @param int The status of the job (see constants in Resque_Job_Status)
*/
public function update($status)
{
if(!$this->isTracking()) {
return;
}
$statusPacket = array(
'status' => $status,
'updated' => time(),
);
Resque::redis()->set((string)$this, json_encode($statusPacket));
// Expire the status for completed jobs after 24 hours
if(in_array($status, self::$completeStatuses)) {
Resque::redis()->expire((string)$this, 86400);
}
}
更新状态的方法中,设置了已经完成的key 1天以后删除
如果key真的是一天过期,那也没有什么问题,只是稍微存多一点,不过这好几g的数据,就有些异常了。
其实redis的过期删除机制是这样的:
第一种,如果有访问这个key,并且key过期,删除,我们队列的数据,程序不会去访问昨天的数据,所以这个机制没法保证我们的队列数据能删除
第二种,抽样过期,每秒十次随机查找20个key,发现过期删除,当找到大于25个key过期之后,继续执行这一过程,详细介绍见 http://redis.io/commands/expire
我们的数据量是非常大,新增非常快的,不知道第二种过期算法,在这样的数据场景下是不是有问题,结果就是运行快半年后,负载异常了。
其实在我们的业务场景中,已经完成的任务是可以直接删除的,所以将php-resque的代码做了修改,将expire直接改成del,这样就没有负载异常的问题了。
/**
* Update the status indicator for the current job with a new status.
*
* @param int The status of the job (see constants in Resque_Job_Status)
*/
public function update($status)
{
if(!$this->isTracking()) {
return;
}
$statusPacket = array(
'status' => $status,
'updated' => time(),
);
Resque::redis()->set((string)$this, json_encode($statusPacket));
// 直接删除已经完成的任务
if(in_array($status, self::$completeStatuses)) {
Resque::redis()->del((string)$this);
}
}