首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Redis SCAN 巡检的正确边界:空批次、重复结果与集群扫描范围

Redis SCAN 巡检的正确边界:空批次、重复结果与集群扫描范围

原创
作者头像
一只特立独行的兔先生
发布于 2026-10-21 18:30:36
发布于 2026-10-21 18:30:36
250
举报

结论:扫描结束,不等于拿到了某一时刻的全量快照

用 Redis 做缓存巡检时,常见错误不是不会调用 SCAN,而是把它当成分页查询:空列表代表结束,COUNT 100 代表每页最多 100 个,遍历数量代表准确库存。这三个判断都不成立。

可靠的只读巡检应先确认扫描范围,再把返回游标为 0 作为正常结束条件;接受重复结果,对执行时间设置预算,并把异常中断与正常完成分开记录。本文给出单机或已确认支持全局扫描的代理入口示例,不执行删除操作。

1. 先确认入口到底覆盖哪些数据

原生 Redis Cluster 的单节点 SCAN 不是全局遍历,不能因为客户端支持重定向,就默认扫描覆盖所有主节点。

腾讯云《集群架构全局 SCAN 使用指南》说明:分布式缓存数据库的集群架构通过 Proxy 扩展扫描能力,Proxy 5.8.9 及以上支持不限定节点的全局扫描。这个版本要求不能直接套用到所有 Redis 服务、直连节点或其他代理。

开始前应记录实例架构、连接入口、Proxy 版本和所选数据库。若不能确认覆盖范围,就先停止“全量巡检”的结论输出;不要拿一个节点的数据量解释整套集群。

2. 三个必须保留的语义

  • 空批次不是结束。 使用 MATCH 后,本批次可能没有匹配项,但游标仍非零,必须继续。
  • COUNT 是工作量提示,不是返回数量上限。 不能据此分配固定长度数组,也不能把它当作严格限流器。
  • SCAN 不是快照。 完整遍历会返回从开始到结束持续存在的元素,但过程中新增、删除的元素是否出现没有统一保证,同一元素也可能返回多次。

因此,累计返回次数只能命名为“观测次数”。将所有 Key 放进客户端集合虽然能去重,却消耗与 Key 数量相关的内存,依然无法把动态扫描变成时点快照。

另外,MATCH 不是前缀索引。匹配结果很少,不代表服务端只做了很少工作。

3. 一个有边界的只读扫描脚本

以下示例使用 Python 和 redis-py。先在隔离测试实例安装依赖,再通过环境变量注入连接地址;地址可能包含凭证,不能写入仓库或日志。TLS、账号权限和网络连通性按实例要求配置,不要为了跑脚本开放公网访问。

代码语言:bash
复制
python -m pip install redis
python scan_audit.py

将下面代码保存为 scan_audit.py,运行前设置 REDIS_URL。SCAN_PATTERN 默认只匹配演练前缀,避免一开始就输出业务 Key。

代码语言:python
复制
import os
import time
import redis

client = redis.Redis.from_url(
    os.environ["REDIS_URL"],
    decode_responses=False,
    socket_connect_timeout=3,
    socket_timeout=3,
)
pattern = os.environ.get("SCAN_PATTERN", "audit-demo:*")
deadline = time.monotonic() + 60
cursor = 0
batches = 0
observations = 0
completed = False

try:
    while True:
        if time.monotonic() >= deadline:
            raise TimeoutError("scan budget exceeded")
        cursor, keys = client.scan(
            cursor=cursor, match=pattern, count=100
        )
        batches += 1
        observations += len(keys)
        # 这里只统计观测次数,不读取 Value,也不输出 Key。
        if cursor == 0:
            completed = True
            break
        time.sleep(0.02)
finally:
    client.close()
    print({
        "completed": completed,
        "batches": batches,
        "observations": observations,
    })

代码刻意不捕获异常后继续成功路径:连接失败、权限不足或游标失效都会让进程报错,并留下 completed=False。60 秒是循环检查的执行预算,不是硬实时截止;正在执行的请求及客户端重试可能让实际耗时超过预算。

这里的 COUNT 100 和每批间隔只是保守演练起点,不是生产推荐配额。上线时观察业务延迟与实例负载,再调整工作量;“只读”并不等于没有资源成本。

4. 如何验证,不靠猜测返回顺序

在没有并发写入的隔离实例中准备三个 Key:

代码语言:bash
复制
redis-cli -u "$REDIS_URL" SET audit-demo:1 a
redis-cli -u "$REDIS_URL" SET audit-demo:2 b
redis-cli -u "$REDIS_URL" SET unrelated:1 c

运行脚本,正常完成应看到 completed=True。如果要核对具体集合,可以在这个小规模演练中临时收集返回 Key,并断言去重后恰好为两个目标 Key;不要把该集合方案直接复制到百万级生产巡检。

验证循环边界时,还需要覆盖“游标非零、结果为空”的响应,以及最后一批“游标为零、仍有结果”的响应。前者必须继续,后者必须先处理结果再退出。不要依赖一次真实扫描恰巧产生这两种情况,也不要把某个游标数值写死成预期分页序号。

5. 游标失效与扫描后的写操作要分开设计

腾讯云文档提示,主从切换或缩容可能触发 invalid cursor(master node idx out of range)。此时不能忽略错误后继续使用旧游标。

本文脚本选择直接失败。若业务要求自动恢复,可以在外层设置有限重启次数,每次从 0 开始,并单独记录该轮统计;不能把失败轮与成功轮的观测次数相加后称为唯一 Key 数。

如果后续要基于扫描结果清理缓存,还要解决另一个竞态:扫描读到旧 Key 后,业务可能已经用同名 Key 写入新数据。直接删除名称并不一定安全。应采用版本化命名或原子条件校验等业务协议,而不是把“重复删除通常无害”当作并发安全证明。

最终验收标准应写清楚:入口范围已确认、游标正常归零、异常未被吞掉、指标名称不冒充快照统计。做到这些,巡检结果才有可解释的边界。

参考文档

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 结论:扫描结束,不等于拿到了某一时刻的全量快照
  • 1. 先确认入口到底覆盖哪些数据
  • 2. 三个必须保留的语义
  • 3. 一个有边界的只读扫描脚本
  • 4. 如何验证,不靠猜测返回顺序
  • 5. 游标失效与扫描后的写操作要分开设计
  • 参考文档
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档