1. 程式人生 > >Redis的事務和watch

Redis的事務和watch

redis的事務

嚴格意義來講, redis的事務和我們理解的傳統資料庫(如mysql)的事務是不一樣的。

redis中的事務定義

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

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

Redis保證一個事務中的所有命令要麼都執行,要麼都不執行。如果在傳送EXEC命令前客戶端斷線了,則Redis會清空事務佇列,事務中的所有命令都不會執行。而一旦客戶端傳送了EXEC命令,所有的命令就都會被執行,即使此後客戶端斷線也沒關係,因為Redis中已經記錄了所有要執行的命令。

除此之外,Redis的事務還能保證一個事務內的命令依次執行而不被其他命令插入。試想客戶端A需要執行幾條命令,同時客戶端B傳送了一條命令,如果不使用事務,則客戶端B的命令可能會插入到客戶端A的幾條命令中執行。如果不希望發生這種情況,也可以使用事務。

事務的應用

事務的應用非常普遍,如銀行轉賬過程中A給B匯款,首先系統從A的賬戶中將錢划走,然後向B的賬戶增加相應的金額。這兩個步驟必須屬於同一個事務,要麼全執行,要麼全不執行。否則只執行第一步,錢就憑空消失了,這顯然讓人無法接受。

和傳統的事務不同

和傳統的mysql事務不同的是,即使我們的加錢操作失敗,我們也無法

在這一組命令中讓整個狀態回滾到操作之前

事務的錯誤處理

如果一個事務中的某個命令執行出錯,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命令會返回一個結果: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命令來保證下一個事務的執行不會受到影響。



作者:jsondream
連結:http://www.jianshu.com/p/361cb9cd13d5