Redis 事務簡介
稍微瞭解 Redis 的朋友都知道,Redis 也提供了事務功能。但是 Redis 的事務和我們平時熟悉的關係型資料庫中的事務是有區別的。
Redis 事務的本質是一組命令的集合:一個事務中所有命令都會被序列化到一個佇列中,在事務執行過程,會按照順序序列執行佇列中的命令,這些命令要麼全部得到執行,要麼全部不執行。另外,其他客戶端提交的命令請求不會插入到事務執行命令序列中。
Redis 事務的使用
Redis 事務的基礎是MULTI
、EXEC
、DISCARD
和WATCH
等命令。
這些命令的使用方式如下:
# 開啟事務
MULTI
command1
command2
# 觸發事務
EXEC
MULTI
命令用於開啟一個事務,它總是返回 OK
。MULTI
執行之後, 客戶端可以繼續向伺服器傳送任意多條命令, 這些命令不會立即被執行, 而是被放到一個佇列中。
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 服務端是怎麼處理那些已經進入佇列的命令的?