Redis事務
1. 事務的基本流程
Redis 事務通過 MULTI
、EXEC
、WATCH
等命令實現,底層原理可以分為以下幾個步驟:
(1) MULTI 命令
- 當客戶端發送
MULTI
命令時,Redis 會將客戶端標記為“事務模式”。 - 在事務模式下,客戶端發送的所有命令不會立即執行,而是被放入一個隊列(命令隊列)中。
(2) 命令入隊
- 在
MULTI
和EXEC
之間,客戶端發送的所有命令都會被追加到事務隊列中。 - 這些命令不會立即執行,而是等待
EXEC
命令的觸發。
(3) EXEC 命令
- 當客戶端發送
EXEC
命令時,Redis 會依次執行事務隊列中的所有命令。 - 執行過程中,所有命令是原子的,不會被其他客戶端的命令打斷。
(4) WATCH 命令
WATCH
命令用于實現樂觀鎖。- 當客戶端對一個或多個鍵執行
WATCH
后,如果在EXEC
執行之前,這些鍵被其他客戶端修改,則當前事務會失敗(返回nil
)
watch我們可以指定監聽一個鍵和多個鍵,然后exec批量執行
WATCH key [key ...]
2. 事務的原子性
- Redis 事務的原子性是通過單線程模型實現的。
- Redis 是單線程的,所有命令都是順序執行的。在
EXEC
執行時,事務隊列中的命令會連續執行,不會被其他客戶端的命令打斷。
3. 事務的一致性
- Redis 事務的一致性是通過
WATCH
機制實現的。 - 如果
WATCH
的鍵在事務執行期間被修改,事務會失敗,從而保證數據的一致性。
4. 事務的局限性
- 不支持回滾:如果事務中的某個命令失敗,其他命令仍然會執行,Redis 不會自動回滾。
- 部分失敗問題:事務中的命令可能會部分成功、部分失敗。
- 性能開銷:
WATCH
機制會增加額外的性能開銷。
Redis 7 對事務的優化
Redis 7 在事務機制上并沒有完全改變底層實現,但引入了一些優化和改進:
1. 性能優化
- Redis 7 對事務的執行流程進行了優化,減少了事務模式下的性能開銷。
- 通過改進命令隊列的處理方式,提高了事務的執行效率。
2. Lua 腳本的增強
- Redis 7 對 Lua 腳本的支持進行了增強,使得 Lua 腳本可以更好地與事務結合使用。
- Lua 腳本在 Redis 7 中的執行效率更高,同時支持更多的 Redis 命令。
3. 更好的錯誤處理
- Redis 7 改進了事務中的錯誤處理機制,使得事務失敗時的錯誤信息更加清晰。
- 如果事務中的某個命令失敗,Redis 7 會返回更詳細的錯誤信息,方便排查問題。
4. 功能增強
- Redis 7 引入了更多的命令和功能,可以與事務結合使用。
- 例如,Redis 7 支持更多的數據類型和操作,使得事務可以處理更復雜的場景。
Redis 事務的底層實現細節
1. 命令隊列
- 在事務模式下,Redis 會為每個客戶端維護一個命令隊列。
- 所有在
MULTI
和EXEC
之間發送的命令都會被追加到隊列中。
2. 事務執行
- 當
EXEC
命令被觸發時,Redis 會依次執行命令隊列中的所有命令。 - 執行過程中,Redis 會保證命令的原子性,不會被其他客戶端的命令打斷。
3. WATCH 機制
WATCH
命令會監視一個或多個鍵。- 如果在
EXEC
執行之前,這些鍵被其他客戶端修改,則當前事務會失敗。 WATCH
的實現基于 Redis 的鍵空間通知機制。
總結
- Redis 事務的底層原理 是基于
MULTI/EXEC/WATCH
機制,通過命令隊列和樂觀鎖實現原子性和一致性。 - Redis 7 在事務機制上進行了性能優化和功能增強,但底層實現并沒有本質變化。
- Redis 事務的局限性 包括不支持回滾、部分失敗問題和性能開銷。
- 如果需要更強大的事務支持,可以結合 Lua 腳本或使用支持 ACID 事務的數據庫。
Redis+Lua腳本實現手動回滾補償
我們每一步執行失敗,我們就依次撤銷前面的操作
可惜這個并不是真正的acid,我們的mysql執行事務的時候宕機了,它的事務沒有提交所以數據并不會進到mysql里面
而redis是人為控制的,所以我們執行lua腳本的時候宕機了,我們之前事務中執行的操作數據仍然進去了,這個是我們無法解決的
local key1 = KEYS[1]
local key2 = KEYS[2]
local key3 = KEYS[3]
local value = ARGV[1]-- 記錄原始值
local original_value1 = redis.call('GET', key1)
local original_value2 = redis.call('GET', key2)
local original_value3 = redis.call('GET', key3)-- 第一步操作
redis.call('SET', key1, value)-- 第二步操作
if redis.call('EXISTS', key2) == 0 then-- 手動回滾第一步操作redis.call('SET', key1, original_value1)return "Key2 does not exist"
end
redis.call('SET', key2, value)-- 第三步操作
if redis.call('EXISTS', key3) == 0 then-- 手動回滾前兩步操作redis.call('SET', key1, original_value1)redis.call('SET', key2, original_value2)return "Key3 does not exist"
end
redis.call('SET', key3, value)return "Transaction successful"