Redis Cluster 单个 GET 正常,MGET 却报 CROSSSLOT
多键操作要求同一个哈希槽;区分同节点与同槽,按槽拆分已有键或为需要原子操作的业务设计 hash tag。
单键 GET 能读取数据,批量 MGET 却报 CROSSSLOT Keys in request don't hash to the same slot,在 Redis Cluster 中先比较这些键的槽号。换成支持集群的客户端,并不意味着它会自动拆分每一种跨槽命令。
Redis Cluster 的普通多键命令要求相关键属于同一个 hash slot。键落在同一台主节点上仍不够:一个节点可以负责许多槽。下面讨论原生 Redis Cluster;代理服务或托管产品如果提供跨槽扩展,应以对应能力为准。
先核对应用实际发出的键
在允许执行管理查询的集群连接中,可以检查具体键的槽号。下面是只读诊断命令示例,主机、端口和键名都应替换;已有认证、TLS 和证书配置应继续保留:
redis-cli -h redis.example.net -p 6379 CLUSTER KEYSLOT 'order:42:detail'
redis-cli -h redis.example.net -p 6379 CLUSTER KEYSLOT 'order:42:status'
键可以尚不存在,CLUSTER KEYSLOT 仍能计算槽号。重点是比较客户端序列化后真正发送的键,而不只是程序里的变量名:客户端可能添加前缀、租户名称或其他编码。
槽号不同,整条普通 MGET 就不能作为一个跨槽请求发送。槽号相同仍失败时,检查日志中的完整键列表,尤其是封装库偷偷加入的键;同时区分 MOVED、ASK、TRYAGAIN,这些重定向或迁移状态并不是同一个报错。
只是批量读取时,可以按槽拆分
如果目标是读取一批彼此独立的键,应用可以逐键 GET,或者先按槽分组,每组单独发 MGET。应使用集群客户端负责槽路由和重定向;不要把所有组都固定发到一台节点。
StackExchange.Redis 可以通过现有连接的 ConnectionMultiplexer.HashSlot 在客户端计算槽。下面是分组思路示例,不包含连接创建与认证配置:
var groups = keys.GroupBy(key => multiplexer.HashSlot(key));
foreach (var group in groups)
{
RedisKey[] groupKeys = group.ToArray();
RedisValue[] values = await database.StringGetAsync(groupKeys);
// 按 groupKeys 与 values 的对应位置保存结果。
}
keys、multiplexer 和 database 应来自同一个实际集群与一致的键编码。重新合并结果时保留原请求顺序、重复键和缺失值,不能把分组后的顺序直接当成原数组顺序。
多组请求不再具有单条 MGET 的原子读取语义;不同组可能看到不同时间点的数据。若业务需要一组键共同参与事务或 Lua 脚本,拆分请求不能保留原来的原子性,应调整数据设计。
需要共同操作的键,用业务范围限定 hash tag
可以在相关键名中放入相同、非空的 {...} 部分,使它们按这部分内容计算槽。例如以下是键命名示例:
order:{42}:detail
order:{42}:status
这两个键按 42 计算槽。标签需要包含足够的业务区分,如完整的租户与订单身份;不要为了消除报错把全部业务键统一放进一个固定标签,否则会集中到一个槽,削弱分片分布。
改键名不是原地调整槽号,而是改变应用访问的键。已有数据不会自动出现在新名字下;上线前须安排新旧键兼容、值与 TTL 保留、回退读取方式和逐步迁移。不能在没有迁移方案时仅修改写入端。
空标签也不能用于分组,例如 foo{}{bar} 会按整个键计算槽;多个花括号的具体匹配规则也会影响结果。用实际键重复检查 CLUSTER KEYSLOT,比肉眼认为前缀相同更可靠。
修复后,应能按预期方式读取整批键,缺失值和顺序保持原业务约定。需要原子操作的一组键必须确实同槽;在重分片期间出现 TRYAGAIN 时,客户端仍需按集群迁移规则处理,不应继续改标签来掩盖迁移状态。


