Nov 7, 2025
后端

Redis 中如何快速高效从海量 key 中找到符合某种前缀的 key ?

如何快速高效的获取 redis 中所有符合 pattern 的 key,本文从三个维度来介绍。

Redis 中如何快速高效从海量 key 中找到符合某种前缀的 key ?
Posted by
SYSTEM· 36 次阅读

我们经常会遇到这个问题,获取所有符合某某前缀的所有key,怎样用高效且不影响性能的方式是值得讨论的问题。下面分析三种方式的利弊来说明一下这个问题。

使用 KEYS prefix:*

这是我们最容易想到也是最容易使用的方式:

PLAINTEXT
KEYS "your_prefix:*"

但是它的问题是:

  • 单线程模型:Redis 的主要命令处理是单线程的。一个命令在执行时,其他所有客户端的请求都必须等待。

  • 全量遍历:KEYS为了找出所有匹配的 key,会遍历数据库中的全部 1 亿个 key。在遍历完成之前,Redis 无法处理任何其他命令。

  • 生产环境灾难:在一个有 1 亿 key 的实例上执行KEYS,会导致 Redis 服务卡顿数十秒甚至数分钟,所有依赖 Redis 的业务都会出现超时和雪崩,这是严重的生产事故。

结论:KEYS 命令只能在调试或 key 总量极少的场景下使用,严禁在线上生产环境对大规模实例使用。

使用 SCAN

正确的操作应该使用 SCAN 命令,它是为解决 KEYS 的阻塞问题而设计的:

PLAINTEXT
# 第一次执行,从游标 0 开始
SCAN 0 MATCH "your_prefix:*" COUNT 1000

# Redis 返回结果
# 1) "1762"  <-- 这是下一次迭代要用的新游标
# 2) 1) "your_prefix:key1"
#    2) "your_prefix:key2"
#    ... (返回一批 key)

# 第二次执行,使用上一次返回的新游标 "1762"
SCAN 1762 MATCH "your_prefix:*" COUNT 1000

# ... 重复这个过程,直到返回的游标为 "0" 时,表示遍历完成。

再来看看优缺点:

  • 非阻塞式(渐进式)迭代:SCAN命令不是一次性返回所有结果。它每次只扫描一小部分数据,然后返回一个游标(cursor)。你下次调用时传入这个游标,Redis 就会从上次结束的地方继续扫描。这个过程是无锁的。

  • 对业务无影响:由于每次扫描的计算量很小(可以把一次SCAN看作 O(1) 操作),它不会长时间阻塞 Redis 主线程,对线上服务的影响极小。

  • COUNT参数:COUNT只是一个建议值,告诉 Redis 你希望每次迭代返回大约多少个 key。它不是精确的,有时多有时少,但可以用来控制单次扫描的粒度。

所以你需要在你的客户端代码中(例如 Java, Python, Go)编写一个循环,不断调用SCAN,直到返回的游标为 "0",并将每次返回的结果聚合起来。

更高效的方式

使用索引

这里需要引入数据结构,就像数据库中一样, 我们同样也可以为 redis 的 key 建立索引。

使用集合 (Set) 或哈希 (Hash) 作为索引是最优的解决方案。

在写入数据时,除了存储原始的 key-value,我们还应该维护一个“索引”。

具体做法:

  • 写入时:

    • 当你创建一个 key,例如 SET "prefix:123" "value"

    • 同时,将这个 key 的唯一部分(如123)添加到一个固定的Set中。

    • SADD "index:prefix" "123"

  • 查找时:

    • 不再需要 SCAN。直接读取索引 Set 的所有成员。

    • SMEMBERS "index:prefix"

    • 这个命令会立即返回所有 prefix 后缀的 key("123","456", ...),时间复杂度是 O(N),其中 N 是 Set 中的成员数量(10w),而不是数据库的总 key 数(1亿)。这非常快。

  • 删除时:

    • 删除原始 key 的同时,也要从索引 Set 中移除对应的成员。

    • DEL "prefix:123"

    • SREM "index:prefix" "123"

优缺点:

  • 优点:查询性能极高,从根本上解决了扫描问题。

  • 缺点:增加了写入/删除时的操作复杂度,并且会占用额外的内存来存储索引。

在从节点 (Replica) 上执行

如果这是一个低频的、用于离线分析的需求,并且你不想修改现有的数据结构,可以考虑:

  • 将 SCAN(甚至是KEYS,如果该从节点不服务其他业务)的操作放在 Redis 的从节点上执行。

  • 这样可以确保即使操作耗时较长,也完全不会影响到主节点(Master)的正常读写,从而隔离了风险。

评论交流
暂无评论,欢迎分享您的见解