Redis 中的事務

程式設計師自由之路發表於2020-10-10

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 服務端是怎麼處理那些已經進入佇列的命令的?

參考

相關文章