事務模式
同時存在多個客戶端時,一個客戶端批量發送的命令和另外一個客戶端的命令執行順序會存在交叉。redis
當須要將批量執行的語句原子化時,須要引入Redis的事務模式。服務器
一個事務中的多條命令以原子化的方式執行,不一樣事務的命令相互時序再也不交叉。微信
入隊/執行分離的事務原子性
客戶端和Redis服務器經過兩階段交互作到批量命令原子化執行:併發
入隊階段:客戶端將請求發送到服務器端,服務器端將其暫存在鏈接對象對應的請求隊列中app
執行階段:發送完一個批次的全部請求後,Redis服務器依次執行鏈接對象隊列中的全部請求。單個實例的Redis單線程執行全部請求,一個鏈接的請求在執行批量請求的過程當中,不會執行其餘客戶端的請求。flex
批量執行命令:EXECthis
由MULTI開啓事務,隨後發送的請求都暫存在服務器端的鏈接上,最後經過EXEC一次性批量執行,並將全部執行結果做爲一個響應,以array類型的協議數據返回客戶端。spa
事務的一致性
入隊階段若是發生語法錯誤,不會後續執行EXEC。.net
EXEC過程當中若是有一條請求命令執行失敗,後續請求會繼續執行,只是在返回客戶端的array數據中標記這條命令失敗。線程
嚴格講,Redis事務並非一致的。
事務的只讀操做
批量請求須要應用程序一開始在入隊階段肯定每次寫操做的值,每一個請求的參數取值不能依賴上一次請求的執行。
入隊的請求應該全是寫操做,只讀操做需放到MULTI語句前執行。
事務在執行期間,若是穿插其餘客戶端的其餘語句,會形成數據不一致問題。經過Redis的WATHC機制能夠解決一致性問題。
樂觀鎖的可串行化事務隔離
Redis經過WATCH機制實現樂觀鎖解決一致性問題:
將本次事務涉及的全部key註冊爲觀察模式,假設此時邏輯時間爲tstart。
執行只讀操做
根據只讀操做的結果組裝寫操做命令併發送到服務器端入隊
發送原子化的批量執行命令EXEC試圖執行鏈接的請求隊列中的命令,假設此時邏輯時間爲tcommit。
執行時有兩種狀況:
假設前面註冊爲觀察模式的key有一個或多個,在tstart和tcommit之間被修改過,那麼EXEC將直接失敗,拒絕執行
不然順序執行請求隊列中的全部請求
事務實現
事務的狀態保存在redisClient中,經過兩個屬性控制:
typedef struct redisClient{ ... int flags; multiState mstate; ...} redisClient;
flags包含多個bit,其中兩個bit分別標記了:當前鏈接處於MULTI和EXEC之間、當前鏈接WATCH以後到如今它所觀察的key是否被修改過
typedef struct mlutiState{ multiCmd *commands; int count; ...} multiState;
count用來標記MULTI和EXEC之間總共有多少個待執行命令,同時commands就是該鏈接的請求隊列。
watch機制經過維護在redisDb中的全局map來實現:
typedef struct redisDb{ dict *dict; /* The keyspace for this DB */ dict *expires; /* Timeout of keys with a timeout set */ dict *blocking_keys;/* Keys with clients waiting for data (BLPOP) */ dict *ready_keys; /* Blocked keys that received a PUSH */ dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ struct evictionPoolEntry * eviction_pool; /* Eviction pool of keys */ int id; /* Database ID */ long long avg_ttl; /* Average TTL, just for stats */} redisDb;
map的鍵是被watch的key,值是watch這些key的redisClient指針的鏈表。
每當redis執行一個寫命令時,同時會對執行命令的這個key在watchedkeys中找到對應的client並將後者的flag對應位置爲REDIS_DIRTY_CAS,後續這個client在執行EXEC以前若是看到flag有REDIS_DIRTY_CAS標記,則拒絕執行。
事務的結束或者顯示UNWATCH都會重置redisClient中的REDIS_DIRTY_CAS標記並從redisDb對應watched_keys鏈表中刪除本身。
事務交互模式
客戶端發送四類請求:監聽相關(WATCH、UNWATCH)、只讀請求、寫請求的批量執行或放棄執行請求(EXEC/DISCARD)、寫請求的入隊(MULTI和EXEC/DISCARD之間)
交互時序爲:開啓對讀寫主鍵的監聽、只讀操做、MULTI請求、根據前面只讀操做的結果編排/參數賦值/入隊寫操做、一次性批量執行隊列中的寫請求。
本文分享自微信公衆號 - shysh95(shysh95)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。