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)建訂單、記錄日志;
- 寫(xiě)了個(gè)
MULTI/EXEC,結(jié)果 Redis 報(bào)錯(cuò):key 不在同一個(gè) slot; - 改用 Pipeline?但又怕中間被其他請(qǐng)求插隊(duì),導(dǎo)致?tīng)顟B(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)一致性,推薦?。?/h2>
這是 唯一能在 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. 編寫(xiě) 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)單批量讀寫(xiě)(同 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報(bào)錯(cuò)MISCONF?Redis?is?configured?to?save?RDB?snap
這篇文章主要給大家介紹了關(guān)于如何解決Redis報(bào)錯(cuò)MISCONF?Redis?is?configured?to?save?RDB?snapshots的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11
Redis?HyperLogLog數(shù)據(jù)量統(tǒng)計(jì)的實(shí)現(xiàn)實(shí)例
在大數(shù)據(jù)時(shí)代,統(tǒng)計(jì)海量數(shù)據(jù)中的唯一值是一個(gè)常見(jiàn)的需求,但同時(shí)也是極具挑戰(zhàn)性的任務(wù),傳統(tǒng)的統(tǒng)計(jì)方法可能會(huì)消耗大量?jī)?nèi)存或計(jì)算資源,而?Redis?的?HyperLogLog?數(shù)據(jù)結(jié)構(gòu)?則提供了一種高效、輕量的解決方案,下面就來(lái)詳細(xì)介紹一下HyperLogLog的使用,感興趣的可以了解一下2025-09-09
Redis 過(guò)期鍵刪除策略的實(shí)現(xiàn)示例
Redis的過(guò)期數(shù)據(jù)刪除策略主要有三種,包括定時(shí)刪除、惰性刪除和定期刪除,本文主要介紹了Redis 過(guò)期鍵刪除策略的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03
CentOS8.4安裝Redis6.2.6的詳細(xì)過(guò)程
本文給大家介紹CentOS8.4安裝Redis6.2.6的詳細(xì)過(guò)程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-11-11
Redis分布式鎖如何自動(dòng)續(xù)期的實(shí)現(xiàn)
本文主要介紹了Redis分布式鎖如何自動(dòng)續(xù)期的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
關(guān)于使用Redisson訂閱數(shù)問(wèn)題
本文主要介紹了關(guān)于使用Redisson訂閱數(shù)問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01

