Redis系列(九):Redis的事務機制

申城異鄉人發表於2020-07-29

提到事務,相信大家都不陌生,事務的ACID四大特性,也是面試時經常問的,不過一般情況下,我們可能想到的是傳統關係型資料庫的事務,其實,Redis也是提供了事務機制的,本篇部落格就來講解下Redis的事務機制。

1. 事務演示

Redis的事務提供了一種將多個命令請求打包,然後一次性、按順序性地執行多個命令的機制。

在事務執行期間,伺服器不會中斷事務而去執行其它客戶端的命令請求,它會將事務中的所有命令執行完畢,然後才去處理其它客戶端的命令請求。

下圖展示了一個Redis事務的執行過程:

可以看出,事務以MULTI命令開始,然後將多個命令放到事務當中,最後由EXEC命令將這個事務提交給伺服器執行。

2. 事務實現原理

一個事務從開始到結束會經歷以下3個階段:

  1. 事務開始
  2. 命令入隊
  3. 事務執行

2.1 事務開始

MULTI命令的執行標誌著事務的開始。

執行完該命令後,客戶端狀態的flags屬性會開啟REDIS_MULTI標識,表示該客戶端從非事務狀態切換至事務狀態。

2.2 命令入隊

當一個客戶端處於非事務狀態時,這個客戶端傳送的命令會立即被伺服器執行:

當一個客戶端處於事務狀態時,這個客戶端傳送的命令,伺服器是否會立即執行,分為以下2種情況:

  1. 如果客戶端傳送的命令為MULTIEXECWATCHDISCARD四個命令中的其中1個,伺服器會立即執行這個命令。
  2. 如果客戶端傳送的命令為以上4個命令外的其它命令,伺服器不會立即執行這個命令,而是將其放到事務佇列裡,然後向客戶端返回QUEUED回覆。

以上流程可以使用以下流程圖來表示:

這裡首先提下DISCARD命令,這個命令用於取消事務,放棄執行事務塊內的所有命令,如下所示:

然後提下事務佇列,每個Redis客戶端都有自己的事務狀態,事務狀態儲存在客戶端狀態的mstates屬性裡:

事務狀態包含1個事務佇列和1個已入隊命令的數量,如下所示:

事務佇列是一個multiCmd型別的陣列,陣列中的每個multiCmd結構儲存了一個已入隊命令的相關資訊,比如:

  1. 指向命令實現函式的指標,如GET命令、SET命令
  2. 命令的引數
  3. 引數的數量

事務佇列以先進先出(FIFO)的方式儲存入隊的命令。

2.3 事務執行

當一個處於事務狀態的客戶端執行EXEC命令時,伺服器會遍歷這個客戶端的事務佇列,執行佇列中儲存的所有命令(按先入先出順序),然後將執行命令的結果一次性返回給客戶端。

3. WATCH命令的實現原理

WATCH命令用於監視任意數量的資料庫鍵,並在EXEC命令執行時,檢測被監視的鍵是否被修改,如果被修改了,伺服器將拒絕執行事務,並向客戶端返回空回覆。

為了更好的理解,我們做個演示,首先,我們開啟客戶端1,執行WATCH命令監視鍵“name”,然後開啟一個事務:

此時,先不要執行EXEC命令,開啟客戶端2,執行以下命令修改“name”鍵的值:

然後,在客戶端1執行EXEC命令時,會返回空回覆,因為“name”鍵的值在客戶端2已經被修改:

那麼,WATCH命令的實現原理是什麼樣的呢?我們從以下3個方面來分析:

  1. 使用WATCH命令監視資料庫鍵
  2. 監視機制的觸發
  3. 判斷事務是否安全

3.1 使用WATCH命令監視資料庫鍵

每個Redis資料庫都儲存著1個watched_keys字典,這個字典的鍵是某個被WATCH命令監視的資料庫鍵,字典的值是一個連結串列,連結串列中記錄了所有監視相應資料庫鍵的客戶端。

舉個例子,假如客戶端1正在監視鍵“name”,客戶端2正在監視鍵“age”,那麼watched_keys字典儲存的資料大概如下:

如果此時客戶端3執行了以下WATCH命令:

那麼watched_keys字典儲存的資料就變為:

3.2 監視機制的觸發

那麼問題來了,既然watched_keys字典儲存了被WATCH命令監視的鍵,那麼監視機制是如何被觸發的呢?

答案是所有對資料庫修改的命令,比如SETLPUSHSADD等,在執行之後都會對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設計與實現》


相關文章