Redis從入門到精通(五、Redis的事務(wù))

Redis通過 MULTI,EXEC,DISCARD,WATCH.UNWATCH 來實現(xiàn)事務(wù)功能。

Redis 事務(wù)介紹

提到事務(wù),我們可能馬上會想到傳統(tǒng)的關(guān)系型數(shù)據(jù)庫中的事務(wù),客戶端首先向服務(wù)器發(fā)送 BEGIN 開啟事務(wù),然后執(zhí)行讀寫操作,最后用戶發(fā)送 COMMIT 或者 ROLLBACK 來提交或者回滾之前的操作。但是Redis中的事務(wù)與關(guān)系型數(shù)據(jù)庫是不一樣的,Redis 通過 MULTI 命令開始,之后輸入一連串的操作,最終以 EXEC 結(jié)束,在這之間輸入的所有的命令都會在 EXEC 之后一起發(fā)給Redis執(zhí)行,所以在這之間用戶無法通過讀取到的結(jié)果做處理,這與關(guān)系型數(shù)據(jù)庫的事務(wù)是由很大的不同的。Redis會在執(zhí)行完成之后返回一組執(zhí)行結(jié)果。Redis中并沒有回滾的操作,這一點會在后面說到。

Redis的這種延遲執(zhí)行事務(wù)會有助于提升性能,客戶端會在收到 EXEC 命令之后再將這一系列的命令一起發(fā)給Redis,然后等待Redis的回復(fù),這種 一次性發(fā)送多條指令,然后等待回復(fù) 的做法稱為流水線(pipeline)模式,它可以通過減少客戶端與服務(wù)器之間的網(wǎng)絡(luò)通信次數(shù)來提高Redis執(zhí)行多個命令的性能。

Redis通過以下兩點保證事務(wù):

  • 事務(wù)中的所有命令都被序列化并按順序執(zhí)行,在執(zhí)行事務(wù)的過程中不會去執(zhí)行其他客戶端的命令,保證命令作為單個隔離操作進行
  • 要么處理所有命令,要么不處理。保證原子性。如果開啟了AOF,Redis會使用單個write命令將事務(wù)寫入文件中,如果因為某些原因?qū)е翧OF寫入被截斷,在重啟時redis會報錯,使用 redis-check-aof 工具可以修復(fù)這個錯誤(刪除掉這個事務(wù)相關(guān)的命令),保證Redis能夠重新啟動

Redis 事務(wù)示例

下面我們來看一些示例:

MULTI EXEC

127.0.0.1:6379[2]> set foo 1
OK
127.0.0.1:6379[2]> set bar 1
OK
127.0.0.1:6379[2]> MULTI
OK
127.0.0.1:6379[2]> INCR foo
QUEUED
127.0.0.1:6379[2]> INCR bar
QUEUED
127.0.0.1:6379[2]> EXEC
1) (integer) 2
2) (integer) 2
127.0.0.1:6379[2]>

可以看到在執(zhí)行 MULTI 之后會返回 OK 表示狀態(tài)回復(fù),然后執(zhí)行兩個 INCR 操作,會返回 QUEUED 表示已經(jīng)進入到隊列當(dāng)中,最后執(zhí)行 EXEC 命令,上述所有命令會一起發(fā)送到Redis,然后收到Redis的一組回復(fù)。

DISCARD

127.0.0.1:6379[2]> MULTI
OK
127.0.0.1:6379[2]> set test 09876
QUEUED
127.0.0.1:6379[2]> DISCARD
OK
127.0.0.1:6379[2]> get test
"1234"
127.0.0.1:6379[2]>

DICARD 可以取消事務(wù)

命令出現(xiàn)語法錯誤

下面來看以下如果這其中有語法錯誤的命令會怎么樣:

127.0.0.1:6379[2]> MULTI
OK
127.0.0.1:6379[2]> set test 1234
QUEUED
127.0.0.1:6379[2]> lpush test 12345
QUEUED
127.0.0.1:6379[2]> EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379[2]> get test
"1234"

可以看到,最終返回結(jié)果是set 命令執(zhí)行成功,而 lpush 命令執(zhí)行失敗,通過 get test 命令,可以看到它的值是1234??梢钥吹?即使后續(xù)的命令出現(xiàn)了錯誤,前面已經(jīng)執(zhí)行成功的命令也不會回滾,同樣也不會影響后續(xù)命令。

Redis事務(wù)不支持回滾

Redis認為只有語法出現(xiàn)錯誤時才會導(dǎo)致事務(wù)的失敗,并且Redis的速度夠快,不需要回滾的能力。Redis官方給出的解釋是(我做了一下翻譯):

如果你有關(guān)系型數(shù)據(jù)庫的相關(guān)經(jīng)驗,實際上Redis命令在事務(wù)期間可能會出現(xiàn)失敗的情況,但是Redis仍然執(zhí)行了事務(wù)中剩余的命令而不是回滾,在你看來這可能很荒謬。

但是對于這種操作有以下很好的見解:

  • Redis 命令只有在出現(xiàn)語法錯的情況下才會導(dǎo)致失敗(這個問題沒辦法再入隊列期間檢測到),或者這個key是錯誤的數(shù)據(jù)類型: 這意味著是編程錯誤造成的命令失敗,在開發(fā)過程中就應(yīng)該檢查到這種錯誤中,而不是到生產(chǎn)中才發(fā)現(xiàn)
  • Redis 內(nèi)部簡單而且速度很快,不需要回滾的能力

一種反對Redis的觀點是bug是會發(fā)生的,但是通常回滾并不能解決編程錯誤所造成的結(jié)果.例如,如果查詢一個key并遞增了2而不是1,或者遞增了錯誤的key,回滾機制將沒辦法提供幫助.考慮到?jīng)]有人解決編程錯誤,而且Redis命令的失敗并不太可能進入生產(chǎn)環(huán)境,所以我們選擇了不支持事務(wù)回滾的更快,更簡單的做法.

WATCH命令的使用

Redis使用WATCH 來解決key的競爭問題,類似于 CAS 操作,來保證多個客戶端同時修改一個key的情況,只能有一個客戶端修改成功。

我用下面的示例演示一下A,B兩個客戶端競爭一個Key的情況:

Client A

127.0.0.1:6379[2]> GET count
"1"
127.0.0.1:6379[2]> WATCH count
OK
127.0.0.1:6379[2]> MULTI
OK
127.0.0.1:6379[2]> incr count
QUEUED
127.0.0.1:6379[2]> incr count
QUEUED
127.0.0.1:6379[2]> EXEC
(nil)

Client B

127.0.0.1:6379[2]> incr count
(integer) 2

在A客戶端WATCH count之后,如果B客戶端執(zhí)行了修改count 這個key的操作,那么A客戶端在 EXEC 之后會返回 nil 沒有進行任何操作。

我們在來看一組沒有競爭的情況:

127.0.0.1:6379[2]> get count
"3"
127.0.0.1:6379[2]> WATCH count
OK
127.0.0.1:6379[2]> MULTI
OK
127.0.0.1:6379[2]> INCR count
QUEUED
127.0.0.1:6379[2]> INCR count
QUEUED
127.0.0.1:6379[2]> EXEC
1) (integer) 4
2) (integer) 5

在沒有多個客戶端競爭的情況下,事務(wù)正常執(zhí)行。

Redis并沒有用典型的加鎖功能來解決key的競爭問題,主要原因是出于性能的考慮?;仡櫼幌玛P(guān)系型數(shù)據(jù)庫中的事務(wù),在訪問以寫入為目的的數(shù)據(jù)時,數(shù)據(jù)庫會對被訪問的數(shù)據(jù)加鎖,直到提交或回滾之后才釋放鎖,如果此時另一個客戶端也這部分數(shù)據(jù)進行寫入操作,客戶端將會被阻塞,直到上一個事務(wù)結(jié)束。這種加鎖的方式稱為悲觀鎖,它的缺點在于持有鎖的客戶端持有鎖的時間越長,其它客戶端被阻塞的時間就越長。Redis為了減少客戶端等待的時間,并不會在執(zhí)行WATCH 命令后對數(shù)據(jù)進行加鎖,而是如果有其他客戶端搶先修改了數(shù)據(jù)的情況下通知執(zhí)行了 WATCH 的客戶端,這種做法叫做樂觀鎖。我們只需在客戶端執(zhí)行事務(wù)失敗之后進行重試的邏輯即可。


更多詳細的資料參考:

Redis事務(wù)官方文檔

Redis實戰(zhàn)


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容