重定向循环源于客户端未设重试上限且混淆MOVED/ASK语义;应设max_redirects为3~5(如redis-py、ioredis、lettuce、go-redis均支持),仅MOVED计数、ASK需发ASKING且不更新拓扑,避免盲目刷新和竞态导致的路由分裂。

重定向循环不是集群问题,是客户端没设重试上限+混淆MOVED/ASK语义导致的。只要控制好重定向次数、区分两类响应、不盲目刷新拓扑,99%的循环都能避免。
max_redirects必须显式设为3~5
默认值(如redis-py的16、ioredis的16)在拓扑临时错乱时极易触发死循环。设太小(如1)又会误杀正常迁移——因为一次槽迁移常伴随2次MOVED响应(源节点返回一次,目标节点接管未稳再返回一次)。
-
redis-py:初始化RedisCluster时传max_redirections=3 -
ioredis:构造选项里加{ maxRedirections: 3 } -
lettuce:用ClusterClientOptions.builder().maxRedirects(3) -
go-redis:ClusterOptions.MaxRedirects = 3
关键点:这个计数器只对MOVED生效,ASK重试不计入——所以设3不会阻断正在迁移的槽。
收到ASK响应必须发ASKING且不更新本地槽映射
常见错误是把ASK 1234 10.0.1.6:6379当MOVED处理:直接更新slot 1234 → node 10.0.1.6,后续所有请求都发错节点。ASK本质是“临时借道”,目标节点默认拒绝非ASKING前缀的请求。
- 解析响应时严格匹配前缀:
if strings.HasPrefix(err.Error(), "ASK "),不能用strings.Contains模糊搜 - 重试前必须先发
ASKING命令(无参数),再发原命令 - 绝对不要修改本地
slots → node缓存——迁移完成前,该槽仍属原节点
漏发ASKING会导致目标节点返回MOVED或ERR,形成二次重定向。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
拓扑刷新不能在重定向路径中同步执行
客户端收到MOVED后,正确做法是:用已知拓扑快速重试一次;仅当连续失败(如2次)才触发CLUSTER SLOTS刷新。否则,刷新请求本身可能因节点状态未稳而返回新MOVED,嵌套跳转。
- 禁用
redis-py的reinitialize_steps=1(强制每步刷拓扑) - 生产环境关闭
log_redirections=True,避免日志掩盖真实重试链路 - 拓扑缓存应带过期时间(建议≤5分钟),但扩缩容后需主动触发刷新,不能只靠超时
拓扑数据本身有延迟——CLUSTER SLOTS返回的是快照,不保证与当前路由状态实时一致。
多线程共享槽映射表必须加读写锁
多个goroutine或线程同时读写slot 1234 → node A/B映射,会因竞态导致部分请求发往旧节点、部分发往新节点,造成路由分裂和数据不一致。
- Java用
ConcurrentHashMap或ReentrantReadWriteLock - Go用
sync.RWMutex保护映射结构体 - Python用
threading.RLock,别用普通Lock阻塞读操作
更稳妥的做法是采用双版本映射:一个active供读取,一个shadow供更新,切换时原子替换——这样即使刷新出错,也能回退到上一版拓扑。

















