Redis Cluster要求多key命令的所有key必须落在同一slot,因CRC16(key)%16384计算slot,不同slot的key无法原子执行;用{}包裹相同标识符(如{1001})可强制同slot。

为什么CROSSSLOT Keys in request don't hash to the same slot会报错
Redis Cluster 把 16384 个 slot 分配到不同节点,每个 key 通过 CRC16(key) % 16384 算出所属 slot。只要一个命令里涉及多个 key(比如 DEL key1 key2、SUNIONSTORE dst k1 k2、EVAL 脚本里访问多个 key),所有 key 必须落在同一个 slot —— 否则直接拒绝执行,不走网络协调,这是设计使然,不是 bug。
常见触发场景包括:
- Laravel Horizon 默认用
queues:default和queues:default:delayed等多个 key,没加 hashtag 就直接ZRANGE/ZREM - Hyperf 或 Swoole 项目里手动调
BRPOPLPUSH或RENAME操作两个无关联前缀的 key - 用
redis-cli --cluster call批量执行时,传入的 key 没统一 slot
用 {} 写 Hash Tag 是最直接的修复方式
Redis 只认第一个 {} 对里的内容参与 slot 计算,其余部分被忽略。例如:
SET user:{1001}:profile "Alice"
SET user:{1001}:settings "dark"
DEL user:{1001}:profile user:{1001}:settings
上面三个 key 都只取 1001 计算 slot,必然落在同一节点、同一 slot。关键规则有:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
{}必须成对出现,且只取第一个有效对;user:{1001}:{v2}:token和user:{1001}:meta效果一样 -
{}内不能为空,user:{}:profile会被当作整个 key 计算 slot - 业务上要选稳定、有区分度的字段做 tag,比如用户 ID、订单号、租户 code,避免全用
{common}导致数据倾斜 - 框架自动拼接的 key(如 Laravel 的
queues:{default})必须在配置里显式加上{},例如REDIS_QUEUE={default}
哪些命令必须用 Hash Tag,哪些可以绕过
不是所有多 key 命令都强制要求同 slot —— 单 key 命令永远安全;而真正受限的是那些原子性依赖服务端一次执行的命令:
- 必须加 Hash Tag 的:
DEL、RENAMENX、SUNIONSTORE、SDIFF、BRPOPLPUSH、带多个 key 参数的EVAL/EVALSHA - 可客户端合并替代的:比如
SUNION本身不写数据,可用SMEMBERS分别取再 Go/PHP 里合并;MGET在集群中是允许跨 slot 的(客户端自动分发请求) - 完全不可用的:
KEYS、SCAN(集群模式下禁用),FLUSHDB(只能 flush 本节点)
注意:MIGRATE 是个特例——它本身不违反 slot 规则,但需要你先确认源 key 和目标 key 是否同 slot,否则迁移后 rename 还是会报错。
验证和调试时别漏掉这些细节
加了 {} 不代表万事大吉,实际部署中容易卡在这几个点:
- 环境变量或配置文件里写的
{queue}被 Shell 或 YAML 解析器提前展开(比如 Laravel 的.env里写成REDIS_QUEUE={default},YAML 中要用引号包裹:REDIS_QUEUE: "{default}") - 用
redis-cli手动测试时,忘了在 key 上加{},误以为代码逻辑有问题 - Hyperf 或其他框架封装了 Redis 客户端,但没透传原始 key,导致你在代码里写了
{1001},实际发出去的还是user:1001:profile - 用
CLUSTER KEYSLOT查 slot 时,输入的 key 必须和代码里完全一致(含大小写、空格、{}),否则查的不是同一个东西
最稳妥的验证路径是:写一个最小 key 示例 → redis-cli -c cluster keyslot "user:{1001}:x" → 再试 DEL → 成功后再套进业务逻辑。跨 slot 错误不报连接或权限问题,只报 slot 不匹配,定位起来其实很干净,就怕在 key 构造环节悄悄丢掉了 {}。

















