相關命令
1. MULTI
用於標記事務塊的開始。Redis會將后續的命令逐個放入隊列中,然后才能使用EXEC命令原子化地執行這個命令序列。
這個命令的運行格式如下所示:
MULTI
這個命令的返回值是一個簡單的字符串,總是OK。
2. EXEC
在一個事務中執行所有先前放入隊列的命令,然后恢復正常的連接狀態。
當使用WATCH命令時,只有當受監控的鍵沒有被修改時,EXEC命令才會執行事務中的命令,這種方式利用了檢查再設置(CAS)的機制。
這個命令的運行格式如下所示:
EXEC
這個命令的返回值是一個數組,其中的每個元素分別是原子化事務中的每個命令的返回值。 當使用WATCH命令時,如果事務執行中止,那么EXEC命令就會返回一個Null值。
3. DISCARD
清除所有先前在一個事務中放入隊列的命令,然后恢復正常的連接狀態。
如果使用了WATCH命令,那么DISCARD命令就會將當前連接監控的所有鍵取消監控。
這個命令的運行格式如下所示:
DISCARD
這個命令的返回值是一個簡單的字符串,總是OK。
4. WATCH
當某個事務需要按條件執行時,就要使用這個命令將給定的鍵設置為受監控的。
這個命令的運行格式如下所示:
WATCH key [key ...]
這個命令的返回值是一個簡單的字符串,總是OK。
對於每個鍵來說,時間復雜度總是O(1)。
5. UNWATCH
清除所有先前為一個事務監控的鍵。
如果你調用了EXEC或DISCARD命令,那么就不需要手動調用UNWATCH命令。
這個命令的運行格式如下所示:
UNWATCH
這個命令的返回值是一個簡單的字符串,總是OK。
時間復雜度總是O(1)。
示例
redis的事務定義和原理
嚴格意義來講,redis的事務和我們理解的傳統數據庫(如mysql)的事務是不一樣的。
Redis中的事務(transaction)是一組命令的集合。
事務同命令一樣都是Redis的最小執行單位,一個事務中的命令要么都執行,要么都不執行。事務的原理是先將屬於一個事務的命令發送給Redis,然后再讓Redis依次執行這些命令。
除此之外,Redis的事務還能保證一個事務內的命令依次執行而不被其他命令插入。試想客戶端A需要執行幾條命令,同時客戶端B發送了一條命令,如果不使用事務,則客戶端B的命令可能會插入到客戶端A的幾條命令中執行。如果不希望發生這種情況,也可以使用事務。
和傳統的事務不同(redis不能rollback,也就是不支持回滾)
和傳統的mysql事務不同的是,即使我們的加錢操作失敗,我們也無法在這一組命令中讓整個狀態回滾到操作之前。
事務的錯誤處理
如果一個事務中的某個命令執行出錯,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)功能。為此開發者必須在事務執行出錯后自己收拾剩下的攤子(將數據庫復原回事務執行前的狀態等,這里我們一般采取日志記錄然后業務補償的方式來處理,但是一般情況下,在redis做的操作不應該有這種強一致性要求的需求,我們認為這種需求為不合理的設計)。
Watch命令
大家可能知道redis提供了基於incr命令來操作一個整數型數值的原子遞增,那么我們假設如果redis沒有這個incr命令,我們該怎么實現這個incr的操作呢?
那么我們下面的正主watch
就要上場了。
如何使用watch命令
正常情況下我們想要對一個整形數值做修改是這么做的(偽代碼實現):
val = GET mykey val = val + 1 SET mykey $val
但是上述的代碼會出現一個問題,因為上面把正常的一個incr(原子遞增操作)分為了兩部分,那么在多線程(分布式)環境中,這個操作就有可能不再具有原子性了。
研究過java的juc包的人應該都知道cas,那么redis也提供了這樣的一個機制,就是利用watch
命令來實現的。
watch命令描述
WATCH命令可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之后的事務就不會執行。監控一直持續到EXEC命令(事務中的命令是在EXEC之后才執行的,所以在MULTI命令后可以修改WATCH監控的鍵值)
利用watch實現incr
具體做法如下:
WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC
注意點
由於WATCH命令的作用只是當被監控的鍵值被修改后阻止之后一個事務的執行,而不能保證其他客戶端不修改這一鍵值,所以在一般的情況下我們需要在EXEC執行失敗后重新執行整個函數。
執行EXEC命令后會取消對所有鍵的監控,如果不想執行事務中的命令也可以使用UNWATCH命令來取消監控。
實現一個hsetNX函數
我們實現的hsetNX這個功能是:僅當字段存在時才賦值。
為了避免競態條件我們使用watch
和事務來完成這一功能(偽代碼):
WATCH key isFieldExists = HEXISTS key, field if isFieldExists is 1 MULTI HSET key, field, value EXEC else UNWATCH return isFieldExists
在代碼中會判斷要賦值的字段是否存在,如果字段不存在的話就不執行事務中的命令,但需要使用UNWATCH命令來保證下一個事務的執行不會受到影響。
參考文檔
http://redisdoc.com/topic/transaction.html
https://www.cnblogs.com/kyrin/p/5967620.html