【问题标题】:predis SCAN slower than KEYSpredis SCAN 比 KEYS 慢
【发布时间】:2021-04-10 11:58:54
【问题描述】:

我需要一种方法来通过前缀获取所有键来删除它们。

我了解到 KEYS 不适合生产,所以我做了一些测试来检查性能。我使用的是 predis 1.1.6 (php),我在本地机器和使用 elasticache redis 的测试 AWS 环境中进行了测试。我在一个大约有 300k 个项目的节点上执行此操作。

我使用前缀:CLIENT/ID_CLIENT/MODULE:HASH 翻译成 client/9999/products:452a269b82c199ef27f5a299e3b0f98531216ccf

所以我需要从客户端和模块中搜索并删除所有键。 由于我使用前缀,所以我设置了正确的前缀并使用了 predis 键方法:

$this->_redisPrefix('client/9999/products:');
$keys = $this->_redis_client->keys('*');

这个速度非常快,大约需要 50 毫秒。

由于不建议在生产中使用 KEYS,因此我尝试使用 SCAN 来实现相同的目的。 predis 没有扫描方法,所以我需要这个:

foreach (new Iterator\Keyspace($this->_redis_client, 'client/9999/products:*') as $key) {
    $keys[] = $key;
}

这会返回完全相同的结果,但需要 20 秒(!)。我认为这与我的本地机器有关,但我已经将它部署到我们的 aws 环境中并且响应时间是相同的。我没有使用分页,因为我需要删除所有项目并且我不知道有多少。可以是 10 个,也可以是 1000 个(或更多)

我想避免使用 KEYS,但我不能在这种时间使用 SCAN。

【问题讨论】:

    标签: php redis predis


    【解决方案1】:

    在生产中使用KEYS

    首先,重要的是要了解为什么不应在生产中使用KEYS

    KEYS 的时间复杂度为 O(N),其中 N 是整个数据库的元素个数。不是有多少满足模式。由于只能同时运行一个命令(Redis 不是多线程的),因此其他所有操作都必须等待该 KEYS 完成。

    见:Why KEYS is advised not to be used in Redis?

    根据文档:

    虽然此操作的时间复杂度为 O(N),但常数时间相当低。例如,在入门级笔记本电脑上运行的 Redis 可以在 40 毫秒内扫描 100 万个密钥数据库。

    警告:将 KEYS 视为仅应极其小心地在生产环境中使用的命令。当它针对大型数据库执行时,它可能会破坏性能。此命令用于调试和特殊操作,例如更改键空间布局。不要在常规应用程序代码中使用 KEYS。如果您正在寻找一种在您的键空间子集中查找键的方法,请考虑使用 SCAN 或集合。

    这表明如果您的记录少于一百万,使用keys 应该没问题。但是随着您的数据库增长,或者您有更多的并发用户,可能会出现问题。

    KEYS 的替代品

    扫描

    KEYS 的常见替代品是SCAN(您正在使用它)。请注意,这仍然是一个糟糕的选择,因为它与KEYS 非常相似,只是结果以块的形式返回,并且具有 O(N),其中 N 是整个数据库的元素数。

    优点是不会阻塞服务器,但时间复杂度和KEYS一样。事实上,如果你只想得到结果,而不关心阻塞数据库,它可能会比KEYS 慢,因为它必须执行多个查询(如你所见)。

    HSET

    更好的选择是使用 HSET。

    当您想将元素放入HSET 时,请使用:

    HSET client/9999/products "id_547" "Book"
    HSET client/9999/products "whatever_key_you_want" "Laptop"
    
    $this->_redis_client->hset('client/9999/products', 'id_547', 'Book');
    $this->_redis_client->hset('client/9999/products', 'whatever_key_you_want', 'Laptop');
    

    当你想得到所有的钥匙时,只需使用HKEYS

    HKEYS client/9999/products
    1) id_547
    2) whatever_key_you_want
    
    $this->_redis_client->hkeys('client/9999/products')
    

    与 KEYS 不同,HKEYS 的复杂度为 O(N),其中 N 是散列的大小(不是整个数据库的大小)。

    如果密钥变得非常大,您可能需要使用HSCAN

    性能测试

    在大约 2,000,000 个项目的 redis 数据库中:

    for ($i = 0; $i <= 100; $i++) {
        $client->set("a:{$i}", "value{$i}");
    }
    for ($i = 0; $i <= 100; $i++) {
        $client->hset("b", $i, "value{$i}");
    }
    
    

    测试 1:键

    $start = microtime(true);
    var_dump(count($client->keys('a:*')));
    $end = microtime(TRUE);
    echo ($end - $start) . "s\n";
    

    测试 2:扫描

    $start = microtime(true);
    $count = 0;
    foreach (new Keyspace($client, 'a:*') as $key) {
        $count++;
    }
    $end = microtime(TRUE);
    echo ($end - $start) . "s\n";
    

    测试 3:HKEYS

    $start = microtime(true);
    var_dump(count($client->hkeys('b')));
    $end = microtime(TRUE);
    echo ($end - $start) . "s\n";
    

    结果

    • 键:~0.21s
    • 扫描:~20 秒
    • HKEYS:~0.01s

    如您所见,HKEYS 速度更快,并且不受数据库大小的影响。

    我还建议使用 redis PECL 扩展而不是 predis:

    有了 Redis 扩展,我得到了:

    • KEYS:~0.21s(变化不大)
    • 扫描:~17 秒(小幅增加)
    • HKEYS:~0.0004s(快得多!)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-04-17
      • 2015-12-18
      • 2017-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-14
      • 1970-01-01
      相关资源
      最近更新 更多