嚴格意義來說,redis的事務和咱們理解的傳統數據庫(如mysql)的事務是不同的。java
Redis中的事務(transaction)是一組命令的集合。mysql
事務同命令同樣都是Redis的最小執行單位,一個事務中的命令要麼都執行,要麼都不執行。事務的原理是先將屬於一個事務的命令發送給Redis,而後再讓Redis依次執行這些命令。redis
Redis保證一個事務中的全部命令要麼都執行,要麼都不執行。若是在發送EXEC命令前客戶端斷線了,則Redis會清空事務隊列,事務中的全部命令都不會執行。而一旦客戶端發送了EXEC命令,全部的命令就都會被執行,即便此後客戶端斷線也不要緊,由於Redis中已經記錄了全部要執行的命令。sql
除此以外,Redis的事務還能保證一個事務內的命令依次執行而不被其餘命令插入。試想客戶端A須要執行幾條命令,同時客戶端B發送了一條命令,若是不使用事務,則客戶端B的命令可能會插入到客戶端A的幾條命令中執行。若是不但願發生這種狀況,也可使用事務。數據庫
事務不過是用隊列包裹起了一組 Redis 命令,並無提供任何額外的持久性功能,因此事務的持久性由 Redis 所使用的持久化模式決定:服務器
redis事務在執行的過程當中,不會處理其它命令,而是等全部命令都執行完後,再處理其它命令(知足隔離性)
redis事務在執行過程當中發生錯誤或進程被終結,都能保證數據的一致性;(詳見參考資料1)網絡
事務的應用很是廣泛,如銀行轉帳過程當中A給B匯款,首先系統從A的帳戶中將錢划走,而後向B的帳戶增長相應的金額。這兩個步驟必須屬於同一個事務,要麼全執行,要麼全不執行。不然只執行第一步,錢就憑空消失了,這顯然讓人沒法接受。多線程
和傳統的mysql事務不一樣的事,即便咱們的加錢操做失敗,咱們也沒法在這一組命令中讓整個狀態回滾到操做以前async
若是一個事務中的某個命令執行出錯,Redis會怎樣處理呢?要回答這個問題,首先須要知道什麼緣由會致使命令執行出錯。分佈式
語法錯誤指命令不存在或者命令參數的個數不對。好比:
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。
運行錯誤指在命令執行時出現的錯誤,好比使用散列類型的命令操做集合類型的鍵,這種錯誤在實際執行以前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作的操做不該該有這種強一致性要求的需求,咱們認爲這種需求爲不合理的設計)。
你們可能知道redis提供了基於incr命令來操做一個整數型數值的原子遞增,那麼咱們假設若是redis沒有這個incr命令,咱們該怎麼實現這個incr的操做呢?
那麼咱們下面的正主watch
就要上場了。
正常狀況下咱們想要對一個整形數值作修改是這麼作的(僞代碼實現):
val = GET mykey val = val + 1 SET mykey $val
可是上述的代碼會出現一個問題,由於上面吧正常的一個incr(原子遞增操做)分爲了兩部分,那麼在多線程(分佈式)環境中,這個操做就有可能再也不具備原子性了。
研究過java的juc包的人應該都知道cas,那麼redis也提供了這樣的一個機制,就是利用watch
命令來實現的。
WATCH命令能夠監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),以後的事務就不會執行。監控一直持續到EXEC命令(事務中的命令是在EXEC以後才執行的,因此在MULTI命令後能夠修改WATCH監控的鍵值)
具體作法以下:
WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC
和此前代碼不一樣的是,新代碼在獲取mykey的值以前先經過WATCH命令監控了該鍵,此後又將set命令包圍在事務中,這樣就能夠有效的保證每一個鏈接在執行EXEC以前,若是當前鏈接獲取的mykey的值被其它鏈接的客戶端修改,那麼當前鏈接的EXEC命令將執行失敗。這樣調用者在判斷返回值後就能夠獲悉val是否被從新設置成功。
因爲WATCH命令的做用只是當被監控的鍵值被修改後阻止以後一個事務的執行,而不能保證其餘客戶端不修改這一鍵值,因此在通常的狀況下咱們須要在EXEC執行失敗後從新執行整個函數。
執行EXEC命令後會取消對全部鍵的監控,若是不想執行事務中的命令也可使用UNWATCH命令來取消監控。
咱們實現的hsetNX這個功能是:僅當字段存在時才賦值。
爲了不競態條件咱們使用watch
和事務來完成這一功能(僞代碼):
WATCH key isFieldExists = HEXISTS key, field if isFieldExists is 1 MULTI HSET key, field, value EXEC else UNWATCH return isFieldExists
在代碼中會判斷要賦值的字段是否存在,若是字段不存在的話就不執行事務中的命令,但須要使用UNWATCH命令來保證下一個事務的執行不會受到影響。
將多個命令打包批量發送到redis服務器執行,減小網絡交互,優化性能,可能的解決方案: