天天看點

Redis學習(事務)

一、概述:

      和衆多其它資料庫一樣,Redis作為NoSQL資料庫也同樣提供了事務機制。在Redis中,MULTI/EXEC/DISCARD/WATCH這四個指令是我們實作事務的基石。相信對有關系型資料庫開發經驗的開發者而言這一概念并不陌生,即便如此,我們還是會簡要的列出Redis中事務的實作特征:

      1). 在事務中的所有指令都将會被串行化的順序執行,事務執行期間,Redis不會再為其它用戶端的請求提供任何服務,進而保證了事物中的所有指令被原子的執行。

      2). 和關系型資料庫中的事務相比,在Redis事務中如果有某一條指令執行失敗,其後的指令仍然會被繼續執行。

      3). 我們可以通過MULTI指令開啟一個事務,有關系型資料庫開發經驗的人可以将其了解為"BEGIN TRANSACTION"語句。在該語句之後執行的指令都将被視為事務之内的操作,最後我們可以通過執行EXEC/DISCARD指令來送出/復原該事務内的所有操作。這兩個Redis指令可被視為等同于關系型資料庫中的COMMIT/ROLLBACK語句。

      4). 在事務開啟之前,如果用戶端與伺服器之間出現通訊故障并導緻網絡斷開,其後所有待執行的語句都将不會被伺服器執行。然而如果網絡中斷事件是發生在用戶端執行EXEC指令之後,那麼該事務中的所有指令都會被伺服器執行。

      5). 當使用Append-Only模式時,Redis會通過調用系統函數write将該事務内的所有寫操作在本次調用中全部寫入磁盤。然而如果在寫入的過程中出現系統崩潰,如電源故障導緻的當機,那麼此時也許隻有部分資料被寫入到磁盤,而另外一部分資料卻已經丢失。Redis伺服器會在重新啟動時執行一系列必要的一緻性檢測,一旦發現類似問題,就會立即退出并給出相應的錯誤提示。此時,我們就要充分利用Redis工具包中提供的redis-check-aof工具,該工具可以幫助我們定位到資料不一緻的錯誤,并将已經寫入的部分資料進行復原。修複之後我們就可以再次重新啟動Redis伺服器了。

二、相關指令清單:

指令原型 時間複雜度 指令描述 傳回值 MULTI   用于标記事務的開始,其後執行的指令都将被存入指令隊列,直到執行EXEC時,這些指令才會被原子的執行。 始終傳回OK

EXEC   執行在一個事務内指令隊列中的所有指令,同時将目前連接配接的狀态恢複為正常狀态,即非事務狀态。如果在事務中執行了WATCH指令,那麼隻有當WATCH所監控的Keys沒有被修改的前提下,EXEC指令才能執行事務隊列中的所有指令,否則EXEC将放棄目前事務中的所有指令。 原子性的傳回事務中各條指令的傳回結果。如果在事務中使用了WATCH,一旦事務被放棄,EXEC将傳回NULL-multi-bulk回複。

DISCARD   復原事務隊列中的所有指令,同時再将目前連接配接的狀态恢複為正常狀态,即非事務狀态。如果WATCH指令被使用,該指令将UNWATCH所有的Keys。 始終傳回OK。

WATCH key [key ...] O(1) 在MULTI指令執行之前,可以指定待監控的Keys,然而在執行EXEC之前,如果被監控的Keys發生修改,EXEC将放棄執行該事務隊列中的所有指令。 始終傳回OK。

UNWATCH O(1) 取消目前事務中指定監控的Keys,如果執行了EXEC或DISCARD指令,則無需再手工執行該指令了,因為在此之後,事務中所有被監控的Keys都将自動取消。 始終傳回OK。

三、指令示例:

   1. 事務被正常執行:

    #在Shell指令行下執行Redis的用戶端工具。

    /> redis-cli

    #在目前連接配接上啟動一個新的事務。

    redis 127.0.0.1:6379> multi

    OK

    #執行事務中的第一條指令,從該指令的傳回結果可以看出,該指令并沒有立即執行,而是存于事務的指令隊列。

    redis 127.0.0.1:6379> incr t1

    QUEUED

    #又執行一個新的指令,從結果可以看出,該指令也被存于事務的指令隊列。

    redis 127.0.0.1:6379> incr t2

    QUEUED

    #執行事務指令隊列中的所有指令,從結果可以看出,隊列中指令的結果得到傳回。

    redis 127.0.0.1:6379> exec

    1) (integer) 1

    2) (integer) 1

   2. 事務中存在失敗的指令:

    #開啟一個新的事務。

    redis 127.0.0.1:6379> multi

    OK

    #設定鍵a的值為string類型的3。

    redis 127.0.0.1:6379> set a 3

    QUEUED

    #從鍵a所關聯的值的頭部彈出元素,由于該值是字元串類型,而lpop指令僅能用于List類型,是以在執行exec指令時,該指令将會失敗。

    redis 127.0.0.1:6379> lpop a

    QUEUED

    #再次設定鍵a的值為字元串4。

    redis 127.0.0.1:6379> set a 4

    QUEUED

    #擷取鍵a的值,以便确認該值是否被事務中的第二個set指令設定成功。

    redis 127.0.0.1:6379> get a

    QUEUED

    #從結果中可以看出,事務中的第二條指令lpop執行失敗,而其後的set和get指令均執行成功,這一點是Redis的事務與關系型資料庫中的事務之間最為重要的差别。

    redis 127.0.0.1:6379> exec

    1) OK

    2) (error) ERR Operation against a key holding the wrong kind of value

    3) OK

    4) "4"

   3. 復原事務:

    #為鍵t2設定一個事務執行前的值。

    redis 127.0.0.1:6379> set t2 tt

    OK

    #開啟一個事務。

    redis 127.0.0.1:6379> multi

    OK

    #在事務内為該鍵設定一個新值。

    redis 127.0.0.1:6379> set t2 ttnew

    QUEUED

    #放棄事務。

    redis 127.0.0.1:6379> discard

    OK

    #檢視鍵t2的值,從結果中可以看出該鍵的值仍為事務開始之前的值。

    redis 127.0.0.1:6379> get t2

    "tt"

四、WATCH指令和基于CAS的樂觀鎖:

      在Redis的事務中,WATCH指令可用于提供CAS(check-and-set)功能。假設我們通過WATCH指令在事務執行之前監控了多個Keys,倘若在WATCH之後有任何Key的值發生了變化,EXEC指令執行的事務都将被放棄,同時傳回Null multi-bulk應答以通知調用者事務執行失敗。例如,我們再次假設Redis中并未提供incr指令來完成鍵值的原子性遞增,如果要實作該功能,我們隻能自行編寫相應的代碼。其僞碼如下:

      val = GET mykey

      val = val + 1

      SET mykey $val

      以上代碼隻有在單連接配接的情況下才可以保證執行結果是正确的,因為如果在同一時刻有多個用戶端在同時執行該段代碼,那麼就會出現多線程程式中經常出現的一種錯誤場景--競态争用(race condition)。比如,用戶端A和B都在同一時刻讀取了mykey的原有值,假設該值為10,此後兩個用戶端又均将該值加一後set回Redis伺服器,這樣就會導緻mykey的結果為11,而不是我們認為的12。為了解決類似的問題,我們需要借助WATCH指令的幫助,見如下代碼:

      WATCH mykey

      val = GET mykey

      val = val + 1

      MULTI

      SET mykey $val

      EXEC

      和此前代碼不同的是,新代碼在擷取mykey的值之前先通過WATCH指令監控了該鍵,此後又将set指令包圍在事務中,這樣就可以有效的保證每個連接配接在執行EXEC之前,如果目前連接配接擷取的mykey的值被其它連接配接的用戶端修改,那麼目前連接配接的EXEC指令将執行失敗。這樣調用者在判斷傳回值後就可以獲悉val是否被重新設定成功。

轉自原文:http://www.cnblogs.com/stephen-liu74/archive/2012/03/28/2357783.html