redis 事務

1、概述:

      和衆多其它數據庫同樣,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服務器了。

2、相關命令列表:redis

命令原型 時間複雜度 命令描述 返回值
MULTI   用於標記事務的開始,其後執行的命令都將被存入命令隊列,直到執行EXEC時,這些命令纔會被原子的執行。 始終返回OK
EXEC   執行在一個事務內命令隊列中的全部命令,同時將當前鏈接的狀態恢復爲正常狀態,即非事務狀態。若是在事務中執行了WATCH命令,那麼只有當WATCH所監控的Keys沒有被修改的前提下,EXEC命令才能執行事務隊列中的全部命令,不然EXEC將放棄當前事務中的全部命令。 原子性的返回事務中各條命令的返回結果。若是在事務中使用了WATCH,一旦事務被放棄,EXEC將返回NULL-multi-bulk回覆。
DISCARD   回滾事務隊列中的全部命令,同時再將當前鏈接的狀態恢復爲正常狀態,即非事務狀態。若是WATCH命令被使用,該命令將UNWATCH全部的Keys。 始終返回OK。
WATCHkey [key ...] O(1) 在MULTI命令執行以前,能夠指定待監控的Keys,然而在執行EXEC以前,若是被監控的Keys發生修改,EXEC將放棄執行該事務隊列中的全部命令。 始終返回OK。
UNWATCH O(1) 取消當前事務中指定監控的Keys,若是執行了EXEC或DISCARD命令,則無需再手工執行該命令了,由於在此以後,事務中全部被監控的Keys都將自動取消。 始終返回OK。

3、命令示例:

   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"

4、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是否被從新設置成功。數據庫

相關文章
相關標籤/搜索