Redis事務和watch

redis的事務

嚴格意義來說,redis的事務和咱們理解的傳統數據庫(如mysql)的事務是不同的。java

redis中的事務定義

Redis中的事務(transaction)是一組命令的集合。mysql

事務同命令同樣都是Redis的最小執行單位,一個事務中的命令要麼都執行,要麼都不執行。事務的原理是先將屬於一個事務的命令發送給Redis,而後再讓Redis依次執行這些命令。redis

Redis保證一個事務中的全部命令要麼都執行,要麼都不執行。若是在發送EXEC命令前客戶端斷線了,則Redis會清空事務隊列,事務中的全部命令都不會執行。而一旦客戶端發送了EXEC命令,全部的命令就都會被執行,即便此後客戶端斷線也不要緊,由於Redis中已經記錄了全部要執行的命令。sql

除此以外,Redis的事務還能保證一個事務內的命令依次執行而不被其餘命令插入。試想客戶端A須要執行幾條命令,同時客戶端B發送了一條命令,若是不使用事務,則客戶端B的命令可能會插入到客戶端A的幾條命令中執行。若是不但願發生這種狀況,也可使用事務。數據庫

 

redis事務持久性

事務不過是用隊列包裹起了一組 Redis 命令,並無提供任何額外的持久性功能,因此事務的持久性由 Redis 所使用的持久化模式決定服務器

  • 在單純的內存模式下,事務確定是不持久的。
  • 在 RDB 模式下,服務器可能在事務執行以後、RDB 文件更新以前的這段時間失敗,因此 RDB 模式下的 Redis 事務也是不持久的。
  • 在 AOF 的「老是 SYNC 」模式下,事務的每條命令在執行成功以後,都會當即調用 fsync 或 fdatasync 將事務數據寫入到 AOF 文件。可是,這種保存是由後臺線程進行的,主線程不會阻塞直到保存成功,因此從命令執行成功到數據保存到硬盤之間,仍是有一段很是小的間隔,因此這種模式下的事務也是不持久的。
  • 其餘 AOF 模式也和「老是 SYNC 」模式相似,因此它們都是不持久的。

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作的操做不該該有這種強一致性要求的需求,咱們認爲這種需求爲不合理的設計)。

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

和此前代碼不一樣的是,新代碼在獲取mykey的值以前先經過WATCH命令監控了該鍵,此後又將set命令包圍在事務中,這樣就能夠有效的保證每一個鏈接在執行EXEC以前,若是當前鏈接獲取的mykey的值被其它鏈接的客戶端修改,那麼當前鏈接的EXEC命令將執行失敗。這樣調用者在判斷返回值後就能夠獲悉val是否被從新設置成功。

注意點

因爲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命令來保證下一個事務的執行不會受到影響。

 

優化網絡特性

將多個命令打包批量發送到redis服務器執行,減小網絡交互,優化性能,可能的解決方案:

  1. 對於全部的get/set操做,可以使用現有的mget/mset指令;
  2. 對於各類不一樣類型的更新操做,可以使用lua腳本將命令打包後,發送到服務器端一次執行;
相關文章
相關標籤/搜索