提到事務,相信大家都不陌生,事務的ACID四大特性,也是面試時經常問的,不過一般情況下,我們可能想到的是傳統關係型資料庫的事務,其實,Redis也是提供了事務機制的,本篇部落格就來講解下Redis的事務機制。
1. 事務演示
Redis的事務提供了一種將多個命令請求打包,然後一次性、按順序性地執行多個命令的機制。
在事務執行期間,伺服器不會中斷事務而去執行其它客戶端的命令請求,它會將事務中的所有命令執行完畢,然後才去處理其它客戶端的命令請求。
下圖展示了一個Redis事務的執行過程:
可以看出,事務以MULTI
命令開始,然後將多個命令放到事務當中,最後由EXEC
命令將這個事務提交給伺服器執行。
2. 事務實現原理
一個事務從開始到結束會經歷以下3個階段:
- 事務開始
- 命令入隊
- 事務執行
2.1 事務開始
MULTI
命令的執行標誌著事務的開始。
執行完該命令後,客戶端狀態的flags屬性會開啟REDIS_MULTI標識,表示該客戶端從非事務狀態切換至事務狀態。
2.2 命令入隊
當一個客戶端處於非事務狀態時,這個客戶端傳送的命令會立即被伺服器執行:
當一個客戶端處於事務狀態時,這個客戶端傳送的命令,伺服器是否會立即執行,分為以下2種情況:
- 如果客戶端傳送的命令為
MULTI
、EXEC
、WATCH
、DISCARD
四個命令中的其中1個,伺服器會立即執行這個命令。 - 如果客戶端傳送的命令為以上4個命令外的其它命令,伺服器不會立即執行這個命令,而是將其放到事務佇列裡,然後向客戶端返回
QUEUED
回覆。
以上流程可以使用以下流程圖來表示:
這裡首先提下DISCARD
命令,這個命令用於取消事務,放棄執行事務塊內的所有命令,如下所示:
然後提下事務佇列,每個Redis客戶端都有自己的事務狀態,事務狀態儲存在客戶端狀態的mstates屬性裡:
事務狀態包含1個事務佇列和1個已入隊命令的數量,如下所示:
事務佇列是一個multiCmd型別的陣列,陣列中的每個multiCmd結構儲存了一個已入隊命令的相關資訊,比如:
- 指向命令實現函式的指標,如GET命令、SET命令
- 命令的引數
- 引數的數量
事務佇列以先進先出(FIFO)的方式儲存入隊的命令。
2.3 事務執行
當一個處於事務狀態的客戶端執行EXEC
命令時,伺服器會遍歷這個客戶端的事務佇列,執行佇列中儲存的所有命令(按先入先出順序),然後將執行命令的結果一次性返回給客戶端。
3. WATCH命令的實現原理
WATCH
命令用於監視任意數量的資料庫鍵,並在EXEC
命令執行時,檢測被監視的鍵是否被修改,如果被修改了,伺服器將拒絕執行事務,並向客戶端返回空回覆。
為了更好的理解,我們做個演示,首先,我們開啟客戶端1,執行WATCH
命令監視鍵“name”,然後開啟一個事務:
此時,先不要執行EXEC命令,開啟客戶端2,執行以下命令修改“name”鍵的值:
然後,在客戶端1執行EXEC命令時,會返回空回覆,因為“name”鍵的值在客戶端2已經被修改:
那麼,WATCH
命令的實現原理是什麼樣的呢?我們從以下3個方面來分析:
- 使用WATCH命令監視資料庫鍵
- 監視機制的觸發
- 判斷事務是否安全
3.1 使用WATCH命令監視資料庫鍵
每個Redis資料庫都儲存著1個watched_keys
字典,這個字典的鍵是某個被WATCH命令監視的資料庫鍵,字典的值是一個連結串列,連結串列中記錄了所有監視相應資料庫鍵的客戶端。
舉個例子,假如客戶端1正在監視鍵“name”,客戶端2正在監視鍵“age”,那麼watched_keys
字典儲存的資料大概如下:
如果此時客戶端3執行了以下WATCH
命令:
那麼watched_keys
字典儲存的資料就變為:
3.2 監視機制的觸發
那麼問題來了,既然watched_keys
字典儲存了被WATCH命令監視的鍵,那麼監視機制是如何被觸發的呢?
答案是所有對資料庫修改的命令,比如SET
、LPUSH
、SADD
等,在執行之後都會對watched_keys
字典進行檢查,如果有客戶端正在監視剛剛被命令修改的鍵,那麼所有監視該鍵的客戶端的REDIS_DIRTY_CAS
標識將被開啟,表示該客戶端的事務安全性已經被破壞。
以上圖為例,如果鍵“name”的值被修改,那麼客戶端1、客戶端3的REDIS_DIRTY_CAS
標識會被開啟。
3.3 判斷事務是否安全
最後非常關鍵的一步是,當伺服器接收到一個客戶端發來的EXEC
命令時,伺服器會根據這個客戶端是否開啟了REDIS_DIRTY_CAS
標識來決定是否執行事務,判斷的流程圖如下所示:
4. 事務執行失敗舉例
先來看第1個例子,這個事務因為命令入隊出錯被伺服器拒絕執行,事務中的所有命令都不會被執行:
再來看第2個例子,事務入隊時出現了不存在的命令,伺服器將拒絕執行這個事務:
再來看第3個例子,RPUSH
命令在執行期間報錯了,但後續命令仍然繼續執行,並且之前執行的命令沒有受到任何影響:
這個例子也說明Redis事務不支援回滾機制。
5. 總結
Redis的事務提供了一種將多個命令打包,然後一次性、有序地執行的機制,
它的原理是多個命令會被入隊到事務佇列中,然後按先進先出(FIFO)的順序執行,
並且事務在執行過程中不會被中斷,當事務佇列中的所有命令都被執行完畢之後,事務才會結束。
6. 參考
黃健巨集 《Redis設計與實現》