1. 程式人生 > 其它 >《Redis設計與實現》讀書筆記(三十二) ——Redis事務設計與實現

《Redis設計與實現》讀書筆記(三十二) ——Redis事務設計與實現

《Redis設計與實現》讀書筆記(三十二) ——Redis事務設計與實現

(原創內容,轉載請註明來源,謝謝)

一、概述

redis的事務同資料庫的事務概念一樣,即多條命令都成功執行,才會生效,否則不生效。redis客戶端中輸入multi命令,然後中間的所有命令都不會立即生效,直到再輸入exec命令。

二、redis事務實現

redis事務實現包括事務開始、事務入隊、事務執行。

1、事務開始

當客戶端輸入multi命令,表示事務開始。該命令會將redis從非事務狀態切換到事務狀態,切換是通過修改客戶端的flags屬性,加上REDIS_MULTI常量,表示開啟事務。

2、命令入隊

當redis開啟事務狀態,只有4個命令會立即執行:multi、discard、exec、watch。其他命令都不立即執行,而是放入redis的事務佇列,並且客戶端回覆queue。

每個redis客戶端都有一個事務狀態屬性,儲存在redisClient結構體的屬性mstate中,該屬性是multiState結構體,如下:

typedef struct multistate{
         multiCmd *commands;
         int count;
} multistate;

該結構體包含命令列表以及命令計數器,命令佇列是FIFO的順序,count記錄已經入隊的命令個數。其中,命令佇列是multiCmd結構體型別的陣列,結構體如下:

typedef struct multiCmd{
         robj **argv;
         int argc;
         struct redisCommand*cmd;
} multiCmd;

即,該結構體包含命令引數、引數個數以及指向實現命令的redisCommand結構體的指標。

3、執行事務

當處於事務狀態的客戶端收到命令exec,則會執行事務佇列中的所有命令,並將結果按照執行順序,全部返回給客戶端。

執行過程包括:

1)建立空白佇列,用於儲存每個命令的執行結果;

2)按照FIFO的順序執行命令佇列的命令,並將結果按順序放入空白佇列;

3)移除flags屬性的REDIS_MULTI標記,表示客戶端退出事務;

4)清除入隊命令計數器、釋放事務佇列;

5)將執行結果返回給客戶端。

三、Watch命令實現

watch命令是一個樂觀鎖,可以在執行exec之前,監視任意數量資料庫的鍵,並在執行exec時,檢查監視的鍵是否有被修改的,如果有一個或以上的鍵被修改,則拒絕執行事務,客戶端返回事務執行失敗的空回覆(nil)。

1、watch監視

通過watch key1 key2… 即可監視鍵。

redis資料庫結構體redisDB中,儲存著watched_keys字典,字典的鍵是被watch命令監視的鍵,值是一個連結串列,記錄所有監視相應資料庫鍵的客戶端。

通過該字典,可以清楚判斷哪些鍵被監視,以及哪些客戶端監視這些鍵。

2、監視觸發

所有對資料庫的鍵進行修改的命令,如set、lpush等,執行後都會自動呼叫multi.c/touchWatchKey函式,對字典watched_keys進行檢查,檢視是否有資料庫監視該鍵。

如果有資料庫監視該鍵,則將監視被修改鍵的所有客戶端的狀態REDIS_DIRTY_CAS標識開啟,表示該客戶端事務的安全性已經被破壞。

3、判斷事務是否安全

當客戶端執行exec命令時,就會判斷對應自身客戶端的狀態是否被開啟REDIS_DIRTY_CAS,如果被開啟,說明至少一個鍵被修改,則事務不安全,redis伺服器拒絕客戶端提交事務,並返回nil;如果沒有被開啟,表示事務安全,正常執行事務。

四、事務ACID性質

ACID分別表示Atomicity(原子性)、Consistency(一致性)、Isolation(隔離性)、(Durability)耐久性。redis的事務總是會保證ACI三個屬性,在開啟某些持久化方式後,也可以保證D的屬性。

1、原子性

事務原子性指要麼事務全部操作都執行,要麼全部不執行。

例如redis的命令錯誤等,在命令入隊的時候就會進行校驗,如果有命令錯誤,則入隊的時候會報錯,等到執行exec時,redis則拒絕執行整個事務。

與很多關係型資料庫不同,redis不支援事務的回滾,因為redis的作者認為事務出錯只有在開發環境中會有,生產環境沒有。而回滾會導致redis程式碼複雜,與設計初衷不符。

2、一致性

事務的一致性指事務執行前後,資料庫是一致的,無論事務是否執行成功。一致指資料庫的資料符號資料庫本身定義和要求,沒有非法、無效資料。

redis在三個地方進行校驗:

1)入隊出錯

命令不存在或命令格式錯誤等,表示入隊出錯,redis會拒絕執行事務。

2)執行錯誤

事務執行過程中可能會發生錯誤,這些錯誤是在入隊的時候無法發現的錯誤。在執行中發生的錯誤,不會中斷事務,事務會繼續進行。對資料庫鍵進行錯誤型別操作是最常發生的執行錯誤。

錯誤的命令會被伺服器報出,並且不會將錯誤的命令進行執行,保證資料一致性。

3)伺服器停機

如果redis事務執行期間發生伺服器停機,則根據redis的持久化的方案,會發生以下不同的情況:

1. 無持久化,則沒有儲存任何資料,資料是一致的。

2. rdb或aof持久化,則可以根據rdb或aof檔案進行回覆,不會發生資料不一致。如果無檔案,則無法恢復資料,但資料仍是一致性的。

3、隔離性

隔離性是指多個事務併發進行,各個事務不會互相影響,並且併發狀態下執行事務與序列狀態下執行事務結果完全相同。

由於redis是單執行緒執行事務,且伺服器保證事務執行期間不會有其他命令插入,因此redis的命令總是序列執行的,保證隔離性。

4、耐久性

事務耐久性是指一個事務執行完畢後,事務的結果被儲存在磁盤裡,後面及時伺服器停機,資料仍存在。

由於redis的事務只是對一組命令進行包裹,並沒有附帶持久化的命令,因此redis事務的持久化與否取決於redis伺服器配置的持久化策略。

只有redis在aof持久化狀態下,且appendfsync選項的值設定為always,程式才會每次將命令的結果實時強制同步到磁碟中,redis的事務才有真正的耐久性,其他情況下的redis事務不具有耐久性。

雖然可以在執行exec之前,輸入一個save命令,強制全磁碟儲存,保證事務的耐久性,但是由於save是阻塞的,效率極低,因此不具有實用性。

五、總結

1、redis的事務是將一組命令打包,一次性、有序的執行。

2、事務中的多個命令會被放入到事務佇列中,FIFO的被執行。

3、事務執行過程中不會被中斷,一個事務執行完才會執行下一個事務。

4、watch命令通過資料庫的redisDB結構體的watched_keys字典中,將欄位與要監視的客戶端進行關聯,當鍵被修改,則相應的監視該鍵的全部客戶端的REDIS_DIRTY_CAS標識被開啟。只有該表示沒開啟,伺服器才會執行客戶端的事務,否則伺服器會拒絕提交事務。

5、redis事務具有原子性、一致性、隔離性,其是否具有耐久性取決於redis持久化的配置策略。

——written by linhxx 2017.09.27