Redis 中的事務

Redis 事務簡介

稍微了解 Redis 的朋友都知道,Redis 也提供了事務功能。但是 Redis 的事務和我們平時熟悉的關係型數據庫中的事務是有區別的。

Redis 事務的本質是一組命令的集合:一個事務中所有命令都會被序列化到一個隊列中,在事務執行過程,會按照順序串行執行隊列中的命令,這些命令要麼全部得到執行,要麼全部不執行。另外,其他客戶端提交的命令請求不會插入到事務執行命令序列中。

Redis 事務的使用

Redis 事務的基礎是MULTIEXECDISCARDWATCH等命令。

這些命令的使用方式如下:

# 開啟事務
MULTI
command1
command2
# 觸發事務
EXEC

MULTI 命令用於開啟一個事務,它總是返回 OKMULTI執行之後, 客戶端可以繼續向服務器發送任意多條命令, 這些命令不會立即被執行, 而是被放到一個隊列中。

EXEC命令負責觸發並執行事務中的所有命令,EXEC命令的回復是一個數組, 數組中的每個元素都是執行事務中的命令所產生的回復。 其中, 回復元素的先後順序和命令發送的先後順序一致。

當使用 AOF 方式做持久化的時候, Redis 會令將事務寫入到磁盤中。 然而,如果 Redis 服務器因為某些原因被管理員殺死,或者遇上某種硬件故障,那麼可能只有部分事務命令會被成功寫入到磁盤中。Redis 在重新啟動時發現 AOF 文件出了這樣的問題,那麼它會退出,並彙報一個錯誤。 使用 redis-check-aof 程序可以修復這一問題:它會移除 AOF 文件中不完整事務的信息,確保服務器可以順利啟動。

另外,需要注意的是:如果客戶端在使用 MULTI 開啟了一個事務之後,卻因為斷線而沒有成功執行 EXEC ,那麼事務中的所有命令都不會被執行。

Redis 事務對錯誤的處理

使用事務時可能會遇上以下兩種錯誤:

  • 事務在執行 EXEC 之前,入隊的命令可能會出錯。比如說,命令可能會產生語法錯誤(參數數量錯誤,參數名錯誤,等等),或者其他更嚴重的錯誤,比如內存不足(如果服務器使用 maxmemory 設置了最大內存限制的話)。
  • 命令可能在 EXEC 調用之後失敗。舉個例子,事務中的命令可能處理了錯誤類型的鍵,比如將列表命令用在了字符串鍵上面,諸如此類。

對於在執行 EXEC 之前發生的錯誤,服務器會對命令入隊失敗的情況進行記錄,並在客戶端調用 EXEC 命令時,拒絕執行並自動放棄這個事務。

至於那些在 EXEC 命令執行之後所產生的錯誤, 並沒有對它們進行特別處理: 即使事務中有某個/某些命令在執行時產生了錯誤, 事務中的其他命令仍然會繼續執行。

對於第二種情況,敏感的朋友會問:Redis 發現錯誤時會不會回滾已經執行成功的操作呢?答案是不會。

Redis 給出的理由是:

  • Redis 命令只會因為錯誤的語法而失敗(並且這些問題不能在入隊時發現),或是命令用在了錯誤類型的鍵上面:這也就是說,從實用性的角度來說,失敗的命令是由編程錯誤造成的,而這些錯誤應該在開發的過程中被發現,而不應該出現在生產環境中。
  • 因為不需要對回滾進行支持,所以 Redis 的內部可以保持簡單且快速。

如果我們的程序中確實需要對事務進行回滾,可以結合使用 Lua 腳本來實現事務。簡單的做法是在事務開始前記錄狀態,如果在程序執行過程中發生了異常錯誤,將被修改的數據恢復到之前即可。

watch 命令的使用

WATCH 命令可以為 Redis 事務提供 check-and-set (CAS)行為。

被 WATCH 的鍵會被監視,並會發覺這些鍵是否被改動過了。 如果有至少一個被監視的鍵在 EXEC 執行之前被修改了, 那麼整個事務都會被取消, EXEC 返回空來表示事務已經失敗。

當發生被監控的鍵被改變時,程序需要做的就是不斷重試這個操作, 直到沒有發生碰撞為止。

下面我們來演示下 watch命令的使用:

127.0.0.1:6379> set csx:key:1 test
OK
127.0.0.1:6379> watch csx:key:1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> ping message1
QUEUED
127.0.0.1:6379> ping message2
QUEUED
# 在執行 exec 之前,使用另外一個客戶端將csx:key:1這個鍵的值修改了
127.0.0.1:6379> exec
(nil)

WATCH 使得 EXEC 命令需要有條件地執行: 事務只能在所有被監視鍵都沒有被修改的前提下執行, 如果這個前提不能滿足的話,事務就不會被執行。

如果你使用 WATCH 監視了一個帶過期時間的鍵, 那麼即使這個鍵過期了, 事務仍然可以正常執行。

當 EXEC 被調用時, 不管事務是否成功執行, 對所有鍵的監視都會被取消。另外, 當客戶端斷開連接時, 該客戶端對鍵的監視也會被取消。

使用無參數的 UNWATCH 命令可以手動取消對所有鍵的監視。 對於一些需要改動多個鍵的事務, 有時候程序需要同時對多個鍵進行加鎖, 然後檢查這些鍵的當前值是否符合程序的要求。 當值達不到要求時, 就可以使用 UNWATCH 命令來取消目前對鍵的監視, 中途放棄這個事務, 並等待事務的下次嘗試。

一個問題

當 Redis 客戶端開啟了事務,並且也輸入了幾個命名,但是客戶端因為某些原因斷開了。那麼 Redis 服務端是怎麼處理那些已經進入隊列的命令的?

參考