index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html
![]()
在处理拥有上亿键的Redis实例时,如何安全高效地查找特定前缀的键是一个关键挑战。文章首先指出,直接使用KEYS命令是不可取的,因为它会阻塞Redis主线程,可能导致生产环境的严重故障。随后,文章详细介绍了使用SCAN命令进行渐进式迭代查找的正确方法,强调了其非阻塞特性,并提供了具体的命令示例和客户端实现思路。最后,文章进一步升华,提出了通过维护Set或Hash集合作为索引的架构设计思路,从根源上解决全量扫描问题,并讨论了在从节点执行扫描操作以隔离风险的方案,为面试和生产实践提供了多层次的解决方案。
❌ **避免使用 KEYS 命令进行全量扫描**:KEYS命令是一个阻塞式操作,会遍历Redis实例中的所有键,在拥有大量键(如1亿)的生产环境中执行,会导致Redis服务长时间卡顿,影响所有依赖Redis的业务,是严重的生产事故,仅适用于调试或键数量极少的场景。
✅ **标准安全做法:使用 SCAN 命令进行渐进式迭代**:SCAN命令是为解决KEYS命令的阻塞问题而设计的。它通过游标(cursor)进行非阻塞的、分批次的数据扫描。客户端需要循环调用SCAN,传入上一次返回的游标,直到游标值为0,这样可以最大程度地减少对线上服务的性能影响,是生产环境中查找大Key的标准方法。
🚀 **架构优化:利用集合 (Set/Hash) 作为索引**:从根本上解决全量扫描需求,可以在写入数据时就维护一个索引。例如,将特定前缀键的标识符存储在一个固定的Set集合中。查找时,直接使用SMEMBERS命令获取Set的成员,其性能远高于SCAN,但增加了写入/删除操作的复杂度和内存占用。
🛡️ **隔离风险:在从节点执行扫描**:如果查找需求是低频的、用于离线分析,且不希望修改现有数据结构,可以将SCAN(甚至KEYS,若从节点不服务其他业务)操作放在Redis的从节点上执行。这样可以完全隔离对主节点的性能影响,确保主服务的可用性。
Fox 2025-11-06 07:15 广东

考察你对Redis底层原理、性能影响以及生产环境实践的综合理解。
这是一个非常经典的 Redis 面试题,它考察的不仅仅是你知不知道某个命令,更是你对 Redis 底层原理、性能影响以及生产环境实践的综合理解。我会分层次地回答这个问题,从“错误答案”到“标准答案”,再到“加分答案”。
层级一:错误或有严重风险的答案 (KEYS) 最直接、最容易想到的方法是使用 KEYS 命令:KEYS "your_prefix:*"
为什么这是错误的答案?在面试中,如果你只回答这个,基本就结束了。因为KEYS命令是一个阻塞式
操作。单线程模型:Redis 的主要命令处理是单线程的。一个命令在执行时,其他所有客户端的请求都必须等待。全量遍历:KEYS为了找出所有匹配的 key,会遍历数据库中的全部 1 亿个 key。在遍历完成之前,Redis 无法处理任何其他命令。生产环境灾难:在一个有 1 亿 key 的实例上执行KEYS,会导致 Redis 服务卡顿数十秒甚至数分钟,所有依赖 Redis 的业务都会出现超时和雪崩,这是严重的生产事故。
结论:
KEYS 命令只能在调试或 key 总量极少的场景下使用,严禁在线上生产环境对大规模实例使用。
层级二:标准且安全的答案 (SCAN) 正确的操作应该使用 SCAN 命令,它是为解决 KEYS 的阻塞问题而设计的。
SCAN 0 MATCH "your_prefix:*" COUNT 1000
SCAN 1762 MATCH "your_prefix:*" COUNT 1000
为什么SCAN是标准答案?非阻塞式(渐进式)迭代:SCAN命令不是一次性返回所有结果。它每次只扫描一小部分数据,然后返回一个游标(cursor)。你下次调用时传入这个游标,Redis 就会从上次结束的地方继续扫描。这个过程是无锁的。对业务无影响:由于每次扫描的计算量很小(可以把一次SCAN看作 O(1) 操作),它不会长时间阻塞 Redis 主线程,对线上服务的影响极小。COUNT参数:COUNT只是一个建议值,告诉 Redis 你希望每次迭代返回大约多少个 key。它不是精确的,有时多有时少,但可以用来控制单次扫描的粒度。
实现方式:你需要在你的客户端代码中(例如 Java, Python, Go)编写一个循环,不断调用SCAN,直到返回的游标为 "0",并将每次返回的结果聚合起来。层级三:更优的架构设计(加分答案)面试官问这个问题,其实也想考察你是否具备良好的数据结构设计能力。一个优秀的架构师会思考:“我们是否可以从根源上避免这种全量扫描的需求?”如果你能提出以下方案,会非常加分:方案一:使用集合 (Set) 或哈希 (Hash) 作为索引
这是最优的解决方案。在写入数据时,除了存储原始的 key-value,我们还应该维护一个“索引”。具体做法:写入时:当你创建一个 key,例如SET "your_prefix:123" "some_value"同时,将这个 key 的唯一部分(如123)添加到一个固定的Set中。SADD "index:your_prefix" "123"
查找时:不再需要SCAN。直接读取索引 Set 的所有成员。SMEMBERS "index:your_prefix"这个命令会立即返回所有your_prefix后缀的 key("123","456", ...),时间复杂度是 O(N),其中 N 是 Set 中的成员数量(10w),而不是数据库的总 key 数(1亿)。这非常快。
删除时:删除原始 key 的同时,也要从索引 Set 中移除对应的成员。DEL "your_prefix:123"SREM "index:your_prefix" "123"
优缺点:优点:查询性能极高,从根本上解决了扫描问题。缺点:增加了写入/删除时的操作复杂度,并且会占用额外的内存来存储索引。
方案二:在从节点(Replica)上执行
如果这是一个低频的、用于离线分析的需求,并且你不想修改现有的数据结构,可以考虑:将SCAN(甚至是KEYS,如果该从节点不服务其他业务)的操作放在Redis 的从节点上执行。这样可以确保即使操作耗时较长,也完全不会影响到主节点(Master)的正常读写,从而隔离了风险。
面试回答总结面试官您好,对于这个问题,我的回答如下:首先,绝对不能使用KEYS "your_prefix:*"命令。因为KEYS是一个阻塞操作,它会锁住 Redis 并遍历所有 1 亿个 key,导致线上服务出现严重卡顿,这是生产环境的禁忌。推荐的标准做法是使用SCAN命令。SCAN是一个非阻塞的、渐进式的迭代命令。我们可以从游标 0 开始,配合MATCH "your_prefix:*"和COUNT参数,通过循环迭代的方式分批次地将这 10w 个 key 找出来。这个过程不会阻塞主线程,对线上服务影响极小,是安全可靠的操作。从架构设计的角度看,一个更治本的方案是在写入时就维护一个索引。例如,我们可以用一个固定的Set集合来存储所有这些特殊前缀 key 的唯一标识。当需要找出它们时,直接使用SMEMBERS命令读取这个 Set 即可,速度极快,避免了任何扫描操作。当然,这需要在业务代码中增加维护索引的逻辑。所以,对于这个临时性的查找需求,我会选择 SCAN;如果这是一个频繁的操作,我会建议通过维护索引的方式来优化架构。
作者丨Fox
来源丨公众号:Fox爱分享(ID:dcl_yc)
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn


阅读原文
跳转微信打开