Redis?Cluster?實(shí)現(xiàn)多key事務(wù)操作的示例
在 Redis 集群模式下,MULTI/EXEC 事務(wù)直接報(bào)錯(cuò): “CROSSSLOT Keys in request don't hash to the same slot”。 那么,如何在保證數(shù)據(jù)一致性的前提下,同時(shí)操作多個(gè) Key? 本文給出 生產(chǎn)級(jí)可行方案,從原子性到最終一致性全覆蓋。
如果你正在使用 Redis Cluster,一定遇到過(guò)這樣的困境:
- 想扣用戶余額,同時(shí)創(chuàng)建訂單、記錄日志;
- 寫了個(gè)
MULTI/EXEC,結(jié)果 Redis 報(bào)錯(cuò):key 不在同一個(gè) slot; - 改用 Pipeline?但又怕中間被其他請(qǐng)求插隊(duì),導(dǎo)致狀態(tài)不一致……
別急!Redis Cluster 雖然限制了跨 slot 的原子操作,但通過(guò)合理設(shè)計(jì),我們依然能實(shí)現(xiàn)安全、可靠、高性能的多 Key 操作。
一、為什么 Redis Cluster 不支持跨節(jié)點(diǎn)事務(wù)?
Redis Cluster 將 key 空間劃分為 16384 個(gè) slot,每個(gè) key 通過(guò) CRC16(key) % 16384 映射到一個(gè) slot,由某個(gè)主節(jié)點(diǎn)負(fù)責(zé)。
而 MULTI/EXEC 事務(wù)要求所有 key 必須屬于同一個(gè) 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
原因很簡(jiǎn)單: 事務(wù)需要在單個(gè)節(jié)點(diǎn)上原子執(zhí)行,而跨 slot 意味著涉及多個(gè)物理節(jié)點(diǎn) —— Redis 無(wú)法保證分布式事務(wù)的 ACID。
?? 注意:Pipeline 也不是事務(wù)!它只是網(wǎng)絡(luò)優(yōu)化,命令之間仍可能被其他客戶端插入。
二、方案一:Hash Tag + Lua 腳本(強(qiáng)一致性,推薦!)
這是 唯一能在 Redis Cluster 中實(shí)現(xiàn)原子多 Key 操作的方式。
? 核心思想:
- 利用 Hash Tag 規(guī)則,強(qiáng)制相關(guān) key 落在同一 slot;
- 用 Lua 腳本 在單節(jié)點(diǎn)內(nèi)完成所有操作,天然原子。
?? 實(shí)施步驟
1. Key 設(shè)計(jì):使用{}包裹聚合 ID
Redis 規(guī)定:只有 {} 內(nèi)的內(nèi)容參與 slot 計(jì)算。
# 所有與用戶 1001 相關(guān)的 key
user:{1001}:balance → slot = CRC16("1001") % 16384
user:{1001}:orders → slot = CRC16("1001") % 16384
user:{1001}:log → slot = CRC16("1001") % 16384
? 這些 key 必然落在同一節(jié)點(diǎn),可被原子操作。
2. 編寫 Lua 腳本(原子執(zhí)行)
-- 扣款 + 加訂單 + 記日志
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])
3. 客戶端調(diào)用(以 Jedis 為例)
String script = "..."; // 上述 Lua
List<String> keys = Arrays.asList(
"user:{1001}:balance",
"user:{1001}:orders",
"user:{1001}:log"
);
List<String> args = Arrays.asList("order_2026", "100");
Object result = jedis.eval(script, keys, args);
? 優(yōu)勢(shì)
- 強(qiáng)原子性:腳本執(zhí)行期間,其他命令無(wú)法插入;
- 高性能:一次網(wǎng)絡(luò)往返;
- 完全兼容 Cluster。
?? 注意事項(xiàng)
- 避免數(shù)據(jù)傾斜:不要用高頻 ID(如 user_id=1)作為 tag;
- 僅適用于“聚合根”模型:所有 key 應(yīng)屬于同一業(yè)務(wù)實(shí)體(用戶、訂單、會(huì)話等)。
?? 最佳實(shí)踐:在領(lǐng)域建模階段,就將需原子操作的數(shù)據(jù)歸為同一聚合,并用
{aggregate_id}作為 Hash Tag。
三、方案二:應(yīng)用層分步操作 + 補(bǔ)償機(jī)制(最終一致性)
當(dāng) key 無(wú)法歸到同一 slot(如跨用戶轉(zhuǎn)賬),且業(yè)務(wù)可接受最終一致性時(shí),可采用 Saga 模式。
示例:用戶 A 轉(zhuǎn)賬給用戶 B
try {
// 1. 凍結(jié) 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) {
// 補(bǔ)償:回滾已執(zhí)行的操作
if (A余額已扣) redis.incrBy("user:A:balance", 100);
if (B余額已加) redis.decrBy("user:B:balance", 100);
redis.del("lock:A:100");
}
? 適用場(chǎng)景
- 跨聚合根操作(如 A→B 轉(zhuǎn)賬);
- 有明確補(bǔ)償邏輯(如退款、撤回);
- 可容忍短暫不一致。
? 劣勢(shì)
- 實(shí)現(xiàn)復(fù)雜(需冪等、重試、監(jiān)控);
- 無(wú)法保證強(qiáng)一致性。
四、方案三:異步隊(duì)列 + 冪等消費(fèi)(高吞吐場(chǎng)景)
將多 Key 操作拆解為消息,交由 Kafka/RocketMQ 等可靠隊(duì)列處理:
graph LR A[發(fā)起操作] --> B[發(fā)消息到 MQ] B --> C[消費(fèi)者1: 更新 key1] C --> D[消費(fèi)者2: 更新 key2]
- 消費(fèi)者需實(shí)現(xiàn)冪等(如用
SET key value NX防重); - 適合非實(shí)時(shí)場(chǎng)景:積分發(fā)放、通知推送、日志同步等。
五、不推薦方案:?jiǎn)为?dú)部署非集群 Redis
- 為事務(wù)單獨(dú)維護(hù)一套 standalone Redis;
- 破壞架構(gòu)統(tǒng)一性,增加運(yùn)維成本;
- 喪失 Cluster 的高可用與擴(kuò)展能力;
- 僅適用于極小規(guī)模、臨時(shí)過(guò)渡場(chǎng)景。
?? 總結(jié):如何選擇?
| 場(chǎng)景 | 推薦方案 |
|---|---|
| 強(qiáng)一致性 + 多 Key 同業(yè)務(wù)實(shí)體 | ? Hash Tag + Lua 腳本 |
| 跨實(shí)體 Key + 可接受最終一致 | ? Saga 補(bǔ)償 或 異步隊(duì)列 |
| 簡(jiǎn)單批量讀寫(同 Key) | ? MSET / MGET(內(nèi)置原子命令) |
?? 核心原則: 在 Redis Cluster 中,*不要對(duì)抗 slot 機(jī)制,而要順應(yīng)它*。 通過(guò)合理的數(shù)據(jù)建模(聚合根 + Hash Tag),你可以在享受 Cluster 高可用的同時(shí),實(shí)現(xiàn)強(qiáng)一致的事務(wù)操作。
到此這篇關(guān)于Redis Cluster 實(shí)現(xiàn)多key事務(wù)操作的文章就介紹到這了,更多相關(guān)Redis Cluster 多key事務(wù)操作內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis?異常?read?error?on?connection?的解決方案
這篇文章主要介紹了Redis異常read?error?on?connection的解決方案,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-08-08
Spring?Boot?緩存?與?Redis問(wèn)題小結(jié)
本文總結(jié)SpringBoot緩存方案,對(duì)比Redis與EnCache優(yōu)劣,指出Redis支持集群但效率較低,EnCache速度快但不支持分布式,SpringBoot默認(rèn)使用ConcurrentHashMapManager,本文給大家介紹Spring?Boot緩存與Redis問(wèn)題小結(jié),感興趣的朋友一起看看吧2025-07-07
Redis過(guò)期Key刪除策略和內(nèi)存淘汰策略的實(shí)現(xiàn)
當(dāng)內(nèi)存使用達(dá)到上限,就無(wú)法存儲(chǔ)更多數(shù)據(jù)了,為了解決這個(gè)問(wèn)題,Redis內(nèi)部會(huì)有兩套內(nèi)存回收的策略,過(guò)期Key刪除策略和內(nèi)存淘汰策略,本文就來(lái)詳細(xì)的介紹一下這兩種方法,感興趣的可以了解一下2024-02-02
redis實(shí)現(xiàn)分布式的方法總結(jié)
在本篇文章中小編給大家整理了關(guān)于redis分布式怎么做的具體內(nèi)容以及知識(shí)點(diǎn)總結(jié),有興趣的朋友們參考下。2019-06-06

