Redis-事務(multi,exec,watch,unwatch)
1.1 概述
Redis中的事務(transaction)是一組命令的集合。事務同命令一樣都是Redis的最小執行單位, 一個事務中的命令要麼都執行,要麼都不執行 。
事務的應用非常普遍,如銀行轉賬過程中A給B匯款,首先系統從A的賬戶中將錢划走,然後向B的賬戶增加相應的金額。這兩個步驟必須屬於同一個事務,要麼全執行,要麼全不執行。否則只執行第一步,錢就憑空消失了,這顯然讓人無法接受。
事務的原理是先將屬於一個事務的命令傳送給Redis,然後再讓Redis依次執行這些命令。
例如:
redis>MULTI
OK
redis>SADD "user:1:following" 2
QUEUED
redis>SADD "user:2:followers" 1
QUEUED
redis>EXEC
1) (integer) 1
2) (integer) 1
上面的程式碼演示了事務的使用方式。
(1)首先使用MULTI命令告訴Redis:“下面我發給你的命令屬於同一個事務,你先不要執行,而是把它們暫時存起來。”Redis回答:“OK。”
(2)而後我們傳送了兩個SADD命令來實現關注和被關注操作,可以看到Redis遵守了承諾,沒有執行這些命令,而是 返回QUEUED表示這兩條命令已經進入等待執行的事務佇列 中了。
(3)當把所有要在同一個事務中執行的命令都發給Redis後,我們 使用EXEC命令告訴Redis將等待執行的事務佇列中的所有命令(即剛才所有返回QUEUED的命令)按照發送順序依次執行。EXEC命令的返回值就是這些命令的返回值組成的列表,返回值順序和命令的順序相同。
Redis保證一個事務中的所有命令要麼都執行,要麼都不執行。如果在傳送EXEC命令前客戶端斷線了,則Redis會清空事務佇列,事務中的所有命令都不會執行。而 一旦客戶端傳送了EXEC命令,所有的命令就都會被執行,即使此後客戶端斷線也沒關係,因為Redis中已經記錄了所有要執行的命令。
除此之外,Redis的事務還能保證一個事務內的命令依次執行而不被其他命令插入。試想客戶端A需要執行幾條命令,同時客戶端B傳送了一條命令,如果不使用事務,則客戶端B的命令可能會插入到客戶端A的幾條命令中執行。如果不希望發生這種情況,也可以使用事務。
1.2 錯誤處理
如果一個事務中的某個命令執行出錯,Redis會怎樣處理呢?要回答這個問題,首先需要知道什麼原因會導致命令執行出錯。
(1)語法錯誤。 語法錯誤指命令不存在或者命令引數的個數不對。比如:
redis>MULTI
OK
redis>SET key value
QUEUED
redis>SET key
(error)ERR wrong number of arguments for 'set' command
redis> ERRORCOMMAND key
(error) ERR unknown command 'ERRORCOMMAND'
redis> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
跟在MULTI命令後執行了3個命令:一個是正確的命令,成功地加入事務佇列;其餘兩個命令都有語法錯誤。而只要有 一個命令有語法錯誤,執行EXEC命令後Redis就會直接返回錯誤,連語法正確的命令也不會執行 ① 。
註釋:
①Redis 2.6.5之前的版本會忽略有語法錯誤的命令,然後執行事務中其他語法正確的命令。就此例而言,SET key value會被執行,EXEC命令會返回一個結果:1) OK。
(2)執行錯誤。 執行錯誤指在命令執行時出現的錯誤,比如使用雜湊型別的命令操作集合型別的鍵,這種錯誤在實際執行之前Redis是無法發現的,所以在事務裡這樣的命令是會被Redis接受並執行的。如果事務裡的 一條命令出現了執行錯誤,事務裡其他的命令依然會繼續執行(包括出錯命令之後的命令) ,示例如下:
redis>MULTI
OK
redis>SET key 1
QUEUED
redis>SADD key 2
QUEUED
redis>SET key 3
QUEUED
redis>EXEC
1) OK
2) (error) ERR Operation against a key holding the wrong kind of value
3) OK
redis>GET key
"3"
可見雖然SADD key 2出現了錯誤,但是SET key 3依然執行了。
Redis的事務沒有關係資料庫事務提供的回滾(rollback)① 功能 。為此開發者必須在事務執行出錯後自己收拾剩下的攤子(將資料庫復原回事務執行前的狀態等)。
註釋:①事務回滾是指將一個事務已經完成的對資料庫的修改操作撤銷。
1.3 watch命令介紹
我們已經知道在一個事務中只有當所有命令都依次執行完後才能得到每個結果的返回值,可是有些情況下需要先獲得一條命令的返回值,然後再根據這個值執行下一條命令。例如,INCR命令可以使用GET和SET命令自己實現,但會出現競態條件,虛擬碼如下:
def incr( key)
value=GET key
if not value
value=0
value= value+1
SET key, value
return value
就目前瞭解的內容我們肯定會想到可以用 事務來實現incr函式以防止競態條件,可是因為事務中的每個命令的執行結果都是最後一起返回的,所以無法將前一條命令的結果作為下一條命令的引數,即在執行SET命令時無法獲得GET命令的返回值,也就無法做到增1的功能了。
為了解決這個問題,我們需要換一種思路。即在GET獲得鍵值後保證該鍵值不被其他客戶端修改,直到函式執行完成後才允許其他客戶端修改該鍵鍵值,這樣也可以防止競態條件。要實現這一思路需要請出事務家族的另一位成員: WATCH 。 WATCH命令可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之後的事務就不會執行。 監控一直持續到EXEC命令(事務中的命令是在EXEC之後才執行的,所以在MULTI命令後可以修改WATCH監控的鍵值),如:
redis>SET key 1
OK
redis>WATCH key
OK
redis>SET key 2
OK
redis>MULTI
OK
redis>SET key 3
QUEUED
redis>EXEC
(nil)
redis>GET key
"2"
上例中在執行WATCH命令後、事務執行前修改了key的值(即SET key 2),所以最後事務中的命令SET key 3沒有執行,EXEC命令返回空結果。
學會了WATCH命令就可以通過事務自己實現incr函數了,虛擬碼如下:
def incr( key)
WATCH key
value=GET key
if not value
value=0
value= value+1
MULTI
SET key, value
result=EXEC
return result[0
]
因為EXEC命令返回值是多行字串型別,所以程式碼中使用result[0]來獲得其中第一個結果。
提示:
由於WATCH命令的作用只是當被監控的鍵值被修改後 阻止 之後一個事務的執行,而不能保證其他客戶端不修改這一鍵值,所以我們需要在EXEC執行失敗後重新執行整個函式。
執行EXEC命令後會取消對所有鍵的監控 ,如果不想執行事務中的命令也可以使用 UNWATCH 命令來取消監控。比如,我們要實現hsetxx函式,作用與HSETNX命令類似,只不過是僅當欄位存在時才賦值。為了避免競態條件我們使用事務來完成這一功能:
def hsetxx( key, field, value)
WATCH key
isFieldExists = HEXISTS key, field
if isFieldExists is 1
MULTI
HSET key, field, value
EXEC
else
UNWATCH
return isFieldExists
在程式碼中會判斷要賦值的欄位是否存在,如果欄位不存在的話就不執行事務中的命令,但需要使用UNWATCH命令來保證下一個事務的執行不會受到影響。