redis的事務(wù)和watch

redis的事務(wù)

嚴(yán)格意義來(lái)講,redis的事務(wù)和我們理解的傳統(tǒng)數(shù)據(jù)庫(kù)(如mysql)的事務(wù)是不一樣的。

redis中的事務(wù)定義

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

事務(wù)同命令一樣都是Redis的最小執(zhí)行單位,一個(gè)事務(wù)中的命令要么都執(zhí)行,要么都不執(zhí)行。
事務(wù)的原理是先將屬于一個(gè)事務(wù)的命令發(fā)送給Redis,然后再讓Redis依次執(zhí)行這些命令。

Redis保證一個(gè)事務(wù)中的所有命令要么都執(zhí)行,要么都不執(zhí)行。如果在發(fā)送EXEC命令前客戶端斷線了,則Redis會(huì)清空事務(wù)隊(duì)列,事務(wù)中的所有命令都不會(huì)執(zhí)行。而一旦客戶端發(fā)送了EXEC命令,所有的命令就都會(huì)被執(zhí)行,即使此后客戶端斷線也沒(méi)關(guān)系,因?yàn)镽edis中已經(jīng)記錄了所有要執(zhí)行的命令。

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

事務(wù)的應(yīng)用

事務(wù)的應(yīng)用非常普遍,如銀行轉(zhuǎn)賬過(guò)程中A給B匯款,首先系統(tǒng)從A的賬戶中將錢(qián)劃走,然后向B的賬戶增加相應(yīng)的金額。這兩個(gè)步驟必須屬于同一個(gè)事務(wù),要么全執(zhí)行,要么全不執(zhí)行。否則只執(zhí)行第一步,錢(qián)就憑空消失了,這顯然讓人無(wú)法接受。

和傳統(tǒng)的事務(wù)不同

和傳統(tǒng)的mysql事務(wù)不同的事,即使我們的加錢(qián)操作失敗,我們也無(wú)法在這一組命令中讓整個(gè)狀態(tài)回滾到操作之前

事務(wù)的錯(cuò)誤處理

如果一個(gè)事務(wù)中的某個(gè)命令執(zhí)行出錯(cuò),Redis會(huì)怎樣處理呢?要回答這個(gè)問(wèn)題,首先需要知道什么原因會(huì)導(dǎo)致命令執(zhí)行出錯(cuò)。

語(yǔ)法錯(cuò)誤

語(yǔ)法錯(cuò)誤指命令不存在或者命令參數(shù)的個(gè)數(shù)不對(duì)。比如:

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命令后執(zhí)行了3個(gè)命令:一個(gè)是正確的命令,成功地加入事務(wù)隊(duì)列;其余兩個(gè)命令都有語(yǔ)法錯(cuò)誤。而只要有一個(gè)命令有語(yǔ)法錯(cuò)誤,執(zhí)行EXEC命令后Redis就會(huì)直接返回錯(cuò)誤,連語(yǔ)法正確的命令也不會(huì)執(zhí)行。

這里需要注意一點(diǎn):
Redis 2.6.5之前的版本會(huì)忽略有語(yǔ)法錯(cuò)誤的命令,然后執(zhí)行事務(wù)中其他語(yǔ)法正確的命令。就此例而言,SET key value會(huì)被執(zhí)行,EXEC命令會(huì)返回一個(gè)結(jié)果:1) OK。

運(yùn)行錯(cuò)誤

運(yùn)行錯(cuò)誤指在命令執(zhí)行時(shí)出現(xiàn)的錯(cuò)誤,比如使用散列類型的命令操作集合類型的鍵,這種錯(cuò)誤在實(shí)際執(zhí)行之前Redis是無(wú)法發(fā)現(xiàn)的,所以在事務(wù)里這樣的命令是會(huì)被Redis接受并執(zhí)行的。如果事務(wù)里的一條命令出現(xiàn)了運(yùn)行錯(cuò)誤,事務(wù)里其他的命令依然會(huì)繼續(xù)執(zhí)行(包括出錯(cuò)命令之后的命令),示例如下:

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"

可見(jiàn)雖然SADD key 2出現(xiàn)了錯(cuò)誤,但是SET key 3依然執(zhí)行了。

Redis的事務(wù)沒(méi)有關(guān)系數(shù)據(jù)庫(kù)事務(wù)提供的回滾(rollback)功能。為此開(kāi)發(fā)者必須在事務(wù)執(zhí)行出錯(cuò)后自己收拾剩下的攤子(將數(shù)據(jù)庫(kù)復(fù)原回事務(wù)執(zhí)行前的狀態(tài)等,這里我們一般采取日志記錄然后業(yè)務(wù)補(bǔ)償?shù)姆绞絹?lái)處理,但是一般情況下,在redis做的操作不應(yīng)該有這種強(qiáng)一致性要求的需求,我們認(rèn)為這種需求為不合理的設(shè)計(jì))。

Watch命令

大家可能知道redis提供了基于incr命令來(lái)操作一個(gè)整數(shù)型數(shù)值的原子遞增,那么我們假設(shè)如果redis沒(méi)有這個(gè)incr命令,我們?cè)撛趺磳?shí)現(xiàn)這個(gè)incr的操作呢?

那么我們下面的正主watch就要上場(chǎng)了。

如何使用watch命令

正常情況下我們想要對(duì)一個(gè)整形數(shù)值做修改是這么做的(偽代碼實(shí)現(xiàn)):

      val = GET mykey
      val = val + 1
      SET mykey $val

但是上述的代碼會(huì)出現(xiàn)一個(gè)問(wèn)題,因?yàn)樯厦姘烧5囊粋€(gè)incr(原子遞增操作)分為了兩部分,那么在多線程(分布式)環(huán)境中,這個(gè)操作就有可能不再具有原子性了。

研究過(guò)java的juc包的人應(yīng)該都知道cas,那么redis也提供了這樣的一個(gè)機(jī)制,就是利用watch命令來(lái)實(shí)現(xiàn)的。

watch命令描述

WATCH命令可以監(jiān)控一個(gè)或多個(gè)鍵,一旦其中有一個(gè)鍵被修改(或刪除),之后的事務(wù)就不會(huì)執(zhí)行。監(jiān)控一直持續(xù)到EXEC命令(事務(wù)中的命令是在EXEC之后才執(zhí)行的,所以在MULTI命令后可以修改WATCH監(jiān)控的鍵值)

利用watch實(shí)現(xiàn)incr

具體做法如下:

      WATCH mykey
      val = GET mykey
      val = val + 1
      MULTI
      SET mykey $val
      EXEC

和此前代碼不同的是,新代碼在獲取mykey的值之前先通過(guò)WATCH命令監(jiān)控了該鍵,此后又將set命令包圍在事務(wù)中,這樣就可以有效的保證每個(gè)連接在執(zhí)行EXEC之前,如果當(dāng)前連接獲取的mykey的值被其它連接的客戶端修改,那么當(dāng)前連接的EXEC命令將執(zhí)行失敗。這樣調(diào)用者在判斷返回值后就可以獲悉val是否被重新設(shè)置成功。

注意點(diǎn)

由于WATCH命令的作用只是當(dāng)被監(jiān)控的鍵值被修改后阻止之后一個(gè)事務(wù)的執(zhí)行,而不能保證其他客戶端不修改這一鍵值,所以在一般的情況下我們需要在EXEC執(zhí)行失敗后重新執(zhí)行整個(gè)函數(shù)。

執(zhí)行EXEC命令后會(huì)取消對(duì)所有鍵的監(jiān)控,如果不想執(zhí)行事務(wù)中的命令也可以使用UNWATCH命令來(lái)取消監(jiān)控。

實(shí)現(xiàn)一個(gè)hsetNX函數(shù)

我們實(shí)現(xiàn)的hsetNX這個(gè)功能是:僅當(dāng)字段存在時(shí)才賦值。

為了避免競(jìng)態(tài)條件我們使用watch事務(wù)來(lái)完成這一功能(偽代碼):

    WATCH key  
    isFieldExists = HEXISTS key, field  
    if isFieldExists is 1  
    MULTI  
    HSET key, field, value  
    EXEC  
    else  
    UNWATCH  
    return isFieldExists

在代碼中會(huì)判斷要賦值的字段是否存在,如果字段不存在的話就不執(zhí)行事務(wù)中的命令,但需要使用UNWATCH命令來(lái)保證下一個(gè)事務(wù)的執(zhí)行不會(huì)受到影響。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文將從Redis的基本特性入手,通過(guò)講述Redis的數(shù)據(jù)結(jié)構(gòu)和主要命令對(duì)Redis的基本能力進(jìn)行直觀介紹。之后概...
    kelgon閱讀 61,689評(píng)論 23 625
  • 《Redis 入門(mén)指南》(第二版) 第一章 Redis 是什么 Redis (REmote Dictionary ...
    EdenPP閱讀 67,463評(píng)論 3 10
  • redis事務(wù) Redis 通過(guò) MULTI 、 DISCARD 、 EXEC 和 WATCH 四個(gè)命令來(lái)實(shí)現(xiàn)事務(wù)...
    全能程序猿閱讀 2,267評(píng)論 0 11
  • 本來(lái)準(zhǔn)備好心情陪你 可是你又突然變卦 我是依賴友情沒(méi)錯(cuò) 誰(shuí)讓你圈子太大而我太小 誰(shuí)讓我把自己看的那么低 誰(shuí)讓我這么...
    當(dāng)神經(jīng)不再大條閱讀 461評(píng)論 0 1
  • 【體驗(yàn)入】今天休息,給家里和老爸打了電話,家里和老爸說(shuō)他們都很好,不用總擔(dān)心他們,反到是他們都說(shuō)你自己出門(mén)在外的照...
    Lzr_2017閱讀 154評(píng)論 0 5

友情鏈接更多精彩內(nèi)容