Redis集群不支持跨槽事务,但通过哈希标签将相关键集中到同一槽位,并利用Lua脚本可保证强一致性原子操作;跨槽场景下,可采用Saga补偿机制或异步队列,实现最终一致性。同一槽内提供强一致性,跨槽提供最终一致性,满足不同场景需求。
如果你正在使用 Redis Cluster,大概率遇到过这类场景:
- 想扣用户余额,同时创建订单、记录日志;
- 写了 MULTI/EXEC,结果 Redis 直接报错:key 不在同一个 slot;
- 换成 Pipeline?又怕中间被其他请求插队,导致状态不一致……
其实,Redis Cluster 虽然限制了跨 slot 的原子操作,但通过合理设计,完全能够实现安全、可靠、高性能的多 Key 操作。
CRC16(key) % 16384 映射到某个 slot,由对应主节点负责。
而 MULTI/EXEC 事务要求所有 key 必须属于同一个 slot,否则直接拒绝:
> MULTI
> SET user:1001:name Alice
> SET order:2001:status paid
> EXEC
(error) CROSSSLOT Keys in request don't hash to the same slot
原因很简单:事务需要在单个节点上原子执行,跨 slot 就意味着涉及多个物理节点——Redis 无法保证分布式事务的 ACID。
特别提醒:Pipeline 也不是事务!它只是网络优化,命令之间仍可能被其他客户端插入。
{} 内的内容参与 slot 计算。
user:{1001}:balance → slot = CRC16("1001") % 16384
user:{1001}:orders → slot = CRC16("1001") % 16384
user:{1001}:log → slot = CRC16("1001") % 16384
这些 key 必然落在同一节点,可被原子操作。
-- 扣款 + 加订单 + 记日志
local balance = tonumber(redis.call('GET', KEYS[1]) or 0)
if balance < tonumber(ARGV[2]) then
return redis.error_reply('INSUFFICIENT_BALANCE')
end
redis.call('DECRBY', KEYS[1], ARGV[2]) -- 扣余额
redis.call('SADD', KEYS[2], ARGV[1]) -- 加订单
redis.call('RPUSH', KEYS[3], 'Order created') -- 记日志
return balance - tonumber(ARGV[2])
String script = "..."; // 上述 Lua
List keys = Arrays.asList(
"user:{1001}:balance",
"user:{1001}:orders",
"user:{1001}:log");
List args = Arrays.asList("order_2026", "100");
Object result = jedis.eval(script, keys, args);
最佳实践:在领域建模阶段,就将需要原子操作的数据归为同一聚合,并用 {aggregate_id} 作为 Hash Tag。
try {
// 1. 冻结 A 的资金(带 TTL 防死锁)
redis.setex("lock:A:100", 30, "100");
// 2. 扣 A 余额
redis.decrBy("user:A:balance", 100);
// 3. 加 B 余额
redis.incrBy("user:B:balance", 100);
// 4. 清除锁
redis.del("lock:A:100");
} catch (Exception e) {
// 补偿:回滚已执行的操作
if (A余额已扣) redis.incrBy("user:A:balance", 100);
if (B余额已加) redis.decrBy("user:B:balance", 100);
redis.del("lock:A:100");
}
A[发起操作] --> B[发消息到 MQ]
B --> C[消费者1: 更新 key1]
C --> D[消费者2: 更新 key2]
- 消费者需实现幂等(如用 SET key value NX 防重);
- 适合非实时场景:积分发放、通知推送、日志同步等。
| 场景 | 推荐方案 |
|---|---|
| 强一致性 + 多 Key 同业务实体 | Hash Tag + Lua 脚本 |
| 跨实体 Key + 可接受最终一致 | Saga 补偿 或 异步队列 |
| 简单批量读写(同 Key) | MSET / MGET(内置原子命令) |
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述