一次 redis.cpu=100%+、QPS=14W+ 的故障解

2019-07-29  本文已影响0人  猿来是八阿哥

故障描述

运维反馈说,redis 出现了 cpu100%+、QPS=14W+ 的情况,需要解决。


error.png

redis CPU=100%+ 的故障分析

问题是 cpu 打满了,所以猜测是计算量太多,同时,是 redis 和 mq 的消费者两端的进程都出现 cpu=100% 的情况,于是初步定为:

  1. mq 和 redis 计算量太多。
  2. mq 的消费者是多进程运行的,其中包含了 redis 的操作,必然是 mq 操作 redis 不当,才导致 mq 进程作为 redis 的 client,导致 redis 的 client 和 server 都 cpu=100%+。

定位 mq (redis client)中计算量大的程序片段

public function consumeToDo(AMQPMessage $message){
    list($result, $role) = $this->handleMessage($message);
    if($result){
        while(0 == Yii::$app->redis->setnx('xxx_lock', 1)){
            usleep(1000);
        }
        $counter = Yii::$app->redis->get('teacher_student_job_counter');
        list($teacher_job_counter, $student_job_counter) = explode('.', $counter);
        if($role == 'teacher'){
            Yii::$app->redis->set('teacher_student_job_counter', --$teacher_job_counter.'.'.$student_job_counter);
        }else{
            Yii::$app->redis->set('teacher_student_job_counter', $teacher_job_counter.'.'.--$student_job_counter);
        }
        Yii::$app->redis->del('xxx_lock');
    }
    return $result;
}

分析上述代码:为避免多进程操作 redis.teacher_student_job_counter 造成的结果异常,增加了“锁保护”,具体是:在操作前,争抢 xxx_lock 锁,在操作完 redis.teacher_student_job_counter 后在释放 xxx_lock 锁。但存在的问题有:

  1. setnx 的 sleep 时间太短,导致单位时间内操作 redis 的次数太多:1000次/秒。
  2. xxx_lock 锁没有过期时间,如果程序中途异常没有释放,有可能导致永远无法获取到 xxx_lock 锁。
    于是修复过后的代码为:
public function consumeToDo(AMQPMessage $message){
    list($result, $role) = $this->handleMessage($message);
    if($result){
        while(0 == Yii::$app->redis->setnx('xxx_lock', 1)){
            usleep(100000);
        }
        Yii::$app->redis->expire('xxx_lock', 10);
        $counter = Yii::$app->redis->get('class_student_job_counter');
        list($teacher_job_counter, $student_job_counter) = explode('.', $counter);
        if($role == 'teacher'){
            Yii::$app->redis->set('class_student_job_counter', --$teacher_job_counter.'.'.$student_job_counter);
        }else{
            Yii::$app->redis->set('class_student_job_counter', $teacher_job_counter.'.'.--$student_job_counter);
        }
        Yii::$app->redis->del('xxx_lock');
    }
    return $result;
}

redis QPS=10W+ 的故障分析

首先我让运维帮忙查看了下当前的 redis 连接:

connection.png
由图可以看出,redis 出现了很多 age>86400 的 setnx 连接,估计正是因为 xxx_lock 锁异常的问题导致的。于是将这些连接删除。
  1. 删除 age>86400 的 redis 连接
redis-cli -a password client list | awk '{split($5, agearr, "="); if(agearr[1]=="age" && int(agearr[2]) > 86400){split($2, addrarr, "="); print "redis-cli -a password client kill "addrarr[2];}}' | sh
  1. 删除正在执行 setnx 的 redis 连接
redis-cli -a password client list | grep cmd=setnx | awk '{split($2, addr, "="); print "redis-cli -a password client kill "addr[2];}' | sh

心得

在多进程使用 setnx 作为锁控制并发的时候,一定要控制抢锁的频率,避免 redis 的 client 和 server 的 cpu 过高。同时,应该为 setnx 锁设置合理的过期时间,避免在异常情况下造成 redis 连接时间太长、太多的问题(我们的场景是 mq 会根据队列长度,启动更多的进程来消费,于是连接越来越多)。

上一篇 下一篇

猜你喜欢

热点阅读