eval报crossslot错误是因为redis集群要求脚本中所有keys必须落在同一slot,而单机redis不校验;唯一可靠解法是用{}哈希标签确保所有key的标签内容完全一致。

为什么EVAL会报CROSSSLOT Keys in request don't hash to the same slot
因为Redis集群不允许脚本访问不同slot的key——这不是Lua写错了,是KEYS数组里传进去的key压根没落在同一个节点上。集群必须把整个脚本发给单一节点执行,而它连该发给谁都不知道,只能拒绝。
错误信息长这样:(error) CROSSSLOT Keys in request don't hash to the same slot 或 ERR 'EVAL' command keys must in same slot。本地单机Redis完全不校验这个,所以开发时测得通,一上集群就崩。
注意:EVALSHA也一样受限,它只是少传脚本体,slot校验逻辑和EVAL完全一致,不是“绕过”方案。
怎么让脚本里的多个KEYS落到同一slot
唯一可靠办法:用{}哈希标签,且所有key的{}内字符串必须完全一致。
-
user:{1001}:name、order:{1001}:items、cart:{1001}:pending→ 都只对1001做CRC16,必然同slot -
user:1001:name、order:1001:items→ 整个字符串参与哈希,几乎必然跨slot -
user:{abc{def}:xyz→ 只取def算slot(嵌套只认最内层第一对) -
user:{}:name→{}为空,退化为对整个key哈希,失去控制力
业务上要批量读用户资料+订单+购物车,就得提前约定好这个tag内容,比如统一用用户ID,不能临时拼接或大小写不一致。
Hyperf里调用EVAL时容易踩的坑
Hyperf默认的Redis客户端不支持集群路由,即使你配了多个节点地址,$redis->eval()也会随机发往某个节点,大概率触发CROSSSLOT。
必须手动注册RedisCluster实例:
Redis 8.2.3 是一款安全优先的高性能键值存储系统。该版本紧急修复了可能引发远程代码执行(RCE)的高危漏洞(CVE-2025-62507),并解决了 HyperLogLog 及 Cuckoo Filter 等数据结构在特定场景下的崩溃问题。建议所有用户立即升级,以保障生产环境的系统稳定与数据安全。
- 确认PHP已启用
phpredis扩展(php -m | grep redis有输出) - 在
config/autoload/redis.php中定义'cluster_nodes' => ['127.0.0.1:7000', ...] - 写一个
RedisClusterFactory,create()里用new RedisCluster(NULL, $nodes)初始化 - 注入这个工厂产出的实例,而不是
Redis对象
Redis::eval()或$redis->eval()这种写法在集群下本质无效——框架不会帮你拆脚本、重试或改key。
脚本里KEYS和ARGV怎么组织才安全
Lua脚本本身不解决slot问题,它只是执行容器;真正起作用的是你传进去的KEYS数组——它们必须已满足同slot条件。
示例(安全):
$redisCluster->eval("return {redis.call('get', KEYS[1]), redis.call('get', KEYS[2])}", 2, '{user:1001}:profile', '{user:1001}:settings');
示例(危险):
$redisCluster->eval("return {redis.call('get', KEYS[1]), redis.call('get', KEYS[2])}", 2, 'user:1001:profile', 'user:1001:settings');
关键点:tag必须显式写在业务代码里,redis.prefix配置对RedisCluster完全无效,Hyperf也不会自动加{}——这事没法外包给框架。
最容易被忽略的是:当key来自DTO、缓存装饰器、中间件等间接路径时,{}很容易在某一层被漏掉或错拼,建议在关键路径加日志打出来看实际key名。










