Redis通過MULTI、EXEC、WATCH、DISCARD等命令來實現事務功能。主要有以下三個階段:
事務開始
MULTI命令的執行,標識着一個事務的開始。MULTI命令會將客戶端狀態的flags
屬性中打開REDIS_MULTI
標識來完成的。
命令入隊
當一個客戶端切換到事務狀態之后,服務器會根據這個客戶端發送來的命令來執行不同的操作。如果客戶端發送的命令為MULTI、EXEC、WATCH、DISCARD中的一個,立即執行這個命令,否則將命令放入一個事務隊列里面,然后向客戶端返回QUEUED
回復
- Redis客戶端擁有自己的事務狀態
typedef struct client {
// ......
// 事務狀態(MULTI或者EXEC)
multiState mstate;
// ......
} client;
- 事務狀態包含一個事務隊列
typedef struct multiState {
// 事務隊列,FIFO
multiCmd *commands;
// 已入隊命令計數
int count;
// ......
} multiState;
- 事務隊列中multiCmd類型保存了一個已入隊命令的相關信息
typedef struct multiCmd {
// 參數
robj **argv;
// 參數數量
int argc;
// 命令指針
struct redisCommand *cmd;
} multiCmd;
事務隊列是按照FIFO的方式保存入隊的命令
事務執行
當一個處於事務狀態的客戶端向服務器發送EXEC命令時,服務器會遍歷這個客戶端的事務隊列,執行隊列中保存的所有命令,最后將執行完的結果全部返回給客戶端(每個命令對應一個返回)
WATCH命令的實現
WATCH命令是一個樂觀鎖,它可以在EXEC命令執行之前,監視任意數量的數據庫鍵,並在執行EXEC命令時,檢查被監視的鍵是否至少有一個已經被修改,如果有,服務器拒絕執行事務,向客戶端返回代表事務執行失敗的空回復
實現原理
- 使用WATCH命令監視數據庫鍵:通過
watched_keys
字典,服務器可以清除地知道哪些數據庫鍵正在被監視,以及哪些客戶端正在監視這些數據庫鍵
typedef struct redisDb {
// ......
// 正在被WATCH命令監視的鍵
dict *watched_keys;
// ......
} redisDb;
- 監視機制的觸發:所有對數據庫進行修改的命令,在執行之后都會調用
touchWatchKey
函數對watched_keys
字典進行檢查,如果有客戶端監視的key被修改過,那么touchWatchKey
函數會將監視被修改的客戶端的REDIS_DIRTY_CAS
標識打開,表示事務已經被破壞 - 判斷事務是否安全:如果客戶端的
REDIS_DIRTY_CAS
標識被打開了,說明客戶端提交的事務已經不再安全,所以服務器會拒絕執行客戶端提交的事務
事務的ACID性質
-
原子性:對於Redis的事務功能來說,事務隊列中的命令要么就全部執行,要么就一個都不執行,但是Redis的事務是不支持回滾操作的
-
一致性:Redis通過謹慎的錯誤檢測和簡單的設計保證事務的一致性。Redis事務可能出錯的地方以及解決方案:
- 入隊錯誤:如果一個事務在入隊命令的過程中發現命令不存在或者命令格式不正確,Redis將拒絕執行這個事務
- 執行錯誤:事務在執行的過程中發生錯誤的命令會被服務器識別出來,並進行相應的錯誤處理,所以這些出錯的命令不會對數據庫做任何修改,也不會對事務的一致性產生任何影響
- 服務器停機:如果Redis服務器在執行事務的過程中停機,且服務器運行在任意模式下(無持久化的內存模式、RDB模式或者AOF模式),事務執行中途發生的停機都不會影響數據庫的一致性
-
隔離性:Redis使用單線程的方式執行事務,並且服務器保證在執行事務期間不會對事務進行中斷,因此,Redis的事務總是串行的方式運行,並且事務總是具有隔離性的
-
耐久性:當服務器運行在AOF持久化模式下,並且
appendfsync
選項的值是always
時,事務是具有耐久性的,其他情況不具有耐久性