php redis之高性能扫描和批量操作

前提:

redis的扫描方法,使用scan,而不是使用 keys* 

因为keys* 会全部key扫描一次,key数量很多时,容易造成阻塞太久甚至down机。

 

scan 原理: 指定每次遍历的key数目和查找规则 ,通过遍历去匹配出对应的key。还会返回当前最后一个匹配值的游标cursor 

scan 语法:   SCAN cursor [MATCH pattern] [COUNT count]

  • cursor - 游标。从0开始
  • pattern - 匹配的规则。
  • count - 指定每次遍历多少个key。
    • 可以简单理解为每次遍历多少个元素
    • 根据测试,推荐 Count大小为 10000

 

因此:Count 参数和  Key的总数 一致时,Scan 命令就和 Keys 效果一样了。Count 参数越大,总时间越短,但每次Redis 阻塞时间也会越长,需要取舍。

 

 

scan用法:

SCAN命令是基于游标的,每次调用后,都会返回一个游标,用于下一次迭代。当游标返回0时,表示迭代结束。

第一次 Scan 时指定游标为 0,表示开启新的一轮迭代,然后 Scan 命令返回一个新的游标,作为第二次 Scan 时的游标值继续迭代,一直到 Scan 返回游标为0,表示本轮迭代结束。

eg: 我想查找  test 开头的key,每次查找30条

 

1.第一次执行找到了test_b,返回了下个游标 17 。

scan 0 match test* count 30

2.使用上次返回的游标,再次查找,又找到了 test_a 和 test

scan 17 match test* count 30

此时游标返回0,表示结束,一共找到 test_a test_b test 三个key

 

在PHP的用法:

/**
 * 查找redis key
 * @param null $pattern // 要匹配的规则  'test_*'
 * @param int $count    // 每次遍历数量.count越大总耗时越短,但单次阻塞越长。 建议5000-10000。并发不高则可以调至接近1w。
 * @return array
 */
public function scan($pattern,$count = 6000){
    $keyArr = array();
    while (true){
        // $iterator 下条数据的坐标
        $data = $this->redis->scan($iterator, $pattern, $count);
        $keyArr = array_merge($keyArr,$data ?: array() );

        if ($iterator === 0){   //迭代结束,未找到匹配
            break;
        }
        if ($iterator === null) {//"游标为null了,重置为0,继续扫描"
            $iterator = "0";
        }

    }
    $keyArr = array_flip($keyArr);
    $keyArr = array_flip($keyArr);
    return $keyArr;
}

//使用 查找test开头的key
$pattern = 'test*';
$this->scan($pattern);

 

 

查出来key之后,若要批量删除,则可以使用redis管道 PIPELINE ,效果是 将多个命令合起来只执行一次,减少redis和客户端的交互时间;

其他批量操作也可以用PIPELINE,下面举个删除的例子:

$keyArr = array('test','test_a','test_b');
$pipe = $redis->multi(2);   //使用管道 事务=2,表示使用管道
foreach ($keyArr as $key){
    $pipe->del($key);
}
$pipe->exec();

 

 

最后来个 PIPELINE结合scan的用法:

// 返回查询的redis key
function redisScan($pattern = null,$count = 6000,$is_del = 1){
    $redis = RedisClient::getInstance();
    $keyArr = $redis->scan($pattern,$count); // 上面的scan方法

    if ($is_del){
        $pipe = $redis->multi(2);   //使用管道
        foreach ($keyArr as $key){
            $pipe->del($key);
        }
        $pipe->exec();

    }else{
        return $keyArr ?? [];
    }
}

//清除缓存
$pattern = 'test*';
$this->redisScan($pattern);

 

注意:redis管道类似事务,multi和exec之间不能包含mysql操作

posted @ 2022-12-27 20:42  jaychou、  阅读(1168)  评论(0编辑  收藏  举报