redis——redis事務相關處理
事務
Redis事務的相關命令有MULTI,EXEC,DISCARD,WATCH。它們允許在一個步驟中執行一組命令,並有兩個重要的保證:
事務中的所有命令都會被序列化並按順序執行。在執行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事務的實施複雜性最低,這在語義上似乎是恰當的。
然而,在不遠的將來,我們將看到整個使用者群只是使用指令碼,這並非不可能。如果發生這種情況,我們可能會棄用並最終刪除事務。