天天看點

深入了解redis之事務與樂觀鎖的實作

事務:要麼同時成功,要麼同時失敗。

事務的特性:

  • 一組指令的集合!一個事務中的所有指令都會被序列化,在事務執行過程中,會按照順序執行。且不會被其他事務所打斷。
  • 一組事務中的所有操作,要麼全部被執行,要麼全部不執行。

redis單條指令保證原子性,但不保證事務原子性。

Redis事務沒有隔離級别的概念!

所有的指令在事務中,并沒有直接執行,隻有發起執行指令的時候才會執行!

redis的事務流程:

  • 開啟事務( )
  • 指令入隊( )
  • 執行事務( )

multi

:開啟事務:進入事務狀态,之後輸入的所有指令都會被放入隊列中,但暫時都不會被執行。

exec

:執行事務:觸發并執行事務中的所有指令。

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1 
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k1 
QUEUED
127.0.0.1:6379(TX)> set k3 v3 
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v1"
4) OK
           

事務中出錯的兩種情況

對于發生在 EXEC 執行之前的錯誤,用戶端以前的做法是檢查指令入隊所得的傳回值:如果指令入隊時傳回

QUEUED

,那麼入隊成功;否則,就是入隊失敗。如果有指令在入隊時失敗,那麼大部分用戶端都會停止并取消這個事務。

不過,從 Redis 2.6.5 開始,伺服器會對指令入隊失敗的情況進行記錄,并在用戶端調用 EXEC 指令時,拒絕執行并自動放棄這個事務。

至于那些在 EXEC 指令執行之後所産生的錯誤, 并沒有對它們進行特别處理: 即使事務中有某個/某些指令在執行時産生了錯誤, 事務中的其他指令仍然會繼續執行。

  1. 如果程式出錯,是由于編譯問題(文法錯誤) 則事務中的其他指令都不會執行
#編譯問題
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1 
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> get k2
(nil)
           

(error) EXECABORT Transaction discarded because of previous errors表示事務被取消

  1. 如果程式出錯,是由于運作時的問題(比如get一個不存在的值、給不存在的key的值執行自增操作),則事務中的其他指令會正常執行。
#運作時出錯
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 "v1"
QUEUED
127.0.0.1:6379(TX)> incr k1 
QUEUE
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2 
"v2"
127.0.0.1:6379> get k3
"v3"
           

​ 簡單來說,如同過安檢的人一樣一個接着一個排隊過安檢,而安檢隻檢查你看起來像不像一個壞人,如果從開始安檢的所有人裡面有一個看起來像壞人,那麼剛才案件的所有人都不能進站。如果安檢的時候沒發現有壞人,但是進站之後壞人開始做壞事被發現了,可是和壞人一起進站的其他人已經進站了,沒辦法再讓他們出去了,于是隻能讓壞人一個人出去。

redis不支援復原

redis官方文檔給出了一下兩點原因:

  • Redis 指令隻會因為錯誤的文法而失敗(并且這些問題不能在入隊時發現),或是指令用在了錯誤類型的鍵上面:這也就是說,從實用性的角度來說,失敗的指令是由程式設計錯誤造成的,而這些錯誤應該在開發的過程中被發現,而不應該出現在生産環境中。
  • 因為不需要對復原進行支援,是以 Redis 的内部可以保持簡單且快速。

redis認為這是程式員應該避免的事情,而不應該由redis的開發者承擔這份責任,況且大多數情況下復原也無法挽回錯誤。正是因為不需要支援復原,redis才能保持高效。

ie:不該我幹的事情我一分錢也不會幹,我隻負責保持高效。

放棄事務:

discard

執行此指令後,事務将被放棄,隊列将被清空,且将會從事務狀态中退出

127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> multi 
OK
127.0.0.1:6379(TX)> set k4 v4 
QUEUED
127.0.0.1:6379(TX)> get v4
QUEUED
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> get k4
(nil)
           

實作樂觀鎖:

watch

指令可以為redis事務提供樂觀鎖行為。

​ 如果隻有一個用戶端對redis-server進行操作的情況下,那麼無論我們做什麼操作基本都能夠保證操作的正确性,但如果有若個用戶端一起對redis-server發起操作請求,那麼就有可能會出現資源的争奪問題。

​ 舉個簡單的例子來說,假設key1對應的值為10,現在兩個程式同時執行如下程式:

multi
var = get key1
var = var + 1 
set key1 $val
           

​ 如果碰巧兩個程式在同一時刻執行了get key1的指令,那麼他們所讀取到到的值都是10,然後分别對10+1的到11,然後又将11 set 給了key1 ,這樣key1最終的值将會是11,而不是正确的12。

是以我們要做的就是派一個人來替我們監視key1的值有沒有發生變化。

watch

指令就是這個人

我們首先執行指令

watch key1

,讓key1處于被監視的狀态,然後我們執行

multi

指令開啟一個事務,在我們執行

exec

指令執行此事務之前,一旦發現key1的值發生了任何改變,那麼我們目前的事務會立即取消,然後進行不斷的嘗試,知道成功執行目前的事務為止。

  • watch可以多次被執行,對鍵的監視從

    watch

    指令執行開始,到

    exec

    指令執行結束
  • 使用者還可以在單個

    watch

    指令中監視任意多個鍵,比如:
watch key1 key2 key3
ok
           

使用者還可以使用無參數的

unwatch

指令取消對所有鍵的監視。

watch何時被取消?

  1. 調用

    exec

    指令後,不管是否執行成功,對鍵的監視都會被取消
  2. 用戶端斷開連接配接,對鍵的監視也會被取消
  3. 使用

    unwatch

    指令手動取消

參考:redis官方文檔

若有不足之處,還請指正