Redis有1亿个Key,如何优雅地找出特定前缀的那10万条?

简介: Redis是后端常用缓存,但线上禁用`KEYS`——它会阻塞单线程致服务假死。应改用渐进式`SCAN`命令安全遍历;更优解是建索引Set或分片存储。快不是万能的,稳才是生产环境的第一准则。(239字)

Redis 是每一位后端工程师最熟悉的中间件之一。
它轻量、高效、灵活,几乎是现代高并发系统中最常见的缓存组件。

可当业务规模上来之后,Redis 的简单世界也不再简单。
有一天你可能突然接到一个看似普通的需求:

「我们的 Redis 里大概有 1 亿个 Key,现在想把前缀是 user:profile: 的那 10 万条数据找出来。」

看似小事,但稍有不慎,就可能让整个 Redis 实例直接假死。


从“最直觉”的做法说起

很多人第一反应是:

KEYS user:profile:*

对吧?
这行命令在开发环境里跑得飞快,一眨眼结果就出来了。

但如果你在线上执行它,那就等着运维同事来找你“喝茶”吧。

❌ 为什么 KEYS 命令不能在线上用?

Redis 是单线程模型,KEYS 命令会遍历整个键空间(无论有多少 Key),
在执行期间,它会完全阻塞主线程。

这意味着:

  • 所有请求(读/写/过期)都会卡住;
  • CPU 飙升;
  • 响应时间暴涨;
  • 严重点可能直接被监控系统判定为宕机重启。

一句话总结:

KEYS 命令在开发环境没问题,但线上执行就是一颗“核弹”。


Redis 官方给出的“安全方案”:SCAN

自 2.8 版本起,Redis 提供了一个安全替代命令 —— SCAN。
它的思路是:渐进式遍历。

SCAN 0 MATCH user:profile:* COUNT 1000

这个命令会做两件事:

  1. 返回部分匹配到的 Key;
  2. 返回一个游标(cursor),下次从这个游标继续扫描。

当游标返回 0 时,表示扫描结束。

也就是说,我们可以用一个循环,不断调用 SCAN,直到把所有匹配 Key 都找完。

✅ 优点

  • 非阻塞,不会影响主线程;
  • 支持模式匹配;
  • 可分批控制速率;
  • 可以边扫描边处理结果。

⚠️ 缺点

  • 扫描是“近似随机”的,不保证顺序;
  • 可能会出现重复 Key(需自行去重);
  • 全库扫描依然耗时较长。

代码实战:安全地找出目标 Key

理论说再多,还不如撸代码来的直接。下面是一段可直接使用的示例代码(使用 redis-py)👇

import redis

# 连接 Redis
r = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True)

cursor = 0
matched_keys = set()  # 使用 set 自动去重

while True:
    # 进行渐进式扫描,每次最多扫描 10000 条
    cursor, keys = r.scan(cursor=cursor, match='user:profile:*', count=10000)

    # 累积结果
    matched_keys.update(keys)

    # 每扫描一批可以打印进度
    print(f"Scanned batch, total found: {len(matched_keys)} keys")

    # 游标返回 0 时表示扫描完成
    if cursor == 0:
        break

print(f"\n✅ 扫描完成,共找到 {len(matched_keys)} 个匹配 key")

💬 运行结果示例:

Scanned batch, total found: 3200 keys
Scanned batch, total found: 7580 keys
...
✅ 扫描完成,共找到 100000 个匹配 key

关键点解释:

  • scan() 是安全的,不会阻塞;
  • count 可以调节扫描速率;
  • 用 set() 去重,防止重复;
  • 可在循环中加入 sleep() 控制速率,防止 CPU 打满。

SCAN 的原理浅析

理解 SCAN 的原理,有助于你判断什么时候它会慢。

  • Redis 内部的 key 空间是一个哈希表;
  • SCAN 通过游标分段遍历哈希槽;
  • 每次返回的数量不固定;
  • 匹配是边扫描边匹配,并非提前过滤。

因此:

  • key 总量越大,SCAN 遍历越久;
  • COUNT 值越大,单次响应时间越长;
  • 若 Redis 实例被大量写入,游标扫描的视图可能略有“漂移”。

这就是为什么说 SCAN 是近似一致性遍历,不是完全快照。


架构层优化思路

如果你频繁需要按前缀查找Key,
那其实问题并不在“怎么找”,而在于“为什么要找”。

这说明数据建模可能需要优化。下面是几种更优雅的方案👇


1️⃣ 为特定前缀维护一个索引集合(Set)

当你写入数据时,同时维护一个索引集合:

SADD user:profile:index 1001
SADD user:profile:index 1002

这样,将来只需:

SMEMBERS user:profile:index

瞬间就能拿到所有对应 Key,无需遍历全库。

💡 优势:

  • 查询O(1),性能极佳;
  • 非阻塞;
  • 可实现分页(ZSET)。

⚠️ 注意:

  • 需要写入时同步维护索引;
  • 索引可能出现脏数据,需定期校验。

2️⃣ 分库 / 分片存储

如果某类 Key 数量特别大,可以按前缀拆分:

  • 不同业务前缀放在不同 Redis 实例;
  • 或者在 Cluster 模式下通过哈希标签定位(如 user:{profile}:id)。

这样每次扫描的范围更小,效率提升明显。

写在最后

很多人对 Redis 的理解,停留在“它很快”。
但在真正的生产环境里,Redis 的快,更像是一把双刃剑。

它让你轻松读写上亿数据,却也可能因为一个命令——
让整个系统陷入停顿。

所以,当你下次再想用 KEYS * 时,请先想一想:

你是在开发环境,还是在生产线上?

成熟的工程师,不是写出最短的命令,而是设计出最稳的系统。

相关文章
|
4月前
|
Windows
Fiddler 4 抓包工具安装教程(fiddler4setup.exe 详细步骤)
Fiddler 4 是 Windows 平台主流 HTTP/HTTPS 抓包调试工具,支持流量捕获、分析与修改。本文详解安装步骤:下载安装包、以管理员运行、按向导完成配置,并指导首次启动及 HTTPS 抓包验证,助你快速上手网络调试。
|
5月前
|
人工智能 前端开发 Shell
一个文件让 AI Coding 效率翻倍:AGENTS.md 实践指南
文章内容基于作者个人技术实践与独立思考,旨在分享经验,仅代表个人观点。
14470 4
一个文件让 AI Coding 效率翻倍:AGENTS.md 实践指南
|
3月前
|
安全 编译器 Go
写了 6 年 Go ,我终于领悟到了泛型的真正威力!
本文深入浅出讲解Go泛型:从重复代码痛点出发,对比空接口缺陷,详解`[T Constraint]`语法、`any`/`comparable`内置约束及自定义类型集合,并通过`Max`、`PrintSlice`、泛型`Stack`等实例演示函数与结构体的泛型应用,助初学者快速掌握类型安全、高效复用的泛型编程。(239字)
286 0
|
3月前
|
存储 JSON 前端开发
从失真到绝对精准:Go语言中如何进行高精度计算?
本文深入剖析Go语言浮点数精度陷阱,揭示`float64`在金额计算中导致“丢一分钱”的根本原因;详解`shopspring/decimal`库如何通过大整数+指数的十进制存储实现金融级精准计算,并提供初始化、运算、序列化等全链路工程实践规范。(239字)
372 0
|
NoSQL Redis 数据库
12)Redis 的游标迭代器(scan)
12)Redis 的游标迭代器(scan)
748 1
|
存储 SQL 关系型数据库
一篇文章搞懂MySQL的分库分表,从拆分场景、目标评估、拆分方案、不停机迁移、一致性补偿等方面详细阐述MySQL数据库的分库分表方案
MySQL如何进行分库分表、数据迁移?从相关概念、使用场景、拆分方式、分表字段选择、数据一致性校验等角度阐述MySQL数据库的分库分表方案。
一篇文章搞懂MySQL的分库分表,从拆分场景、目标评估、拆分方案、不停机迁移、一致性补偿等方面详细阐述MySQL数据库的分库分表方案
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
1908 3
|
SQL Java 数据库连接
自定义HikariCP连接池
自定义HikariCP连接池
2650 0
|
缓存 算法 API
查询域名WHOIS信息免费API接口教程
该API用于查询顶级域名的WHOIS信息,不支持国别域名和中文域名。通过POST或GET请求,需提供用户ID、KEY及待查询域名。返回信息包括域名状态、注册商、时间等详细数据。示例与文档见官网。
1806 5