1. 程式人生 > >Redis系列(九):Redis的事務機制

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

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