Redis 通過 MULTI 、 DISCARD ,、 EXEC 和 WATCH 四個(gè)命令來實(shí)現(xiàn)事務(wù)功能,, 本章首先討論使用 MULTI 、 DISCARD 和 EXEC 三個(gè)命令實(shí)現(xiàn)的一般事務(wù),, 然后再來討論帶有 WATCH 的事務(wù)的實(shí)現(xiàn),。 因?yàn)槭聞?wù)的安全性也非常重要,, 所以本章最后通過常見的 ACID 性質(zhì)對(duì) Redis 事務(wù)的安全性進(jìn)行了說明。 事務(wù)事務(wù)提供了一種“將多個(gè)命令打包,, 然后一次性,、按順序地執(zhí)行”的機(jī)制, 并且事務(wù)在執(zhí)行的期間不會(huì)主動(dòng)中斷 —— 服務(wù)器在執(zhí)行完事務(wù)中的所有命令之后,, 才會(huì)繼續(xù)處理其他客戶端的其他命令,。 以下是一個(gè)事務(wù)的例子, 它先以 MULTI 開始一個(gè)事務(wù),, 然后將多個(gè)命令入隊(duì)到事務(wù)中,, 最后由 EXEC 命令觸發(fā)事務(wù), 一并執(zhí)行事務(wù)中的所有命令: 一個(gè)事務(wù)從開始到執(zhí)行會(huì)經(jīng)歷以下三個(gè)階段:
下文將分別介紹事務(wù)的這三個(gè)階段,。 開始事務(wù)MULTI 命令的執(zhí)行標(biāo)記著事務(wù)的開始: 這個(gè)命令唯一做的就是,, 將客戶端的 REDIS_MULTI 選項(xiàng)打開, 讓客戶端從非事務(wù)狀態(tài)切換到事務(wù)狀態(tài),。 命令入隊(duì)當(dāng)客戶端處于非事務(wù)狀態(tài)下時(shí),, 所有發(fā)送給服務(wù)器端的命令都會(huì)立即被服務(wù)器執(zhí)行: 但是, 當(dāng)客戶端進(jìn)入事務(wù)狀態(tài)之后,, 服務(wù)器在收到來自客戶端的命令時(shí),, 不會(huì)立即執(zhí)行命令, 而是將這些命令全部放進(jìn)一個(gè)事務(wù)隊(duì)列里,, 然后返回 QUEUED ,, 表示命令已入隊(duì): 以下流程圖展示了這一行為: 事務(wù)隊(duì)列是一個(gè)數(shù)組,, 每個(gè)數(shù)組項(xiàng)是都包含三個(gè)屬性:
舉個(gè)例子,, 如果客戶端執(zhí)行以下命令: 那么程序?qū)榭蛻舳藙?chuàng)建以下事務(wù)隊(duì)列:
執(zhí)行事務(wù)前面說到, 當(dāng)客戶端進(jìn)入事務(wù)狀態(tài)之后,, 客戶端發(fā)送的命令就會(huì)被放進(jìn)事務(wù)隊(duì)列里,。 但其實(shí)并不是所有的命令都會(huì)被放進(jìn)事務(wù)隊(duì)列, 其中的例外就是 EXEC ,、 DISCARD ,、 MULTI 和 WATCH 這四個(gè)命令 —— 當(dāng)這四個(gè)命令從客戶端發(fā)送到服務(wù)器時(shí), 它們會(huì)像客戶端處于非事務(wù)狀態(tài)一樣, 直接被服務(wù)器執(zhí)行: 如果客戶端正處于事務(wù)狀態(tài),, 那么當(dāng) EXEC 命令執(zhí)行時(shí), 服務(wù)器根據(jù)客戶端所保存的事務(wù)隊(duì)列,, 以先進(jìn)先出(FIFO)的方式執(zhí)行事務(wù)隊(duì)列中的命令: 最先入隊(duì)的命令最先執(zhí)行,, 而最后入隊(duì)的命令最后執(zhí)行。 比如說,,對(duì)于以下事務(wù)隊(duì)列:
程序會(huì)首先執(zhí)行 SET 命令,, 然后執(zhí)行 GET 命令, 再然后執(zhí)行 SADD 命令,, 最后執(zhí)行 SMEMBERS 命令。 執(zhí)行事務(wù)中的命令所得的結(jié)果會(huì)以 FIFO 的順序保存到一個(gè)回復(fù)隊(duì)列中,。 比如說,,對(duì)于上面給出的事務(wù)隊(duì)列,程序?qū)殛?duì)列中的命令創(chuàng)建如下回復(fù)隊(duì)列:
當(dāng)事務(wù)隊(duì)列里的所有命令被執(zhí)行完之后,, EXEC 命令會(huì)將回復(fù)隊(duì)列作為自己的執(zhí)行結(jié)果返回給客戶端,, 客戶端從事務(wù)狀態(tài)返回到非事務(wù)狀態(tài), 至此,, 事務(wù)執(zhí)行完畢,。 事務(wù)的整個(gè)執(zhí)行過程可以用以下偽代碼表示: 在事務(wù)和非事務(wù)狀態(tài)下執(zhí)行命令無論在事務(wù)狀態(tài)下,, 還是在非事務(wù)狀態(tài)下, Redis 命令都由同一個(gè)函數(shù)執(zhí)行,, 所以它們共享很多服務(wù)器的一般設(shè)置,, 比如 AOF 的配置、RDB 的配置,,以及內(nèi)存限制,,等等。 不過事務(wù)中的命令和普通命令在執(zhí)行上還是有一點(diǎn)區(qū)別的,,其中最重要的兩點(diǎn)是:
事務(wù)狀態(tài)下的 DISCARD ,、 MULTI 和 WATCH 命令除了 EXEC 之外, 服務(wù)器在客戶端處于事務(wù)狀態(tài)時(shí),, 不加入到事務(wù)隊(duì)列而直接執(zhí)行的另外三個(gè)命令是 DISCARD ,、 MULTI 和 WATCH 。 DISCARD 命令用于取消一個(gè)事務(wù),, 它清空客戶端的整個(gè)事務(wù)隊(duì)列,, 然后將客戶端從事務(wù)狀態(tài)調(diào)整回非事務(wù)狀態(tài), 最后返回字符串 OK 給客戶端,, 說明事務(wù)已被取消,。 Redis 的事務(wù)是不可嵌套的, 當(dāng)客戶端已經(jīng)處于事務(wù)狀態(tài),, 而客戶端又再向服務(wù)器發(fā)送 MULTI 時(shí),, 服務(wù)器只是簡(jiǎn)單地向客戶端發(fā)送一個(gè)錯(cuò)誤, 然后繼續(xù)等待其他命令的入隊(duì),。 MULTI 命令的發(fā)送不會(huì)造成整個(gè)事務(wù)失敗,, 也不會(huì)修改事務(wù)隊(duì)列中已有的數(shù)據(jù)。 WATCH 只能在客戶端進(jìn)入事務(wù)狀態(tài)之前執(zhí)行,, 在事務(wù)狀態(tài)下發(fā)送 WATCH 命令會(huì)引發(fā)一個(gè)錯(cuò)誤,, 但它不會(huì)造成整個(gè)事務(wù)失敗, 也不會(huì)修改事務(wù)隊(duì)列中已有的數(shù)據(jù)(和前面處理 MULTI 的情況一樣),。 帶 WATCH 的事務(wù)WATCH 命令用于在事務(wù)開始之前監(jiān)視任意數(shù)量的鍵: 當(dāng)調(diào)用 EXEC 命令執(zhí)行事務(wù)時(shí),, 如果任意一個(gè)被監(jiān)視的鍵已經(jīng)被其他客戶端修改了, 那么整個(gè)事務(wù)不再執(zhí)行,, 直接返回失敗,。 以下示例展示了一個(gè)執(zhí)行失敗的事務(wù)例子: 以下執(zhí)行序列展示了上面的例子是如何失敗的:
在時(shí)間 T4 ,客戶端 B 修改了 name 鍵的值,, 當(dāng)客戶端 A 在 T5 執(zhí)行 EXEC 時(shí),,Redis 會(huì)發(fā)現(xiàn) name 這個(gè)被監(jiān)視的鍵已經(jīng)被修改, 因此客戶端 A 的事務(wù)不會(huì)被執(zhí)行,,而是直接返回失敗,。 下文就來介紹 WATCH 的實(shí)現(xiàn)機(jī)制,并且看看事務(wù)系統(tǒng)是如何檢查某個(gè)被監(jiān)視的鍵是否被修改,,從而保證事務(wù)的安全性的,。 WATCH 命令的實(shí)現(xiàn)在每個(gè)代表數(shù)據(jù)庫的 redis.h/redisDb 結(jié)構(gòu)類型中,, 都保存了一個(gè) watched_keys 字典, 字典的鍵是這個(gè)數(shù)據(jù)庫被監(jiān)視的鍵,, 而字典的值則是一個(gè)鏈表,, 鏈表中保存了所有監(jiān)視這個(gè)鍵的客戶端。 比如說,,以下字典就展示了一個(gè) watched_keys 字典的例子: 其中,, 鍵 key1 正在被 client2 、 client5 和 client1 三個(gè)客戶端監(jiān)視,, 其他一些鍵也分別被其他別的客戶端監(jiān)視著,。 WATCH 命令的作用, 就是將當(dāng)前客戶端和要監(jiān)視的鍵在 watched_keys 中進(jìn)行關(guān)聯(lián),。 舉個(gè)例子,, 如果當(dāng)前客戶端為 client10086 , 那么當(dāng)客戶端執(zhí)行 WATCH key1 key2 時(shí),, 前面展示的 watched_keys 將被修改成這個(gè)樣子: 通過 watched_keys 字典,, 如果程序想檢查某個(gè)鍵是否被監(jiān)視, 那么它只要檢查字典中是否存在這個(gè)鍵即可,; 如果程序要獲取監(jiān)視某個(gè)鍵的所有客戶端,, 那么只要取出鍵的值(一個(gè)鏈表), 然后對(duì)鏈表進(jìn)行遍歷即可,。 WATCH 的觸發(fā)在任何對(duì)數(shù)據(jù)庫鍵空間(key space)進(jìn)行修改的命令成功執(zhí)行之后 (比如 FLUSHDB ,、 SET ,、 DEL ,、 LPUSH 、 SADD ,、 ZREM ,,諸如此類), multi.c/touchWatchedKey 函數(shù)都會(huì)被調(diào)用 —— 它檢查數(shù)據(jù)庫的 watched_keys 字典,, 看是否有客戶端在監(jiān)視已經(jīng)被命令修改的鍵,, 如果有的話, 程序?qū)⑺斜O(jiān)視這個(gè)/這些被修改鍵的客戶端的 REDIS_DIRTY_CAS 選項(xiàng)打開: 當(dāng)客戶端發(fā)送 EXEC 命令,、觸發(fā)事務(wù)執(zhí)行時(shí),, 服務(wù)器會(huì)對(duì)客戶端的狀態(tài)進(jìn)行檢查:
可以用一段偽代碼來表示這個(gè)檢查: 舉個(gè)例子,,假設(shè)數(shù)據(jù)庫的 watched_keys 字典如下圖所示: 如果某個(gè)客戶端對(duì) key1 進(jìn)行了修改(比如執(zhí)行 DEL key1 ),, 那么所有監(jiān)視 key1 的客戶端, 包括 client2 ,、 client5 和 client1 的REDIS_DIRTY_CAS 選項(xiàng)都會(huì)被打開,, 當(dāng)客戶端 client2 、 client5 和 client1 執(zhí)行 EXEC 的時(shí)候,, 它們的事務(wù)都會(huì)以失敗告終,。 最后,當(dāng)一個(gè)客戶端結(jié)束它的事務(wù)時(shí),,無論事務(wù)是成功執(zhí)行,,還是失敗, watched_keys 字典中和這個(gè)客戶端相關(guān)的資料都會(huì)被清除,。 事務(wù)的 ACID 性質(zhì)勘誤:Redis 的事務(wù)是保證原子性的,,本節(jié)的內(nèi)容將原子性和回滾功能混淆了,等待修復(fù)中,。 —— 2013.6.23 在傳統(tǒng)的關(guān)系式數(shù)據(jù)庫中,,常常用 ACID 性質(zhì)來檢驗(yàn)事務(wù)功能的安全性。 Redis 事務(wù)保證了其中的一致性(C)和隔離性(I),,但并不保證原子性(A)和持久性(D),。 以下四小節(jié)是關(guān)于這四個(gè)性質(zhì)的詳細(xì)討論。 原子性(Atomicity)單個(gè) Redis 命令的執(zhí)行是原子性的,,但 Redis 沒有在事務(wù)上增加任何維持原子性的機(jī)制,,所以 Redis 事務(wù)的執(zhí)行并不是原子性的。 如果一個(gè)事務(wù)隊(duì)列中的所有命令都被成功地執(zhí)行,,那么稱這個(gè)事務(wù)執(zhí)行成功,。 另一方面,如果 Redis 服務(wù)器進(jìn)程在執(zhí)行事務(wù)的過程中被停止 —— 比如接到 KILL 信號(hào),、宿主機(jī)器停機(jī),,等等,那么事務(wù)執(zhí)行失敗,。 當(dāng)事務(wù)失敗時(shí),,Redis 也不會(huì)進(jìn)行任何的重試或者回滾動(dòng)作。 一致性(Consistency)Redis 的一致性問題可以分為三部分來討論:入隊(duì)錯(cuò)誤,、執(zhí)行錯(cuò)誤,、Redis 進(jìn)程被終結(jié),。 入隊(duì)錯(cuò)誤在命令入隊(duì)的過程中,如果客戶端向服務(wù)器發(fā)送了錯(cuò)誤的命令,,比如命令的參數(shù)數(shù)量不對(duì),,等等, 那么服務(wù)器將向客戶端返回一個(gè)出錯(cuò)信息,, 并且將客戶端的事務(wù)狀態(tài)設(shè)為 REDIS_DIRTY_EXEC ,。 當(dāng)客戶端執(zhí)行 EXEC 命令時(shí), Redis 會(huì)拒絕執(zhí)行狀態(tài)為 REDIS_DIRTY_EXEC 的事務(wù),, 并返回失敗信息,。 因此,帶有不正確入隊(duì)命令的事務(wù)不會(huì)被執(zhí)行,,也不會(huì)影響數(shù)據(jù)庫的一致性,。 執(zhí)行錯(cuò)誤如果命令在事務(wù)執(zhí)行的過程中發(fā)生錯(cuò)誤,比如說,,對(duì)一個(gè)不同類型的 key 執(zhí)行了錯(cuò)誤的操作,, 那么 Redis 只會(huì)將錯(cuò)誤包含在事務(wù)的結(jié)果中, 這不會(huì)引起事務(wù)中斷或整個(gè)失敗,,不會(huì)影響已執(zhí)行事務(wù)命令的結(jié)果,,也不會(huì)影響后面要執(zhí)行的事務(wù)命令, 所以它對(duì)事務(wù)的一致性也沒有影響,。 Redis 進(jìn)程被終結(jié)如果 Redis 服務(wù)器進(jìn)程在執(zhí)行事務(wù)的過程中被其他進(jìn)程終結(jié),,或者被管理員強(qiáng)制殺死,那么根據(jù) Redis 所使用的持久化模式,,可能有以下情況出現(xiàn):
隔離性(Isolation)Redis 是單進(jìn)程程序,并且它保證在執(zhí)行事務(wù)時(shí),,不會(huì)對(duì)事務(wù)進(jìn)行中斷,,事務(wù)可以運(yùn)行直到執(zhí)行完所有事務(wù)隊(duì)列中的命令為止。因此,,Redis 的事務(wù)是總是帶有隔離性的,。 持久性(Durability)因?yàn)槭聞?wù)不過是用隊(duì)列包裹起了一組 Redis 命令,并沒有提供任何額外的持久性功能,,所以事務(wù)的持久性由 Redis 所使用的持久化模式?jīng)Q定:
小結(jié)
|
|