slan's blog

有梦就去追,累了就休息

php-resque 长时间运行 redis cpu占用过高的问题

date: 2015-03-20 09:53:51

前些天,公司有台队列服务器,用的是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);
    }
}