登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

Redis SCAN 游标分页的重复处理与幂等遍历方案

来源:17golang原创

时间:2026-10-17 10:28:48 113浏览 收藏

Redis SCAN 不是“每次固定返回一页”的分页接口:某一轮返回空数组不代表结束,重复 key 也不一定是 Redis 出错。可靠的做法是让游标决定是否结束,再让业务处理具备去重或幂等能力。

官方地址:https://redis.io/docs/latest/commands/scan/

要点速览
  • 从游标 0 开始,始终使用上一次返回的游标;只有返回 0 才结束。
  • MATCH、TYPE 和 COUNT 改变筛选或工作提示,不能当作稳定分页协议。
  • 重复结果要么由本次任务的 seen 集合过滤,要么交给可重复执行的副作用收敛。

先把游标和返回集合分开理解

SCAN cursor [MATCH pattern] [COUNT count] [TYPE type] 每次返回两个值:新的 cursor 和本次拿到的 key。第一次传 0,随后把服务端返回的 cursor 原样传回;当 cursor 再次变为 0,才完成一次 full iteration。Redis 官方还明确说明,单次返回数量没有固定保证,甚至可能返回空集合,所以不能写成“keys 为空就 break”。

Redis SCAN 游标、过滤参数与返回 key 集合的静态关系说明图
图1:SCAN 的游标、过滤参数和返回 key 集合关系说明图,不是运行截图。

以 COUNT 100 为例,它只是告诉 Redis 每轮大致投入多少扫描工作,并不承诺返回 100 个 key。MATCH 过滤发生在取出元素之后,匹配很少时连续出现空页是正常现象;此时仍要继续推进游标。

重复结果要在业务层收敛

SCAN 只保存一个游标,集合在遍历过程中又可能扩缩容,因此同一个元素可能被返回多次。小规模、需要一次性汇总的任务可以把 seen 集合放在整次遍历的生命周期内;但 key 数量很大时,内存去重本身也会变成压力。清理、刷新索引、补写标记这类操作,更适合设计成重复执行不会改变最终结果的幂等动作。

// ScanAndApply 按游标遍历 key,并在本次任务内过滤重复结果。
func ScanAndApply(ctx context.Context, rdb *redis.Client, match string, apply func(string) error) error {
    cursor := uint64(0)
    seen := make(map[string]struct{}) // 小批量任务使用;超大任务应改用幂等副作用

    for {
        keys, next, err := rdb.Scan(ctx, cursor, match, 100).Result()
        if err != nil {
            return fmt.Errorf("scan cursor %d: %w", cursor, err) // 保留游标,便于日志定位
        }
        for _, key := range keys {
            if _, exists := seen[key]; exists {
                continue // 重复返回不再次执行非幂等动作
            }
            seen[key] = struct{}{}
            if err := apply(key); err != nil {
                return fmt.Errorf("apply key %q: %w", key, err) // 业务失败立即交给上层重试
            }
        }
        cursor = next
        if cursor == 0 {
            return nil // 空页也走到这里,只有游标归零才完成
        }
    }
}

这段模板适合有限规模的一次性任务。若任务要覆盖数百万 key,不要盲目扩大 seen;可以把处理记录写入带任务 ID 的 Redis Set 或数据库唯一键,让重复调用自然收敛,同时保留失败重试能力。

Redis SCAN 重复结果、去重集合与幂等业务副作用的边界说明图
图2:重复结果在去重层与幂等业务层之间的边界说明图,不是运行截图。

参数选择与数据变化期间的边界

参数或现象正确理解处理建议
cursor服务端返回的下一位置原样传递,直到变为 0
COUNT每轮工作量提示按延迟和负载调节,不当页大小
MATCH返回前过滤允许空页,不能提前结束
遍历中增删 key结果可能重复、遗漏或不稳定缩短单轮时间,副作用保持幂等

如果必须生成严格一致的快照,SCAN 不是这个协议;它的价值是把大集合拆成小步读取,减少长时间阻塞。生产任务还应记录任务 ID、最近一次 cursor、已处理数量和最后一个错误,失败后从头重跑时依靠幂等设计兜底,而不是把 cursor 当成永久断点。

常见问题

SCAN 返回空数组是不是扫描结束?

不是。只有返回的 cursor 等于 0 才结束;MATCH 稀疏或 COUNT 较小时出现空页很常见。

COUNT 设置成 100 是否一定返回 100 个 key?

不一定。COUNT 是工作量提示,返回数量可能更少、更多或为零,调用方必须继续使用返回游标。

重复 key 应该全部放进 seen 集合吗?

小批量汇总可以这样做;大批量任务更应让删除、写标记或刷新动作可重复执行,再用任务记录或唯一约束控制最终状态。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>