《Redis官方文件》事務
事務
MULTI
、 EXEC
、 DISCARD
和 WATCH
是 Redis 事務的基礎。事務允許一次獨立的執行一組命令,並且擁有兩個重要的保證。
- Redis事務的執行是單步的獨立的操作:所有的在事務中的命令都是序列化和順序地。它在執行事務中永遠不會被另一個客戶端打斷。
- Redis的事務是原子性的:所有的命令,要麼全部執行,要麼全部不執行。
- EXEC的命令觸發執行Redis事務中的所有命令,所以如果這個客戶端之前呼叫了
MULTI
命令卻斷開了redis事務中的連線,那麼這個事務的將不會被執行。 - 當我們使用了AOF序列化(
append-only-file
)時,Redis會確保去使用單獨的同步Write(2)
Redis-Check-AOF
工具移除部分的事務,去修復這個AOF檔案,所以Redis可以再次啟動。
- EXEC的命令觸發執行Redis事務中的所有命令,所以如果這個客戶端之前呼叫了
從Redis2.2版本開始,redis有了一個額外的保證,check-and-set
(CAS
)操作形式非常像樂觀鎖。
CAS
的文件就在這一頁
用法
Redis事務從你輸入MULTI
命令開始,這個命令總是答覆OK
。從這個時候開始使用者可以提出多種命令,Redis將會把他們入隊,而不是執行這些命令。所有的命令只有在EXEC
呼叫之後才會執行。
呼叫DISCARD
命令將會重新整理事務中的佇列並退出這個事務。
這裡有個一個原子增長foo
和bar
鍵例子
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1
在這個回話之後可能看到EXEC
返回一個答覆陣列,數組裡面的每一個元素的順序和事務中命令傳送的相同。
當Redis連線到一個MULTI
請求上下文環境中,所有的命令傳送後都會得到一個字串答覆QUEUED
(這是redis事務協議的傳送一個狀態答覆)。當EXEC
事務中的錯誤
在事務中可能會碰到的兩種命令錯誤
- 一個可能是命令入隊失敗,這個在命令在執行呼叫之前會發生錯誤。例如,這個命令可能有語法錯誤(錯誤的引數數量,錯誤的命令名),或者其他某些緊急的狀態,如記憶體溢位(如果redis配置檔案配置了記憶體最大限制指令)
- 一個可能是執行呼叫之後失敗,例如,從我們施行了一個由於錯誤的value的key操作(例如對著String型別的value施行了List命令操作)
客戶端發生了第一個錯誤情況,在exec執行之前發生的。通過檢查佇列命令返回值:如果這個命令回答這個佇列的命令是正確的,否者redis會返回一個錯誤。如果那裡發生了一個佇列命令錯誤,大部分客戶端將會退出並丟棄這個事務。
然後從reids2.6.5開始,redis服務將會記住在這塊命令中有一個錯誤,並且將會拒絕執行這個事務並在執行時返回一個錯誤,和自動退出這個事務。
在redis2.6.5之前,客服端只會執行那些成功的佇列命令子集,以免客戶端執行先前被忽略的錯誤。這個新的行為使得最小的流水線事務變得更加容易,所以這個事務會被同時傳送,讀取所有的應答。
至於那些錯誤發生在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
返回了兩個批量答覆,一個是OK
,另一個是-ERR
,至於怎樣處理這個錯誤,取決於客戶端。
重點:儘管當一個命令失敗了,佇列的所有的其他命令也會被執行 – redis並不會停止執行其他的命令。
另一個例子,當事務中的命令在入隊時,發生語法錯誤,會立刻報告。
MULTI
+OK
INCR a b c
-ERR wrong number of arguments for 'incr' command
這次是由於錯誤的INCR
命令具有語法錯誤無法入隊。
問什麼redis不支援事務回滾
如果你相關的資料庫經驗,因為事務中的命令可能會失敗,但redis事務還會執行其他的,而不是事務回滾,這對你來說可能有些奇怪
但是對於這種行為有好的觀點
- Redis的命令是會失敗的,如果使用了錯誤的語法(這個命令入隊時無法被檢測錯誤),或者使用了錯誤的資料型別的鍵:這就意味著實際上錯誤的命令會返回一個程式錯誤的答覆。
- Redis的內部極其簡單和快速,來源於它不需要回滾功能。
一個說會產生Bug論據的反對Redis觀點,但是需要注意的是,通常回滾並不能解決來自程式設計的錯誤。舉個例子,你本來想+1,卻+2了,又或者+在錯誤的型別上。回滾並不能解決。由於無法提供一個避免程式設計師自己的錯誤,而這種錯誤在產品中並不會出現,所以我們選擇了一個簡單和快速的方法去支援回滾這個事務。
放棄事務
DISCARD
被使用會忽略這個事務,在這種情況下,不執行命令,連線狀態恢復正常。
> SET foo 1
OK
> MULTI
OK
> INCR foo
QUEUED
> DISCARD
OK
> GET foo
"1"
使用 check-and-set 操作實現樂觀鎖
WATCH
命令為Redis事務提供了CAS行為
被WATCH
監控的值,會被檢測是否被改變。如果有一個以上的值在EXEC
呼叫前被改變,那麼整個事務將會被取消。EXEC
會會犯多個空答覆(@nil-reply
)來表示這個事務早就失敗了
舉個例子,假設我們需要原子操作為一個值+1,這個(假設這個增加的值不存在)。
第一步像這樣
val = GET mykey
val = val + 1
SET mykey $val
上面這個實現可能在一個客戶端中表現的很好。但是如果多個客戶端同時對這個鍵操作,那麼就會產生競爭。
舉個例子,客戶端A和B都讀到了老的值10,去加+1,
兩個客戶端都SET key 11 .那麼最後這個值可能變成11,而不是12
WATCH
就可以很好的解決這個問題
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
使用上面的程式碼,如果這裡和其他的客戶端產生了競爭去修改這個這個VAL
的值。
在我們呼叫WATCH
和EXEC
後,這個事務就失敗
我們僅僅需要去重複這整個操作就可以了,這就是強大的樂觀鎖。
在許多例子中,多個客戶端將會訪問不同的KEY,所以很少發生衝突,通常我們不需要重複這個操作
WATCH
說明
那麼WATCH
究竟是怎麼樣的? EXEC
命令執行需要有條件: 事務只能在所有被監視鍵都沒有被修改的前提下(但是他們可能同時更改它在相同的客戶端事務下而沒有中斷事務詳情.)執行, 如果這個前提不能滿足的話,事務就不會被執行。
可以多次呼叫WATCH
。 所有的“WATCH”呼叫後都開始檢測改變,直到“EXEC”被呼叫的時刻。一個WATCH
可以同時檢測多個KEY
當EXEC
被執行後,所有的key都會被取消檢測UNWATCH
,不論這個事務是否結束。當然如果這個客戶端斷開了,一切都會UNWATCH
也可以使用無引數UNWATCH
命令來取消所有被監視的鍵。有時,這是有用的,因為我們使用了樂觀鎖鎖定幾個鍵,因為可能我們需要執行一個事務來更改這些鍵,但是在讀取後我們不想繼續的監控鍵的當前內容之後。 當這種情況發生時,我們只需呼叫“UNWATCH”,以便其他連線可以自由使用新的事務了。
使用 WATCH
去實現ZPOP(ZSET)
舉個例子說明怎樣使用WATCH
,可以建立一個新的原子操作,另外去實現redis不支援的ZPOP。
這是一個從a中彈出分數較低的原子命令實現方式。這也是最簡單的方法。
WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC
如果EXEC
執行失敗(返回了 @nil-reply),我們需要重複這個操作
Redis 指令碼和事務
由於複製的原因redis2.6一直才支援指令碼,然而事務卻已經存在很久了。不過我們並不打算在短時間內就移除事務功能, 因為事務提供了一種即使不使用指令碼,也可以避免競爭條件的方法, 而且redis事務本身的實現並不複雜。
不過在不遠的將來, 可能所有使用者都會只使用指令碼來實現事務也說不定。 如果真的發生這種情況的話, 那麼我們將廢棄並最終移除事務功能。