Redis HSCAN 怎么分页读取大 Hash 而不阻塞服务
来源:17golang原创
时间:2026-09-09 15:37:08 237浏览 收藏
一个 Hash 里有几十万字段时,别用 HGETALL 把整块数据一次性取回。更稳妥的做法是用 HSCAN key cursor COUNT count 做渐进遍历:每次拿到一小批字段和值,保存 Redis 返回的新游标,直到游标回到 0。
官方地址:https://redis.io/docs/latest/commands/hscan/
- HSCAN 是游标遍历,不是按字段名排序的固定页码;COUNT 只是工作量提示。
- 循环必须从游标 0 开始,并以 Redis 返回的下一个游标继续,返回 0 才算本轮结束。
- Hash 在扫描期间发生增删改时,可能出现重复或漏掉变化中的字段,处理逻辑应保持幂等。
先把 HSCAN 的“分页”理解准确
HSCAN 的语法是 HSCAN key cursor [MATCH pattern] [COUNT count] [NOVALUES]。第一次把 cursor 设为 0,Redis 返回一个新的 cursor 和本批结果;下一次把这个新 cursor 原样传回去。返回的 cursor 再次变成 0,才表示这轮迭代完成。
这里的分页只是“分批取数据”,不是 MySQL 那种固定的第 1 页、第 2 页。COUNT 200 不保证每次恰好返回 200 个元素,Redis 会把它当作工作量提示;小 Hash 甚至可能一次就结束,大 Hash 则需要多次请求。官方文档给出的复杂度是单次调用 O(1),完整遍历合计为 O(N),因此它能把大任务拆开,但不会让总工作量消失。

用 cursor 循环读取,而不是反复从 0 开始
下面这段命令适合先观察一个大 Hash 的遍历形态。每次执行后,把响应第一项的游标保存下来,再继续下一次。
# 从 0 开始,让 Redis 返回第一批字段和值 redis-cli HSCAN user:profile:all 0 COUNT 200 # 把上一次响应中的新游标继续传回;不要把它误当成页码 2 redis-cli HSCAN user:profile:all 7312 COUNT 200 # 当响应第一项变成 0,才结束本轮遍历 redis-cli HSCAN user:profile:all 0 COUNT 200
示例里的 7312 只是示意值,真实游标必须使用上一次 Redis 响应返回的值。生产代码通常维护 cursor 变量,并在每批处理完成后再保存进度;如果任务需要跨进程恢复,可把 key、cursor、扫描条件和业务批次号一起持久化。
用 COUNT、MATCH 和预算控制单批压力
COUNT 适合调节单次网络响应和应用处理时间,但不要把它当成精确限流器。每批真正的成本还包括反序列化、业务过滤、下游写入和日志输出。可以先从较小值开始,根据单批耗时逐步调大。
如果只需要一部分字段,可以加入 MATCH;如果只关心字段名,Redis 支持 NOVALUES,可以减少返回内容。注意 MATCH 是扫描结果过滤,不等价于对 Hash 建立索引;字段很多但匹配项很少时,仍可能需要走完整个游标空间。
| 需求 | 写法 | 需要留意 |
|---|---|---|
| 字段和值都要 | HSCAN key cursor COUNT 200 | 每批数量不固定 |
| 只查部分字段 | HSCAN key cursor MATCH user:* COUNT 200 | 过滤不等于索引 |
| 只要字段名 | HSCAN key cursor COUNT 200 NOVALUES | 客户端需支持该选项 |

数据变化时,怎样避免把扫描结果当成快照
HSCAN 适合渐进读取,不承诺给你一个冻结的 Hash 快照。扫描过程中字段可能被修改、删除或新增,因此同一个字段可能再次出现,某些变化也可能不在本轮结果里。不要在业务层按“第几页”做严格补偿,也不要因为看到重复字段就简单判定 Redis 出错。
更可靠的处理方式是让每个字段的消费动作幂等:以字段名和业务版本作为去重键,重复到达时覆盖同一结果;对必须完整导出的任务,可以先暂停写入,或给 Hash 建立导出版本,把扫描结果写入临时集合后再切换。扫描中断时保存 cursor 可以减少重头开始的成本,但恢复后仍要接受边界重复,最好从上一个已确认批次重新回放。
上线前至少记录三个指标:单批耗时、返回元素数和完整扫描轮次。若单批耗时超过业务预算,先降低 COUNT 或缩短每批的业务处理;若反复扫描仍没有回到 0,检查是否错误地把旧 cursor、字符串空值或多个消费者的 cursor 混用了。
常见问题
HSCAN 能保证每页固定 100 条吗?
不能。COUNT 只是提示,实际返回数量会随编码和数据状态变化。需要固定业务批次时,应在应用侧累积到预算后提交,而不是依赖 Redis 恰好返回固定条数。
扫描到一半删除字段会怎样?
本轮结果可能看不到被删除字段,也可能已经拿到它的旧值。导出或同步逻辑要有版本、时间点或幂等策略,不能把一次 HSCAN 当作事务快照。
什么时候可以直接用 HGETALL?
当 Hash 规模明确较小、响应体和应用内存都在预算内时,HGETALL 更简单。规模会增长或请求路径不能被大响应拖慢时,再采用 HSCAN。
-
398 收藏
-
117 收藏
-
426 收藏
-
298 收藏
-
171 收藏
-
273 收藏
-
216 收藏
-
186 收藏
-
441 收藏
-
469 收藏
-
259 收藏
-
131 收藏
-
448 收藏
-
300 收藏
-
269 收藏
-
191 收藏
-
378 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习