php - predis SCAN 比 KEYS 慢

标签 php redis predis

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

我读到有关 KEYS 不适合生产的信息,因此我做了一些测试来检查性能。我使用 predis 1.1.6 (php),并在本地计算机和使用 elasticache redis 的测试 AWS 环境中进行了测试。我在一个包含大约 30 万个项目的节点上执行此操作。

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

所以我需要搜索并删除客户端和模块中的所有 key 。 由于我使用前缀,因此我设置了正确的前缀并使用了 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。

最佳答案

在生产中使用KEYS

首先,了解为什么 KEYS 不应该在生产中使用很重要。

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

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

根据文档:

While the time complexity for this operation is O(N), the constant times are fairly low. For example, Redis running on an entry level laptop can scan a 1 million key database in 40 milliseconds.

Warning: consider KEYS as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases. This command is intended for debugging and special operations, such as changing your keyspace layout. Don't use KEYS in your regular application code. If you're looking for a way to find keys in a subset of your keyspace, consider using SCAN or sets.

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

KEYS的替代品

扫描

KEYS 的常见替代方案是 SCAN(这就是您正在使用的)。请注意,这仍然是一个糟糕的替代方案,因为它与 KEYS 非常相似,只是结果以 block 的形式返回,并且具有 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');

当你想获取所有 key 时,只需使用 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.21 秒
  • 扫描:约 20 秒
  • HKEYS:~0.01 秒

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

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

使用 Redis 扩展我得到:

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

关于php - predis SCAN 比 KEYS 慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67034121/

相关文章:

php - Braintree 付款托管资金

php - 如果旁边有 '-',正则表达式只会添加 4 个数字

java - CompletableFuture 跨微服务(JVM)

redis - 如果我在主 Redis 实例上运行长事务或 Lua 脚本,它会阻塞只读从属实例吗

laravel-5 - Laravel、Predis - 如何迭代哈希

php - 如何通过 Accept : application json header in call, 发布请求 Laravel 5.1

php - 更新选择框值的 JavaScript 或 Qt 方式?

redis - Redis复制中的master_replid2是什么意思?

c - 将变量传递给 C 中的其他函数

php - Predis 使用 PHP : How to get which Redis node ( host ) that holds a particular key?