帮你快速理解、总结文档立即下载

集群架构全局 SCAN 使用指南

最近更新时间:2026-08-12 15:08:45
我的收藏
本文介绍在云分布式缓存数据库(兼容 Redis)集群架构下,如何使用扩展的 SCAN 命令遍历集群数据,并处理游标失效场景,避免扫描进入死循环。

业务场景

在批量清理过期 Key、统计 Key 分布、数据巡检或迁移前核对等运维场景中,业务需要遍历实例中的全部 Key。原生 Redis Cluster 的数据分散在多个分片节点上,不支持全局 SCAN,无法直接跨节点扫描,给上述运维操作带来不便。云分布式缓存数据库集群架构通过 Proxy 架构扩展了 SCAN 能力,支持在单条命令中完成跨分片的遍历,简化了集群下的全量 Key 遍历操作。

背景信息

云分布式缓存数据库集群架构通过 Proxy 架构扩展了 SCAN 命令,支持两种扫描模式:
定向扫描: 在命令末尾添加 NODEID 参数,仅针对指定分片节点扫描。
全局扫描: 从 Proxy 5.8.9版本开始,支持无节点限制的全局 SCAN,可完整遍历集群所有分片的数据。

前提条件

已创建云分布式缓存数据库集群架构实例,并获取实例的连接地址与访问密码。
实例的 Proxy 版本不低于5.8.9(全局扫描能力所需)。
已完成客户端连接配置,连接方式可参见 Jedis 连接 Redis

使用限制

说明:
当集群发生节点切换(主从角色变更)分片缩减(集群缩容)时,可能小概率触发 SCAN 游标失效,导致游标对应的分片索引与实际节点映射关系断裂,报错如下:-ERR invalid cursor(master node idx out of range)
若忽略该错误继续迭代,不仅无法获取有效数据,还会因游标与分片逻辑不同步而进入死循环,持续消耗系统资源。因此扫描逻辑必须对游标失效做异常处理。

操作步骤

步骤1:定向扫描指定分片

在 SCAN 命令末尾追加 NODEID 参数,可只扫描指定分片节点的数据,适用于按分片排查或分片级处理的场景。
使用示例:当集群中某个分片出现热点 Key 导致延迟升高时,可通过定向扫描该分片快速定位大 Key 或异常 Key 的分布情况,无需遍历全集群数据。假设该分片的节点 ID 为 f2f3c387b9fab0e67af02039845c60278b13bed0,执行如下命令:
scan 0 MATCH * COUNT 100 f2f3c387b9fab0e67af02039****************
节点 ID 为40位十六进制字符串,可通过控制台「节点管理」页面查看,或执行 cluster nodes 命令获取。

步骤2:执行全局扫描

不带 NODEID 参数时,SCAN 将遍历集群所有分片的数据(需 Proxy 5.8.9及以上)。从游标 0 开始迭代,直到游标重新返回 0 表示遍历完成。

步骤3:处理游标失效并安全遍历

为规避游标失效导致的死循环,扫描逻辑应捕获异常,将游标重置为起始位0并重启扫描流程,同时设置最大重试次数。完整参考代码(Java + Jedis)如下:
package com.example.service.impl;

public class RedisServiceImpl implements RedisService {
// scan 最大重试次数
private static final int MAX_RETRIES = 3;
private final RedisConnectionFactory connectionFactory;

@Override
public void scanKeys(String pattern, int count) {
try (Jedis jedis = connectionFactory.getConnection()) {
int retryCount = 0;
boolean scanCompleted = false;
// 对 scan 过程报错进行捕获,遇到报错从 0 开始重新扫描
while (!scanCompleted && retryCount <= MAX_RETRIES) {
String cursor = ScanParams.SCAN_POINTER_START;
ScanParams scanParams = new ScanParams();
scanParams.count(count);
scanParams.match(pattern);
try {
while (true) {
ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
List<String> keys = scanResult.getResult();
if (!keys.isEmpty()) {
processKeysWithBusinessLogic(keys); // 业务处理
}
cursor = scanResult.getCursor();
// 游标为 "0" 时完成扫描
if (cursor.equals(ScanParams.SCAN_POINTER_START)) {
scanCompleted = true;
break;
}
}
} catch (Exception e) {
retryCount++;
// 超过最大重试次数,终止重试
if (retryCount > MAX_RETRIES) {
throw new RuntimeException("达到最大重试次数,扫描失败", e);
}
}
}
}
}
}
关键参数与逻辑说明如下:
参数 / 逻辑
说明
MAX_RETRIES
SCAN 最大重试次数,示例为3,可按业务需要调整。
cursor
游标,每轮扫描从起始位 0ScanParams.SCAN_POINTER_START)开始,游标重新返回 0 表示遍历完成。
scanParams.match(pattern)
按模式匹配 Key。
scanParams.count(count)
每次迭代返回的元素数量提示。
异常捕获
捕获到游标失效等异常后,重试计数加1并从游标 0 重新扫描;超过 MAX_RETRIES 则抛出异常终止,避免死循环。

验证方法

观察扫描过程:游标最终重新返回 0scanCompletedtrue,表示已完整遍历,未陷入死循环。
统计扫描结果:记录处理的总批次数、总 Key 数量与耗时,与预期数据规模核对,确认遍历完整。

常见问题

Q:扫描时报错 invalid cursor(master node idx out of range) 怎么办?
该报错通常由集群节点切换或缩容导致游标失效引起。应捕获异常后将游标重置为 0 并重新发起扫描,切勿忽略错误继续用原游标迭代,否则会进入死循环。
Q:如何避免全局 SCAN 陷入死循环?
必须对扫描过程做异常处理,并设置最大重试次数:捕获到游标异常时从 0 重启扫描,超过重试上限则终止并抛出异常。
Q:只想扫描某个分片的数据怎么做?
使用定向扫描,在 SCAN 命令末尾追加 NODEID 参数即可,具体命令写法以实例实际支持为准。