首頁 >後端開發 >C#.Net教程 >Redis教學(八):事務詳解

Redis教學(八):事務詳解

黄舟
黄舟原創
2016-12-28 14:58:451085瀏覽

一、概述:

      和眾多其它資料庫一樣,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執行在一個交易內命令佇列中的所有指令,同時將目前連線的狀態還原為正常狀態,即非交易狀態。如果在交易中執行了WATCH指令,那麼只有當WATCH所監控的Keys沒有被修改的前提下,EXEC指令才能執行交易佇列中的所有指令,否則EXEC將放棄目前交易中的所有指令。 原子性的返回事務中各條命令的回傳結果。如果在事務中使用了WATCH,一旦事務被放棄,EXEC將返回NULL-multi-bulk回應。 回滾事務佇列中的所有指令,同時再將目前連線的狀態還原為正常狀態,即非交易狀態。如果WATCH指令被使用,則該指令將UNWATCH所有的Keys。 總是回傳OKWATCHkey [key ...]O(1)在MULTI指令執行之前,可以指定待監控的Keys,然而在執行EXEC之前,如果被監控的Keys發生修改,EXEC將放棄執行該事務佇列中的所有命令。 始終返回OK。 UNWATCHO(1)取消當前事務中指定監控的Keys,如果執行了EXEC或DISCARD命令,則無需再手動執行該命令了,因為在此之後,事務中所有被監控的Keys都將自動取消。 始終返回OK。
指令原型 時間複雜度 指令描述 回傳值
MULTI

EXEC

DISCARD
🎜🎜🎜

三、指令範例:

   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"


 
 #为键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"

樂觀鎖定:


      在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是否重新設定成功。

 以上就是Redis教學(八):事務詳解的內容,更多相關內容請關注PHP中文網(www.php.cn)!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn