【Redis 实用技巧#0】在 Redis 中,如何优雅地找出 10 万个指定前缀的 key?
在维护大型 Redis 集群的日常工作中,我们常常会遇到这样的需求:从数亿个 key 中筛选出特定前缀的一批数据。在此我们也给大家分享一个技巧,起源于不久前我们一位客户的监控系统就触发了一个告警,业务线需要紧急导出所有 user:profile: 前缀的 key 进行分析,数量大约在 10 万左右。
面对这样的需求,很多人的第一反应可能是直接使用 KEYS 命令,一条简单的 KEYS user:profile:* 似乎就能搞定。在开发环境中这个命令响应极快,能在毫秒级返回结果。
但要提醒的是,如果在生产环境执行这条命令,后果可能是灾难性的:轻则服务响应缓慢,重则直接导致整个 Redis 实例被监控判定为宕机,随即触发重启。
KEYS 命令为何是生产环境的"核弹"?
Redis 的核心架构是单线程模型,所有命令排队串行执行。
KEYS 命令的执行逻辑是遍历整个 key 空间,不管你的 key 是 100 万个还是 1 亿个,它都会从头扫到尾。
更致命的是,这个过程会完全阻塞主线程,期间所有的读写请求、key 过期操作都会被强制暂停。这直接导致了连锁反应:CPU 使用率瞬间飙升,客户端响应时间从毫秒级暴涨至秒级甚至超时。在极端情况下,这种长达数秒的停顿足以让健康检查失败,触发故障转移或实例重启。因此,KEYS 命令在开发环境虽方便,但在生产环境使用无异于引火自焚。
所有请求(读、写、过期)全部卡死
CPU 使用率瞬间飙高
响应时间激增
严重时甚至会被监控系统判定为宕机,导致系统重启
Redis 官方的安全替代方案:SCAN 命令
从 2.8 版本开始,Redis 提供了 SCAN 命令作为安全替代。它的核心设计理念是**增量遍历**,即将一次性的全量扫描拆分为多次小规模操作。
基本用法很直观:
这条命令会返回两部分内容:一部分是当前批次匹配的 key,另一部分是一个游标值,用于下一次继续扫描。当游标返回 0 时,意味着整个遍历过程结束。我们可以通过编写一个简单的循环,不断调用 SCAN 直到游标归零,就能安全地获取所有目标 key。
✅ 优点
SCAN 命令的优势显而易见。首先它是非阻塞的,每次只扫描一小部分数据就释放主线程,不影响正常业务。其次支持模式匹配和批次大小控制,让我们能灵活调节扫描速率。最重要的是,它允许边扫描边处理结果,不需要等到全部遍历完成。
⚠️ 缺点
当然,SCAN 也有其局限性。由于采用近似随机的哈希槽遍历方式,它无法保证 key 的返回顺序,而且在 Redis 实例写入负载很高时,同一 key 可能会被重复返回,需要**业务层做好去重**。此外,面对超大规模数据集,完整遍历依然需要较长时间,只是不会阻塞业务而已。
实战代码:安全扫描的正确姿势
理论不如实践,下面是一个开箱即用的 Java 示例 👇
这段代码的关键在于三个细节:
使用 Set 自动去重避免重复 key 干扰
通过 count 参数灵活控制每批次扫描量
在必要时添加 sleep 控制速率。
实际运行中,你会看到类似这样的输出:
整个扫描过程平顺推进,Redis 实例的 CPU 和响应时间几乎不受影响。
理解 SCAN 的底层原理
要真正用好 SCAN,需要理解它的实现机制。Redis 内部使用哈希表存储所有 key,SCAN 命令本质上是通过游标分段遍历哈希槽。每次调用时,它只扫描哈希表的一小部分,返回该片段中匹配模式的 key。由于哈希表在持续写入时会发生 rehash,游标的视角可能会有轻微漂移,因此 SCAN 被称为"近似一致性遍历",而非某个时间点的完整快照。
这意味着 SCAN 的性能表现与多个因素相关。key 总量越大,遍历所需批次自然越多。count 参数并非精确控制,而是单次调用扫描槽位的上限值,设置过大会导致单次调用变慢。在写入密集型场景下,重复 key 的概率会略有增加。理解这些底层细节,才能在调优时做出正确决策。
从架构层面根治问题
如果你发现自己频繁需要按前缀搜索 key,这其实是数据模型设计需要优化的信号。问题不在于搜索方式,而在于为何需要搜索。以下两种架构方案能从根源上解决问题。
1️⃣ 为特定前缀维护索引集合
第一种方案是为高频前缀维护反向索引。在写入数据时,同步将 key 加入对应的索引集合。例如每次写入 user:profile:1001 时,执行 SADD user:profile:index 1001。后续查询只需执行 SMEMBERS user:profile:index 即可瞬间获取所有 key,复杂度从 O(N)降至 O(1)。这种方案查询性能极佳,天然支持分页(可用 ZSET 实现有序索引)。代价是增加了写入逻辑的复杂度,需要保证索引与主数据的一致性,同时索引本身可能产生脏数据,需要定期巡检校验。
写入数据时,同步维护一个索引集合:
这样以后只需要:
就能立刻获取所有对应 key,无需全库遍历。
2️⃣ 数据库分片/Partitioning
第二种方案是数据库分片或分区。当某类 key 数量达到千万级以上时,可按业务前缀拆分存储。将不同前缀的 key 分散到独立 Redis 实例,或在集群模式下使用 hash tag(如 user:{profile}:id)确保同类 key 落在同一节点。这样每次扫描的范围被限制在单个分片,效率大幅提升。分片策略的选择需要权衡业务特性和运维成本,但对于超大规模数据集,这是必经之路。
写在最后
Redis"快"的特性人尽皆知,但在生产环境中,这种快是把双刃剑。其让你能轻松应对亿级数据读写,也能让一条不当命令拖垮整个系统。当下次下意识想敲下 KEYS * 时,请先确认自己是在开发环境还是生产环境。
真正的工程成熟度不在于写出最简短的命令,而在于设计出最稳定的系统。理解工具的原理与边界,才能在关键时刻做出正确选择。
版权声明: 本文为 InfoQ 作者【艾体宝IT】的原创文章。
原文链接:【http://xie.infoq.cn/article/797a83fbfddbe6dbe6ed56d1b】。文章转载请联系作者。







评论