天天看點

Redis事務:用法,常見錯誤和API

MULTI

EXEC DISCARD

WATCH

是Redis事務的基礎。它們允許在一個步驟中執行一組指令,并有兩個重要的保證:

  • 事務中的所有指令都被序列化并按順序執行。在執行Redis事務的過程中,不會發生由另一個用戶端發出的請求。這保證了指令作為一個單獨的操作被執行。
  • 要麼所有的指令都沒有被處理、要麼沒有指令被執行,是以Redis事務也是原子的。 指令,觸發事務中的所有指令的執行,是以,如果用戶端在執行 指令前,在事務的上下文中丢失與伺服器的連接配接,沒有指令會被執行,然而如果 指令被調用時,所有的操作都被執行。使用 append-only file  ,Redis確定使用單個寫操作,系統調用将事務寫入磁盤。但是,如果Redis伺服器崩潰或被系統管理者以某種方式殺死,則可能隻有部分指令被執行。Redis會在重新啟動時檢測到這種情況,會退出并顯示錯誤資訊。使用該

    redis-check-aof

    工具,可以修複将删除部分事務的append only file,以便伺服器可以重新啟動。

從版本2.2開始,Redis允許為上述兩個提供額外的保證,采用與 check-and-set (CAS) 操作非常相似的樂觀鎖定形式。

Redis事務--用法

使用

指令開啟Redis事務。該指令總是回複

OK

。此時使用者可以發出多個指令。Redis不會執行這些指令,而是将它們排隊。一旦

被調用,所有的指令被執行。

調用

重新整理事務隊列,并且退出事務。

以下示例遞增鍵

foo

bar

原子。

  1. > MULTI

  2. OK

  3. > INCR foo

  4. QUEUED

  5. > INCR bar

  6. QUEUED

  7. > EXEC

  8. 1) (integer) 1

  9. 2) (integer) 1

從上面的會話中可以看出,

EXEC

傳回一個響應數組,其中每個元素都是事務中單個指令的回複,這與指令發出的順序相同。

當Redis連接配接處于

MULTI

請求的上下文中時,所有指令都将回複該字元串

QUEUED

(從Redis協定的角度來看,這是作為狀态回複的發送)。當

EXEC

被調用時,queued指令被有計劃地執行。

事務中的錯誤

在事務過程中,可能遇到兩種指令錯誤:

  • 指令可能無法進入隊列,是以在調用

    EXEC

    之前可能會出現錯誤。例如,指令可能在文法上是錯誤的(參數數量錯誤,錯誤的指令名稱...),或者可能存在一些嚴重的情況,例如記憶體不足(如果伺服器被配置為使用該

    maxmemory

    指令,具有記憶體限制)。

  • 在 調用

    EXEC 

    指令之後,指令可能會失敗,例如,我們對具有錯誤值的鍵執行操作(例如,針對string值調用list 操作)。

EXEC

調用之前,用戶端通過檢查排隊指令的傳回值,來感覺第一類錯誤:如果指令使用QUEUED進行響應,則排隊正确,否則Redis傳回錯誤。如果排隊指令時發生錯誤,大多數用戶端将中止放棄該事務。

但是從Redis 2.6.5開始,伺服器會記住在指令不斷累加執行過程中出現的錯誤,将

EXEC

指令期間會拒絕事務,并傳回錯誤,還會自動丢棄事務。

在Redis 2.6.5之前,這種行為隻是在成功排隊的指令子集内執行事務,以防用戶端調用

EXEC

而不管以前的錯誤。新的行為使得将transactions與pipelining,混合在一起變得更加簡單,是以整個事務可以一次發送,一次讀取所有的回複。

在 

EXEC 

之後發生的錯誤不是以一種特殊的方式處理的:即使某些指令在事務中失敗,所有其他的指令也會被執行。

這在協定層面更加清晰。在以下示例中,即使文法正确,一個指令在執行時也會失敗:

  1. Trying 127.0.0.1...

  2. Connected to localhost.

  3. Escape character is '^]'.

  4. MULTI

  5. +OK

  6. SET a 3

  7. abc

  8. +QUEUED

  9. LPOP a

  10. +QUEUED

  11. EXEC

  12. *2

  13. +OK

  14. -ERR Operation against a key holding the wrong kind of value

EXEC

傳回了兩個元素的

Bulk string reply

,其中一個是

OK

代碼,另一個是

-ERR

答複。這是由用戶端庫找到一個明智的方式,來提供錯誤給使用者。

需要注意的是,即使指令失敗,隊列中的所有其他指令也會被處理 --Redis 不會停止指令的處理。

另一個例子,再次用telnet使用Wire協定,顯示了如何報告文法錯誤:

  1. MULTI

  2. +OK

  3. INCR a b c

  4. -ERR wrong number of arguments for 'incr' command

  • 這次由于文法錯誤,錯誤的

    INCR

    指令根本沒有排隊。

為什麼Redis不支援復原(roll backs)?

如果您有關系資料庫背景,那麼Redis指令在事務處理期間可能會失敗,但Redis仍然會執行事務的其餘部分而不是復原事務,這可能對您來說看起來很奇怪。

但是對于這種行為有很好的觀點:

  • 如果使用錯誤的文法調用Redis指令(并且在指令排隊期間無法檢測到問題),或者針對儲存錯誤資料類型的鍵,則Redis指令可能會失敗:這意味着,實際上,失敗的指令是程式設計錯誤的結果,以及在開發過程中很可能被檢測到的一種錯誤,而不是在生産中。

  • Redis的内部簡化和更快,因為它不需要復原的能力。

反對Redis觀點的一個觀點是錯誤發生了,但是應該注意的是一般情況下復原并不能避免程式設計錯誤。例如,如果一個查詢增加了一個鍵而不是1,或者增加了錯誤的鍵,那麼復原機制就沒有辦法提供幫助。鑒于沒有人能夠挽救程式員的錯誤,并且Redis指令失敗所需的錯誤類型不太可能進入生産環境,是以我們選擇了不支援錯誤復原的更簡單快捷的方法。

DISCARD

指令

https://redis.io/commands/discard

可以使用

DISCARD

來中止事務。在這種情況下,不執行任何指令,并且連接配接狀态恢複正常。

  1. > SET foo 1

  2. OK

  3. > MULTI

  4. OK

  5. > INCR foo

  6. QUEUED

  7. > DISCARD

  8. OK

  9. > GET foo

  10. "1"

樂觀鎖定使用check-and-set

WATCH

用于為Redis事務提供檢查和設定(check-and-set)(CAS)行為。

被WATCH

監控的鍵,可以檢測他們的變化。如果在

EXEC

指令之前,至少修改了一個被監視的鍵( watched key ),則整個事務将中止,

EXEC

傳回一個

Null reply

以通知事務失敗。

例如,假設我們需要将鍵的值自動遞增1(讓我們假設Redis沒有

INCR

)。

第一次嘗試可能如下:

  1. val = GET mykey

  2. val = val + 1

  3. SET mykey $val

隻有當我們有一個用戶端在特定的時間執行操作時,這才能可靠地工作。如果多個用戶端嘗試在大約同一時間遞增key,則會出現競争狀況。例如,用戶端A和B将讀取舊值,例如10,兩個用戶端的值将遞增為11,最後将

SET設定

為鍵的值。是以最終的價值将是11而不是12。

感謝

WATCH,

我們能夠很好地模拟這個問題:

  1. WATCH mykey

  2. val = GET mykey

  3. val = val + 1

  4. MULTI

  5. SET mykey $val

  6. EXEC

使用上面的代碼,如果有競争條件,另一個用戶端在我們調用

WATCH

和調用

EXEC

之間,修改val的值,事務将失敗。

我們不得不重複這個操作,希望這次我們不會再有新的競争。這種形式的鎖定稱為樂觀鎖定,是一種非常強大的鎖定形式。在許多用例中,多個用戶端将通路不同的key,是以碰撞是不太可能的 - 通常不需要重複操作。

  1. 悲觀鎖(Pessimistic Lock), 

  2. 顧名思義,就是很悲觀,每次去拿資料的時候都認為别人會修改,是以每次在拿資料的時候都會上鎖,

    這樣别人想拿這個資料就會block直到它拿到鎖。

  3. 傳統的關系型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。

  4. 樂觀鎖(Optimistic Lock), 

  5. 顧名思義,就是很樂觀,每次去拿資料的時候都認為别人不會修改,是以不會上鎖,但是在更新的時候會判斷一下在此期間别人有沒有去更新這個資料,

    可以使用版本号等機制。

  6. 樂觀鎖适用于多讀的應用類型,這樣可以提高吞吐量,像資料庫如果提供類似于write_condition機制的其實都是提供的樂觀鎖。

  7. 兩種鎖各有優缺點,

  8. 不可認為一種好于另一種,像樂觀鎖适用于寫比較少的情況下,即沖突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。

  9. 但如果經常産生沖突,上層應用會不斷的進行retry,這樣反倒是降低了性能,是以這種情況下用悲觀鎖就比較合适。

WATCH

解釋

  • 那麼

    WATCH

    真的是什麼?

  • 這是一個使

    EXEC

    有條件的指令:隻有在任何被

    WATCH的

    鍵沒有被修改的情況下,我們才會要求Redis執行事務。(但是,他們可能會被事務内相同的用戶端沒有放棄它而改變。)否則事務不會進入的。(請注意:如果您

    WATCH

    一個存在有效期的鍵<volatile key>,鍵過期後,

    EXEC

    仍然可以工作。)

  • WATCH

    可以被多次調用。簡單地說,所有的

    WATCH

    調用,都将具有監視從調用開始,直到

    EXEC

    被調用期間變化的效果。您也可以将任意數量的key發送給一個

    WATCH

    調用。

  • EXEC

    被調用時,不管事務是否中止,所有的key都被UNWATCH。另外,當一個用戶端連接配接關閉,所有的key也都被

    UNWATCH

  • 也可以使用

    UNWATCH

    指令(不帶參數)來重新整理所有watched keys。有時候,我們樂觀地鎖定了幾個鍵,這是非常有用的,因為可能我們需要執行一個事務來改變這些鍵,但是在讀完這些鍵的目前内容之後,我們不想繼續。發生這種情況時,我們隻需調用

    UNWATCH,

    以便連接配接可以自由地用于新的事務。

使用

WATCH

來實作ZPOP

一個很好的例子來說明

WATCH

如何被用來建立新的原子操作,否則Redis是不支援實作ZPOP,這是一個以原子的方式從一個有序集合中以較低分數彈出元素的指令。這是最簡單的實作:

  1. WATCH zset

  2. element = ZRANGE zset 0 0

  3. MULTI

  4. ZREM zset element

  5. EXEC

  • 如果

    EXEC

    失敗(即傳回一個

    空的答複

    ),我們隻是重複這個操作。

Redis腳本和事務

  • 一個

    Redis的腳本

    是有事務性的,是以一切都可以用Redis的事務做的,你也可以做一個腳本,通常腳本會更簡單,更快速。

  • 這是由于在Redis 2.6中引入了腳本,事務早已存在。然而,我們不可能在短時間内取消對事務的支援,因為即使不采用Redis腳本,在語義上似乎也是合适的,但仍然有可能避免競争狀況,特别是因為Redis事務的實施複雜性是最小的。

  • 然而,在不遠的将來,我們将看到整個使用者群隻是使用腳本。如果發生這種情況,我們可能會棄用并最終删除事務。

  • 更多腳本内容:

    http://blog.csdn.net/fly910905/article/details/78955343

Redis事務指令

  1. MULTI:類似于mysql中的BEGIN; 标記一個事務塊的開始。

  2. EXEC:類似于COMMIT; 執行所有事務塊内的指令。

  3. DISCARD:類似于ROLLBACK;取消事務,放棄執行事務塊内的所有指令。

  4. WATCH key [key ...] : 則是用于來實作mysql中類似鎖的功能。 監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他指令所改動,那麼事務将被打斷。

  5. UNWATCH: 

    取消 WATCH 指令對所有 key 的監視。

來源: 

https://redis.io/topics/transactions