相關命令: DISCARD EXEC MULTI UNWATCH WATCH 事務
MULTI,EXEC,DISCARD和WATCH是Redis事務的基礎。它們允許在一個步驟中執行一組命令,並有兩個重要的保證: 事務中的所有命令都會被序列化並按順序執行。在執行Redis事務的過程中,不會發生由另一個用戶端發出的請求被服務的情況。這保證命令作為一個單獨的隔離的操作被執行。 無論是所有的命令都被處理還是沒有命令被處理,Redis事務都是原子的。EXEC命令觸發事務中所有命令的執行,因此如果在調用MULTI命令之前,用戶端在事務上下文中失去與伺服器的串連,則不執行任何操作,相反如果調用EXEC命令, 所有的操作都被執行。在使用僅追加檔案時,Redis確保使用單個寫入(2)系統調用將事務寫入磁碟。但是,如果Redis伺服器崩潰或被系統管理員以某種強制的方式殺死,可能只有部分操作被註冊。Redis將在重新啟動時檢測到這種情況,並會在出現錯誤時退出。使用redis-check-aof工具可以刪除部分事務修複僅追加檔案,以便伺服器可以重新啟動。
從版本2.2開始,Redis允許為上述兩項提供額外保證,採用與check-and-set(CAS)操作非常相似的樂觀鎖定形式。這將在本頁後面記錄。 用法
使用MULTI命令進入Redis事務。該命令總是以OK回應。此時使用者可以發出多個命令。Redis不會執行這些命令,而是將它們排隊。EXEC被調用後,所有的命令都會被執行。
調用DISCARD而不是重新整理事務隊列並將退出事務。
以下樣本以原子方式遞增key foo和bar。
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1
從上面的會話中可以看出,EXEC返回一個響應數組,其中每個元素都是事務中單個命令的響應,順序與命令的發出順序相同。
當Redis串連處於MULTI請求的上下文中時,所有命令將以字串QUEUED(從Redis協議的角度作為狀態回複發送)進行回複。EXEC被調用時,排隊的命令被簡單地安排執行。 事務中的錯誤
事務過程中,可能會遇到兩種命令錯誤: 命令可能無法排隊,因此在調用EXEC之前可能會出現錯誤。例如,命令可能在文法上是錯誤的(參數數量錯誤,錯誤的命令名稱…),或者可能存在某些關鍵條件,如記憶體不足的情況(如果伺服器配置為使用maxmemory 指示)。 例如,因為我們針對具有錯誤值的key執行操作(例如,針對字串值調用list操作),所以命令可能在調用EXEC後失敗。
通過檢查排隊命令的傳回值,用戶端用於檢測EXEC調用之前發生的第一種錯誤:如果命令使用QUEUED進行響應,則它已正確排隊,否則Redis將返回錯誤。如果排隊命令時發生錯誤,大多數用戶端將中止該事務並放棄它。
然而,從Redis 2.6.5開始,伺服器會記住在累積命令期間發生錯誤,並拒絕執行EXEC期間返回錯誤的事務,並自動丟棄該事務。
在Redis 2.6.5之前,行為只是在成功排隊的命令子集內執行事務,以防用戶端調用EXEC而不管以前的錯誤。新的行為使得將事務與流水線混合變得更加簡單,因此整個事務可以一次發送,一次讀取所有回複。
EXEC之後發生的錯誤不是以一種特殊的方式處理的:即使某些命令在事務中失敗,也會執行所有其他命令。
這在協議層面更加清晰。 在以下樣本中,即使文法正確,一個命令在執行時也會失敗:
Trying 127.0.0.1…
Connected to localhost.
Escape character is ‘^]’.
MULTI
+OK
SET a 3
abc
+QUEUED
LPOP a
+QUEUED
EXEC
*2
+OK
-ERR Operation against a key holding the wrong kind of value
EXEC返回two-element批量字串回複,其中一個是OK代碼,另一個是-ERR回複。用戶端庫需要找到一種明智的方式將錯誤提供給使用者。
需要注意的是,即使命令失敗,隊列中的所有其他命令也會被處理–Redis不會停止命令的處理。
另一個例子,再次使用telnet協議使用Wire協議,可以顯示語法錯誤是如何報告的:
MULTI
+OK
INCR a b c
-ERR wrong number of arguments for ‘incr’ command
這次由於語法錯誤,錯誤的INCR命令根本沒有排隊。 為什麼Redis不支援復原。
如果您有關聯式資料庫背景,Redis命令在交易處理期間可能會失敗,但Redis將執行其餘事務而不是復原事務,這可能對您來說看起來很奇怪。
但是,對於這種行為有很好的意見: 如果使用錯誤的文法調用Redis命令(並且在命令排隊期間無法檢測到問題),或者針對儲存錯誤資料類型的key,則Redis命令可能會失敗:這意味著實際上,失敗的命令是編程錯誤的結果, 以及在開發過程中很可能檢測到的一種錯誤,而不是在生產中。 Redis內部簡化且速度更快,因為它不需要復原功能。
反對Redis觀點的一個觀點是錯誤發生了,但是應該指出的是一般情況下,復原並不能避免編程錯誤。例如,如果查詢將key增加2而不是1,或增加錯誤的key,則復原機制無法提供協助。鑒於沒有人能夠挽救程式員的錯誤,並且Redis命令失敗所需的錯誤類型不太可能進入生產環境,所以我們選擇了不支援錯誤復原的更簡單快捷的方法。 放棄命令隊列
DISCARD可用於中止交易。在這種情況下,不執行任何命令並且串連狀態恢複正常。
> SET foo 1
OK
> MULTI
OK
> INCR foo
QUEUED
> DISCARD
OK
> GET foo
“1” 樂觀鎖定使用check-and-set
WATCH用於為Redis事務提供check-and-set(CAS)行為。
監視key被監視以檢測對它們的改變。如果在EXEC命令之前至少修改了一個監視的key,則整個事務中止,並且EXEC返回Null回覆以通知事務失敗。
例如,假設我們需要將key的值自動遞增1(讓我們假設Redis沒有INCR)。
第一次嘗試可能如下:
val = GET mykey
val = val + 1
SET mykey $val
只有當我們有一個用戶端在給定時間內執行操作時,這才能可靠地工作。如果多個用戶端嘗試在大約同一時間遞增key,則會出現競爭狀況。例如,用戶端A和B將讀取舊值,例如10,這兩個用戶端的值將遞增為11,最後將SET作為key的值。所以最終的值將是11而不是12。
感謝WATCH,我們能夠很好地類比這個問題:
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
使用上面的代碼,如果存在競爭條件,並且另一個用戶端在我們對WATCH的調用和我們對EXEC的調用之間的時間內修改了val的結果,則事務將失敗。
我們只需要重複這次的操作,希望這次我們不會得到新的競爭。這種形式的鎖定稱為樂觀鎖定,是一種非常強大的鎖定形式。在許多用例中,多個用戶端將訪問不同的key,因此碰撞不太可能發生 - 通常不需要重複該操作。 WATCH說明
那麼WATCH真的是什麼。 這是一個使EXEC有條件的命令:只有在沒有任何WATCHed key被修改的情況下,我們才會要求Redis執行事務。 (但是它們可能會被事務中的同一個用戶端改變而不會中止它,更多的是這個)否則,事務根本不會進入。(請注意,如果您WATCH易失性key並且Redis在您WATCH該key後到期了該key,那麼EXEC將繼續工作。更多的是這個。)
WATCH可以多次調用。簡單地說,所有的WATCH調用都將具有WATCH從調用開始發生變化的效果,直到EXEC被調用。您也可以將任意數量的key發送到單個WATCH調用。
當調用EXEC時,無論事務是否中止,所有key都是UNWATCHed。此外,當用戶端串連關閉時,所有都會被UNWATCHed。
也可以使用UNWATCH命令(無參數)來重新整理所有watch的key。
有時候,我們樂觀地鎖定了幾個key,這很有用,因為可能我們需要執行一個事務來改變這些key,但是在讀完了key的當前內容之後我們不想繼續。發生這種情況時,我們只需調用UNWATCH,以便串連可以自由用於新事務。 使用WATCH來實現ZPOP
舉一個很好的例子來說明如何使用WATCH來建立新的原子操作,否則Redis不支援實現ZPOP,即以原子方式從排序集合中以較低分數彈出元素的命令。這是最簡單的實現:
WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC
如果EXEC失敗(即返回空回複),我們只需重複該操作。 Redis指令碼和事務
根據定義,Redis指令碼是事務性的,因此您可以使用Redis事務執行的所有操作都可以通過指令碼完成,通常指令碼將更簡單快捷。
這種重複是由於在Redis 2.6中引入了指令碼,而事務早已存在。然而,我們不可能在短時間內取消對事務的支援,因為即使不採用Redis指令碼編寫,仍然可以避免競爭狀況,尤其是因為Redis事務的實施複雜性最低,這在語義上似乎是恰當的。
然而,在不遠的將來,我們將看到整個使用者群只是使用指令碼,這並非不可能。如果發生這種情況,我們可能會棄用並最終刪除事務。