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 * 时,请先想一想:

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

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

相关文章
|
5月前
|
Windows
Fiddler 4 抓包工具安装教程(fiddler4setup.exe 详细步骤)
Fiddler 4 是 Windows 平台主流 HTTP/HTTPS 抓包调试工具,支持流量捕获、分析与修改。本文详解安装步骤:下载安装包、以管理员运行、按向导完成配置,并指导首次启动及 HTTPS 抓包验证,助你快速上手网络调试。
|
6月前
|
人工智能 前端开发 Shell
一个文件让 AI Coding 效率翻倍:AGENTS.md 实践指南
文章内容基于作者个人技术实践与独立思考,旨在分享经验,仅代表个人观点。
16317 4
一个文件让 AI Coding 效率翻倍:AGENTS.md 实践指南
|
5月前
|
Go 开发者
你知道 Go 结构体和结构体指针调用的区别吗?一文带你彻底搞懂!
本文深入解析Go中结构体方法接收器的选择难题:值接收器操作副本,无法修改原值;指针接收器直接操作原始数据。核心选择原则——需修改必用指针,大结构体优先指针,小结构体/无修改场景推荐值接收器,并兼顾接口实现与方法集一致性。(239字)
223 0
|
5月前
|
中间件 关系型数据库 PHP
面试官问“装饰器模式”,这样回答薪资多要 3000!
本文用“奶茶加料”生动讲解PHP装饰器模式:当继承导致类爆炸时,改用组合——像包洋葱一样层层装饰对象。通过接口、基础类、抽象装饰器和具体装饰器四步实现,动态叠加功能,零修改扩展代码,完美践行开闭原则。(239字)
164 0
|
存储 SQL 关系型数据库
一篇文章搞懂MySQL的分库分表,从拆分场景、目标评估、拆分方案、不停机迁移、一致性补偿等方面详细阐述MySQL数据库的分库分表方案
MySQL如何进行分库分表、数据迁移?从相关概念、使用场景、拆分方式、分表字段选择、数据一致性校验等角度阐述MySQL数据库的分库分表方案。
一篇文章搞懂MySQL的分库分表,从拆分场景、目标评估、拆分方案、不停机迁移、一致性补偿等方面详细阐述MySQL数据库的分库分表方案
|
NoSQL Redis 数据库
12)Redis 的游标迭代器(scan)
12)Redis 的游标迭代器(scan)
760 1
|
SQL Java 数据库连接
自定义HikariCP连接池
自定义HikariCP连接池
2714 0
|
缓存 算法 API
查询域名WHOIS信息免费API接口教程
该API用于查询顶级域名的WHOIS信息,不支持国别域名和中文域名。通过POST或GET请求,需提供用户ID、KEY及待查询域名。返回信息包括域名状态、注册商、时间等详细数据。示例与文档见官网。
1876 5
|
Kubernetes 监控 调度
k8s学习--kubernetes服务自动伸缩之垂直伸缩(资源伸缩)VPA详细解释与安装
k8s学习--kubernetes服务自动伸缩之垂直伸缩(资源伸缩)VPA详细解释与安装
1226 2

热门文章

最新文章