<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 事務 [TOC=2,3] Redis 通過 [MULTI](http://redis.readthedocs.org/en/latest/transaction/multi.html#multi "(in Redis 命令參考 v2.8)") 、 [DISCARD](http://redis.readthedocs.org/en/latest/transaction/discard.html#discard "(in Redis 命令參考 v2.8)") 、 [EXEC](http://redis.readthedocs.org/en/latest/transaction/exec.html#exec "(in Redis 命令參考 v2.8)") 和 [WATCH](http://redis.readthedocs.org/en/latest/transaction/watch.html#watch "(in Redis 命令參考 v2.8)") 四個命令來實現事務功能,本章首先討論使用 [MULTI](http://redis.readthedocs.org/en/latest/transaction/multi.html#multi "(in Redis 命令參考 v2.8)") 、 [DISCARD](http://redis.readthedocs.org/en/latest/transaction/discard.html#discard "(in Redis 命令參考 v2.8)") 和 [EXEC](http://redis.readthedocs.org/en/latest/transaction/exec.html#exec "(in Redis 命令參考 v2.8)") 三個命令實現的一般事務,然后再來討論帶有 [WATCH](http://redis.readthedocs.org/en/latest/transaction/watch.html#watch "(in Redis 命令參考 v2.8)") 的事務的實現。 因為事務的安全性也非常重要,所以本章最后通過常見的 ACID 性質對 Redis 事務的安全性進行了說明。 ### 事務 事務提供了一種“將多個命令打包,然后一次性、按順序地執行”的機制,并且事務在執行的期間不會主動中斷 ——服務器在執行完事務中的所有命令之后,才會繼續處理其他客戶端的其他命令。 以下是一個事務的例子,它先以 [MULTI](http://redis.readthedocs.org/en/latest/transaction/multi.html#multi "(in Redis 命令參考 v2.8)") 開始一個事務,然后將多個命令入隊到事務中,最后由 [EXEC](http://redis.readthedocs.org/en/latest/transaction/exec.html#exec "(in Redis 命令參考 v2.8)") 命令觸發事務,一并執行事務中的所有命令: ~~~ redis> MULTI OK redis> SET book-name "Mastering C++ in 21 days" QUEUED redis> GET book-name QUEUED redis> SADD tag "C++" "Programming" "Mastering Series" QUEUED redis> SMEMBERS tag QUEUED redis> EXEC 1) OK 2) "Mastering C++ in 21 days" 3) (integer) 3 4) 1) "Mastering Series" 2) "C++" 3) "Programming" ~~~ 一個事務從開始到執行會經歷以下三個階段: 1. 開始事務。 1. 命令入隊。 1. 執行事務。 下文將分別介紹事務的這三個階段。 ### 開始事務 [MULTI](http://redis.readthedocs.org/en/latest/transaction/multi.html#multi "(in Redis 命令參考 v2.8)") 命令的執行標記著事務的開始: ~~~ redis> MULTI OK ~~~ 這個命令唯一做的就是,將客戶端的 `REDIS_MULTI` 選項打開,讓客戶端從非事務狀態切換到事務狀態。 ![digraph normal_to_transaction { rankdir = LR; node [shape = circle, style = filled]; edge [style = bold]; label = "客戶端狀態的切換"; normal [label = "非事務狀態", fillcolor = "#FADCAD"]; transaction [label = "事務狀態", fillcolor = "#A8E270"]; normal -> transaction [label = "打開選項\nREDIS_MULTI"];}](https://box.kancloud.cn/2015-09-13_55f4effd1373a.svg) ### 命令入隊 當客戶端處于非事務狀態下時,所有發送給服務器端的命令都會立即被服務器執行: ~~~ redis> SET msg "hello moto" OK redis> GET msg "hello moto" ~~~ 但是,當客戶端進入事務狀態之后,服務器在收到來自客戶端的命令時,不會立即執行命令,而是將這些命令全部放進一個事務隊列里,然后返回 `QUEUED` ,表示命令已入隊: ~~~ redis> MULTI OK redis> SET msg "hello moto" QUEUED redis> GET msg QUEUED ~~~ 以下流程圖展示了這一行為: ![digraph enqueue { node [shape = plaintext, style = filled]; edge [style = bold]; command_in [label = "服務器接到來自客戶端的命令"]; in_transaction_or_not [label = "客戶端是否正處于事務狀態?", shape = diamond, fillcolor = "#95BBE3"]; enqueu_command [label = "將命令放進事務隊列里", fillcolor = "#A8E270"]; return_enqueued [label = "向客戶端返回 QUEUED 字符串\n表示命令已入隊", fillcolor = "#A8E270"]; exec_command [label = "執行命令", fillcolor = "#FADCAD"]; return_command_result [label = "向客戶端返回命令的執行結果", fillcolor = "#FADCAD"]; // command_in -> in_transaction_or_not; in_transaction_or_not -> enqueu_command [label = "是"]; in_transaction_or_not -> exec_command [label = "否"]; exec_command -> return_command_result; enqueu_command -> return_enqueued;}](https://box.kancloud.cn/2015-09-13_55f4effd23181.svg) 事務隊列是一個數組,每個數組項是都包含三個屬性: 1. 要執行的命令(cmd)。 1. 命令的參數(argv)。 1. 參數的個數(argc)。 舉個例子,如果客戶端執行以下命令: ~~~ redis> MULTI OK redis> SET book-name "Mastering C++ in 21 days" QUEUED redis> GET book-name QUEUED redis> SADD tag "C++" "Programming" "Mastering Series" QUEUED redis> SMEMBERS tag QUEUED ~~~ 那么程序將為客戶端創建以下事務隊列: | 數組索引 | cmd | argv | argc | |-----|-----|-----|-----| | `0` | `SET` | `["book-name", "Mastering C++ in 21 days"]` | `2` | | `1` | `GET` | `["book-name"]` | `1` | | `2` | `SADD` | `["tag", "C++", "Programming", "Mastering Series"]` | `4` | | `3` | `SMEMBERS` | `["tag"]` | `1` | ### 執行事務 前面說到,當客戶端進入事務狀態之后,客戶端發送的命令就會被放進事務隊列里。 但其實并不是所有的命令都會被放進事務隊列,其中的例外就是 [EXEC](http://redis.readthedocs.org/en/latest/transaction/exec.html#exec "(in Redis 命令參考 v2.8)") 、 [DISCARD](http://redis.readthedocs.org/en/latest/transaction/discard.html#discard "(in Redis 命令參考 v2.8)") 、 [MULTI](http://redis.readthedocs.org/en/latest/transaction/multi.html#multi "(in Redis 命令參考 v2.8)") 和 [WATCH](http://redis.readthedocs.org/en/latest/transaction/watch.html#watch "(in Redis 命令參考 v2.8)") 這四個命令 ——當這四個命令從客戶端發送到服務器時,它們會像客戶端處于非事務狀態一樣,直接被服務器執行: ![digraph not_enque_command { node [shape = plaintext, style = filled]; edge [style = bold]; command_in [label = "服務器接到來自客戶端的命令"]; in_transaction_or_not [label = "客戶端是否正處于事務狀態?", shape = diamond, fillcolor = "#95BBE3"]; not_exec_and_discard [label = "命令是否\nEXEC 、 DISCARD 、\nMULTI 或 WATCH ?", shape = diamond, fillcolor = "#FFC1C1"]; enqueu_command [label = "將命令放進事務隊列里", fillcolor = "#A8E270"]; return_enqueued [label = "向客戶端返回 QUEUED 字符串\n表示命令已入隊", fillcolor = "#A8E270"]; exec_command [label = "執行命令", fillcolor = "#FADCAD"]; return_command_result [label = "向客戶端返回命令的執行結果", fillcolor = "#FADCAD"]; // command_in -> in_transaction_or_not; in_transaction_or_not -> not_exec_and_discard [label = "是"]; not_exec_and_discard -> enqueu_command [label = "否"]; not_exec_and_discard -> exec_command [label = "是"]; in_transaction_or_not -> exec_command [label = "否"]; exec_command -> return_command_result; enqueu_command -> return_enqueued;}](https://box.kancloud.cn/2015-09-13_55f4effd2ccb1.svg) 如果客戶端正處于事務狀態,那么當 [EXEC](http://redis.readthedocs.org/en/latest/transaction/exec.html#exec "(in Redis 命令參考 v2.8)") 命令執行時,服務器根據客戶端所保存的事務隊列,以先進先出(FIFO)的方式執行事務隊列中的命令:最先入隊的命令最先執行,而最后入隊的命令最后執行。 比如說,對于以下事務隊列: | 數組索引 | cmd | argv | argc | |-----|-----|-----|-----| | `0` | `SET` | `["book-name", "Mastering C++ in 21 days"]` | `2` | | `1` | `GET` | `["book-name"]` | `1` | | `2` | `SADD` | `["tag", "C++", "Programming", "Mastering Series"]` | `4` | | `3` | `SMEMBERS` | `["tag"]` | `1` | 程序會首先執行 [SET](http://redis.readthedocs.org/en/latest/string/set.html#set "(in Redis 命令參考 v2.8)") 命令,然后執行 [GET](http://redis.readthedocs.org/en/latest/string/get.html#get "(in Redis 命令參考 v2.8)") 命令,再然后執行 [SADD](http://redis.readthedocs.org/en/latest/set/sadd.html#sadd "(in Redis 命令參考 v2.8)") 命令,最后執行 [SMEMBERS](http://redis.readthedocs.org/en/latest/set/smembers.html#smembers "(in Redis 命令參考 v2.8)") 命令。 執行事務中的命令所得的結果會以 FIFO 的順序保存到一個回復隊列中。 比如說,對于上面給出的事務隊列,程序將為隊列中的命令創建如下回復隊列: | 數組索引 | 回復類型 | 回復內容 | |-----|-----|-----| | `0` | status code reply | `OK` | | `1` | bulk reply | `"Mastering C++ in 21 days"` | | `2` | integer reply | `3` | | `3` | multi-bulk reply | `["Mastering Series", "C++", "Programming"]` | 當事務隊列里的所有命令被執行完之后,[EXEC](http://redis.readthedocs.org/en/latest/transaction/exec.html#exec "(in Redis 命令參考 v2.8)") 命令會將回復隊列作為自己的執行結果返回給客戶端,客戶端從事務狀態返回到非事務狀態,至此,事務執行完畢。 事務的整個執行過程可以用以下偽代碼表示: ~~~ def execute_transaction(): # 創建空白的回復隊列 reply_queue = [] # 取出事務隊列里的所有命令、參數和參數數量 for cmd, argv, argc in client.transaction_queue: # 執行命令,并取得命令的返回值 reply = execute_redis_command(cmd, argv, argc) # 將返回值追加到回復隊列末尾 reply_queue.append(reply) # 清除客戶端的事務狀態 clear_transaction_state(client) # 清空事務隊列 clear_transaction_queue(client) # 將事務的執行結果返回給客戶端 send_reply_to_client(client, reply_queue) ~~~ ### 在事務和非事務狀態下執行命令 無論在事務狀態下,還是在非事務狀態下,Redis 命令都由同一個函數執行,所以它們共享很多服務器的一般設置,比如 AOF 的配置、RDB 的配置,以及內存限制,等等。 不過事務中的命令和普通命令在執行上還是有一點區別的,其中最重要的兩點是: 1. 非事務狀態下的命令以單個命令為單位執行,前一個命令和后一個命令的客戶端不一定是同一個; 而事務狀態則是以一個事務為單位,執行事務隊列中的所有命令:除非當前事務執行完畢,否則服務器不會中斷事務,也不會執行其他客戶端的其他命令。 1. 在非事務狀態下,執行命令所得的結果會立即被返回給客戶端; 而事務則是將所有命令的結果集合到回復隊列,再作為 [EXEC](http://redis.readthedocs.org/en/latest/transaction/exec.html#exec "(in Redis 命令參考 v2.8)") 命令的結果返回給客戶端。 ### 事務狀態下的 DISCARD 、 MULTI 和 WATCH 命令 除了 [EXEC](http://redis.readthedocs.org/en/latest/transaction/exec.html#exec "(in Redis 命令參考 v2.8)") 之外,服務器在客戶端處于事務狀態時,不加入到事務隊列而直接執行的另外三個命令是 [DISCARD](http://redis.readthedocs.org/en/latest/transaction/discard.html#discard "(in Redis 命令參考 v2.8)") 、 [MULTI](http://redis.readthedocs.org/en/latest/transaction/multi.html#multi "(in Redis 命令參考 v2.8)") 和 [WATCH](http://redis.readthedocs.org/en/latest/transaction/watch.html#watch "(in Redis 命令參考 v2.8)") 。 [DISCARD](http://redis.readthedocs.org/en/latest/transaction/discard.html#discard "(in Redis 命令參考 v2.8)") 命令用于取消一個事務,它清空客戶端的整個事務隊列,然后將客戶端從事務狀態調整回非事務狀態,最后返回字符串 `OK` 給客戶端,說明事務已被取消。 Redis 的事務是不可嵌套的,當客戶端已經處于事務狀態,而客戶端又再向服務器發送 [MULTI](http://redis.readthedocs.org/en/latest/transaction/multi.html#multi "(in Redis 命令參考 v2.8)") 時,服務器只是簡單地向客戶端發送一個錯誤,然后繼續等待其他命令的入隊。[MULTI](http://redis.readthedocs.org/en/latest/transaction/multi.html#multi "(in Redis 命令參考 v2.8)") 命令的發送不會造成整個事務失敗,也不會修改事務隊列中已有的數據。 [WATCH](http://redis.readthedocs.org/en/latest/transaction/watch.html#watch "(in Redis 命令參考 v2.8)") 只能在客戶端進入事務狀態之前執行,在事務狀態下發送 [WATCH](http://redis.readthedocs.org/en/latest/transaction/watch.html#watch "(in Redis 命令參考 v2.8)") 命令會引發一個錯誤,但它不會造成整個事務失敗,也不會修改事務隊列中已有的數據(和前面處理 [MULTI](http://redis.readthedocs.org/en/latest/transaction/multi.html#multi "(in Redis 命令參考 v2.8)") 的情況一樣)。 ### 帶 WATCH 的事務 [WATCH](http://redis.readthedocs.org/en/latest/transaction/watch.html#watch "(in Redis 命令參考 v2.8)") [http://redis.readthedocs.org/en/latest/transaction/watch.html#watch] 命令用于在事務開始之前監視任意數量的鍵:當調用 [EXEC](http://redis.readthedocs.org/en/latest/transaction/exec.html#exec "(in Redis 命令參考 v2.8)") [http://redis.readthedocs.org/en/latest/transaction/exec.html#exec] 命令執行事務時,如果任意一個被監視的鍵已經被其他客戶端修改了,那么整個事務不再執行,直接返回失敗。 以下示例展示了一個執行失敗的事務例子: ~~~ redis> WATCH name OK redis> MULTI OK redis> SET name peter QUEUED redis> EXEC (nil) ~~~ 以下執行序列展示了上面的例子是如何失敗的: | 時間 | 客戶端 A | 客戶端 B | |-----|-----|-----| | T1 | `WATCH name` | ? | | T2 | `MULTI` | ? | | T3 | `SET name peter` | ? | | T4 | ? | `SET name john` | | T5 | `EXEC` | ? | 在時間 T4 ,客戶端 B 修改了 `name` 鍵的值,當客戶端 A 在 T5 執行 [EXEC](http://redis.readthedocs.org/en/latest/transaction/exec.html#exec "(in Redis 命令參考 v2.8)") 時,Redis 會發現 `name` 這個被監視的鍵已經被修改,因此客戶端 A 的事務不會被執行,而是直接返回失敗。 下文就來介紹 [WATCH](http://redis.readthedocs.org/en/latest/transaction/watch.html#watch "(in Redis 命令參考 v2.8)") 的實現機制,并且看看事務系統是如何檢查某個被監視的鍵是否被修改,從而保證事務的安全性的。 ### WATCH 命令的實現 在每個代表數據庫的 `redis.h/redisDb` 結構類型中,都保存了一個 `watched_keys` 字典,字典的鍵是這個數據庫被監視的鍵,而字典的值則是一個鏈表,鏈表中保存了所有監視這個鍵的客戶端。 比如說,以下字典就展示了一個 `watched_keys` 字典的例子: ![digraph watched_keys { rankdir = LR; node [shape = record, style = filled]; edge [style = bold]; // keys watched_keys [label = "watched_keys |<key1> key1 |<key2> key2 |<key3> key3 | ... |<keyN> keyN", fillcolor = "#A8E270"]; // clients blocking for key1 client1 [label = "client1", fillcolor = "#95BBE3"]; client5 [label = "client5", fillcolor = "#95BBE3"]; client2 [label = "client2", fillcolor = "#95BBE3"]; null_1 [label = "NULL", shape = plaintext]; watched_keys:key1 -> client2; client2 -> client5; client5 -> client1; client1 -> null_1; // clients blocking for key2 client7 [label = "client7", fillcolor = "#95BBE3"]; null_2 [label = "NULL", shape = plaintext]; watched_keys:key2 -> client7; client7 -> null_2; // key3 client3 [label = "client3", fillcolor = "#95BBE3"]; client4 [label = "client4", fillcolor = "#95BBE3"]; client6 [label = "client6", fillcolor = "#95BBE3"]; null_3 [label = "NULL", shape = plaintext]; watched_keys:key3 -> client3; client3 -> client4; client4 -> client6; client6 -> null_3;}](https://box.kancloud.cn/2015-09-13_55f4effd35981.svg) 其中, 鍵 `key1` 正在被 `client2` 、 `client5` 和 `client1` 三個客戶端監視,其他一些鍵也分別被其他別的客戶端監視著。 [WATCH](http://redis.readthedocs.org/en/latest/transaction/watch.html#watch "(in Redis 命令參考 v2.8)") 命令的作用,就是將當前客戶端和要監視的鍵在 `watched_keys` 中進行關聯。 舉個例子,如果當前客戶端為 `client10086` ,那么當客戶端執行 `WATCH key1 key2` 時,前面展示的 `watched_keys` 將被修改成這個樣子: ![digraph new_watched_keys { rankdir = LR; node [shape = record, style = filled]; edge [style = bold]; // keys watched_keys [label = "watched_keys |<key1> key1 |<key2> key2 |<key3> key3 | ... |<keyN> keyN", fillcolor = "#A8E270"]; // clients blocking for key1 client1 [label = "client1", fillcolor = "#95BBE3"]; client5 [label = "client5", fillcolor = "#95BBE3"]; client2 [label = "client2", fillcolor = "#95BBE3"]; client10086 [label = "client10086", fillcolor = "#FFC1C1"]; null_1 [label = "NULL", shape = plaintext]; watched_keys:key1 -> client2; client2 -> client5; client5 -> client1; client1 -> client10086; client10086 -> null_1; // clients blocking for key2 client7 [label = "client7", fillcolor = "#95BBE3"]; client10086_2 [label = "client10086", fillcolor = "#FFC1C1"]; null_2 [label = "NULL", shape = plaintext]; watched_keys:key2 -> client7; client7 -> client10086_2; client10086_2 -> null_2; // key3 client3 [label = "client3", fillcolor = "#95BBE3"]; client4 [label = "client4", fillcolor = "#95BBE3"]; client6 [label = "client6", fillcolor = "#95BBE3"]; null_3 [label = "NULL", shape = plaintext]; watched_keys:key3 -> client3; client3 -> client4; client4 -> client6; client6 -> null_3;}](https://box.kancloud.cn/2015-09-13_55f4effd40188.svg) 通過 `watched_keys` 字典,如果程序想檢查某個鍵是否被監視,那么它只要檢查字典中是否存在這個鍵即可;如果程序要獲取監視某個鍵的所有客戶端,那么只要取出鍵的值(一個鏈表),然后對鏈表進行遍歷即可。 ### WATCH 的觸發 在任何對數據庫鍵空間(key space)進行修改的命令成功執行之后(比如 [FLUSHDB](http://redis.readthedocs.org/en/latest/server/flushdb.html#flushdb "(in Redis 命令參考 v2.8)") 、 [SET](http://redis.readthedocs.org/en/latest/string/set.html#set "(in Redis 命令參考 v2.8)") 、 [DEL](http://redis.readthedocs.org/en/latest/key/del.html#del "(in Redis 命令參考 v2.8)") 、 [LPUSH](http://redis.readthedocs.org/en/latest/list/lpush.html#lpush "(in Redis 命令參考 v2.8)") 、 [SADD](http://redis.readthedocs.org/en/latest/set/sadd.html#sadd "(in Redis 命令參考 v2.8)") 、 [ZREM](http://redis.readthedocs.org/en/latest/sorted_set/zrem.html#zrem "(in Redis 命令參考 v2.8)") [http://redis.readthedocs.org/en/latest/sorted_set/zrem.html#zrem] ,諸如此類),`multi.c/touchWatchedKey` 函數都會被調用 ——它檢查數據庫的 `watched_keys` 字典,看是否有客戶端在監視已經被命令修改的鍵,如果有的話,程序將所有監視這個/這些被修改鍵的客戶端的 `REDIS_DIRTY_CAS` 選項打開: ![digraph dirty_cas { rankdir = LR; node [shape = circle, style = filled]; edge [style = bold]; label = "客戶端狀態的切換"; normal [label = "非事務狀態", fillcolor = "#FADCAD"]; transaction [label = "事務狀態", fillcolor = "#A8E270"]; dirty_cas [label = "事務安全性\n已被破壞", fillcolor = "#B22222"]; normal -> transaction [label = "打開選項\nREDIS_MULTI"]; transaction -> dirty_cas [label = "打開選項\nREDIS_DIRTY_CAS"];}](https://box.kancloud.cn/2015-09-13_55f4effd4aae6.svg) 當客戶端發送 [EXEC](http://redis.readthedocs.org/en/latest/transaction/exec.html#exec "(in Redis 命令參考 v2.8)") 命令、觸發事務執行時,服務器會對客戶端的狀態進行檢查: - 如果客戶端的 `REDIS_DIRTY_CAS` 選項已經被打開,那么說明被客戶端監視的鍵至少有一個已經被修改了,事務的安全性已經被破壞。服務器會放棄執行這個事務,直接向客戶端返回空回復,表示事務執行失敗。 - 如果 `REDIS_DIRTY_CAS` 選項沒有被打開,那么說明所有監視鍵都安全,服務器正式執行事務。 可以用一段偽代碼來表示這個檢查: ~~~ def check_safety_before_execute_trasaction(): if client.state & REDIS_DIRTY_CAS: # 安全性已破壞,清除事務狀態 clear_transaction_state(client) # 清空事務隊列 clear_transaction_queue(client) # 返回空回復給客戶端 send_empty_reply(client) else: # 安全性完好,執行事務 execute_transaction() ~~~ 舉個例子,假設數據庫的 `watched_keys` 字典如下圖所示: ![digraph watched_keys { rankdir = LR; node [shape = record, style = filled]; edge [style = bold]; // keys watched_keys [label = "watched_keys |<key1> key1 |<key2> key2 |<key3> key3 | ... |<keyN> keyN", fillcolor = "#A8E270"]; // clients blocking for key1 client1 [label = "client1", fillcolor = "#95BBE3"]; client5 [label = "client5", fillcolor = "#95BBE3"]; client2 [label = "client2", fillcolor = "#95BBE3"]; null_1 [label = "NULL", shape = plaintext]; watched_keys:key1 -> client2; client2 -> client5; client5 -> client1; client1 -> null_1; // clients blocking for key2 client7 [label = "client7", fillcolor = "#95BBE3"]; null_2 [label = "NULL", shape = plaintext]; watched_keys:key2 -> client7; client7 -> null_2; // key3 client3 [label = "client3", fillcolor = "#95BBE3"]; client4 [label = "client4", fillcolor = "#95BBE3"]; client6 [label = "client6", fillcolor = "#95BBE3"]; null_3 [label = "NULL", shape = plaintext]; watched_keys:key3 -> client3; client3 -> client4; client4 -> client6; client6 -> null_3;}](https://box.kancloud.cn/2015-09-13_55f4effd51be3.svg) 如果某個客戶端對 `key1` 進行了修改(比如執行 `DEL key1` ),那么所有監視 `key1` 的客戶端,包括 `client2` 、 `client5` 和 `client1` 的 `REDIS_DIRTY_CAS` 選項都會被打開,當客戶端 `client2` 、 `client5` 和 `client1` 執行 [EXEC](http://redis.readthedocs.org/en/latest/transaction/exec.html#exec "(in Redis 命令參考 v2.8)") 的時候,它們的事務都會以失敗告終。 最后,當一個客戶端結束它的事務時,無論事務是成功執行,還是失敗, `watched_keys` 字典中和這個客戶端相關的資料都會被清除。 ### 事務的 ACID 性質 Warning 勘誤:Redis 的事務是保證原子性的,本節的內容將原子性和回滾功能混淆了,等待修復中。 —— 2013.6.23 在傳統的關系式數據庫中,常常用 [ACID 性質](http://en.wikipedia.org/wiki/ACID) [http://en.wikipedia.org/wiki/ACID]來檢驗事務功能的安全性。 Redis 事務保證了其中的一致性(C)和隔離性(I),但并不保證原子性(A)和持久性(D)。 以下四小節是關于這四個性質的詳細討論。 ### 原子性(Atomicity) 單個 Redis 命令的執行是原子性的,但 Redis 沒有在事務上增加任何維持原子性的機制,所以 Redis 事務的執行并不是原子性的。 如果一個事務隊列中的所有命令都被成功地執行,那么稱這個事務執行成功。 另一方面,如果 Redis 服務器進程在執行事務的過程中被停止 —— 比如接到 KILL 信號、宿主機器停機,等等,那么事務執行失敗。 當事務失敗時,Redis 也不會進行任何的重試或者回滾動作。 ### 一致性(Consistency) Redis 的一致性問題可以分為三部分來討論:入隊錯誤、執行錯誤、Redis 進程被終結。 #### 入隊錯誤 在命令入隊的過程中,如果客戶端向服務器發送了錯誤的命令,比如命令的參數數量不對,等等,那么服務器將向客戶端返回一個出錯信息,并且將客戶端的事務狀態設為 `REDIS_DIRTY_EXEC` 。 當客戶端執行 [EXEC](http://redis.readthedocs.org/en/latest/transaction/exec.html#exec "(in Redis 命令參考 v2.8)") 命令時,Redis 會拒絕執行狀態為 `REDIS_DIRTY_EXEC` 的事務,并返回失敗信息。 ~~~ redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> set key (error) ERR wrong number of arguments for 'set' command redis 127.0.0.1:6379> EXISTS key QUEUED redis 127.0.0.1:6379> EXEC (error) EXECABORT Transaction discarded because of previous errors. ~~~ 因此,帶有不正確入隊命令的事務不會被執行,也不會影響數據庫的一致性。 #### 執行錯誤 如果命令在事務執行的過程中發生錯誤,比如說,對一個不同類型的 key 執行了錯誤的操作,那么 Redis 只會將錯誤包含在事務的結果中,這不會引起事務中斷或整個失敗,不會影響已執行事務命令的結果,也不會影響后面要執行的事務命令,所以它對事務的一致性也沒有影響。 #### Redis 進程被終結 如果 Redis 服務器進程在執行事務的過程中被其他進程終結,或者被管理員強制殺死,那么根據 Redis 所使用的持久化模式,可能有以下情況出現: - 內存模式:如果 Redis 沒有采取任何持久化機制,那么重啟之后的數據庫總是空白的,所以數據總是一致的。 - RDB 模式:在執行事務時,Redis 不會中斷事務去執行保存 RDB 的工作,只有在事務執行之后,保存 RDB 的工作才有可能開始。所以當 RDB 模式下的 Redis 服務器進程在事務中途被殺死時,事務內執行的命令,不管成功了多少,都不會被保存到 RDB 文件里。恢復數據庫需要使用現有的 RDB 文件,而這個 RDB 文件的數據保存的是最近一次的數據庫快照(snapshot),所以它的數據可能不是最新的,但只要 RDB 文件本身沒有因為其他問題而出錯,那么還原后的數據庫就是一致的。 - AOF 模式:因為保存 AOF 文件的工作在后臺線程進行,所以即使是在事務執行的中途,保存 AOF 文件的工作也可以繼續進行,因此,根據事務語句是否被寫入并保存到 AOF 文件,有以下兩種情況發生: 1)如果事務語句未寫入到 AOF 文件,或 AOF 未被 SYNC 調用保存到磁盤,那么當進程被殺死之后,Redis 可以根據最近一次成功保存到磁盤的 AOF 文件來還原數據庫,只要 AOF 文件本身沒有因為其他問題而出錯,那么還原后的數據庫總是一致的,但其中的數據不一定是最新的。 2)如果事務的部分語句被寫入到 AOF 文件,并且 AOF 文件被成功保存,那么不完整的事務執行信息就會遺留在 AOF 文件里,當重啟 Redis 時,程序會檢測到 AOF 文件并不完整,Redis 會退出,并報告錯誤。需要使用 redis-check-aof 工具將部分成功的事務命令移除之后,才能再次啟動服務器。還原之后的數據總是一致的,而且數據也是最新的(直到事務執行之前為止)。 ### 隔離性(Isolation) Redis 是單進程程序,并且它保證在執行事務時,不會對事務進行中斷,事務可以運行直到執行完所有事務隊列中的命令為止。因此,Redis 的事務是總是帶有隔離性的。 ### 持久性(Durability) 因為事務不過是用隊列包裹起了一組 Redis 命令,并沒有提供任何額外的持久性功能,所以事務的持久性由 Redis 所使用的持久化模式決定: - 在單純的內存模式下,事務肯定是不持久的。 - 在 RDB 模式下,服務器可能在事務執行之后、RDB 文件更新之前的這段時間失敗,所以 RDB 模式下的 Redis 事務也是不持久的。 - 在 AOF 的“總是 SYNC ”模式下,事務的每條命令在執行成功之后,都會立即調用 `fsync` 或 `fdatasync` 將事務數據寫入到 AOF 文件。但是,這種保存是由后臺線程進行的,主線程不會阻塞直到保存成功,所以從命令執行成功到數據保存到硬盤之間,還是有一段非常小的間隔,所以這種模式下的事務也是不持久的。 其他 AOF 模式也和“總是 SYNC ”模式類似,所以它們都是不持久的。 ### 小結 - 事務提供了一種將多個命令打包,然后一次性、有序地執行的機制。 - 事務在執行過程中不會被中斷,所有事務命令執行完之后,事務才能結束。 - 多個命令會被入隊到事務隊列中,然后按先進先出(FIFO)的順序執行。 - 帶 `WATCH` 命令的事務會將客戶端和被監視的鍵在數據庫的 `watched_keys` 字典中進行關聯,當鍵被修改時,程序會將所有監視被修改鍵的客戶端的 `REDIS_DIRTY_CAS` 選項打開。 - 只有在客戶端的 `REDIS_DIRTY_CAS` 選項未被打開時,才能執行事務,否則事務直接返回失敗。 - Redis 的事務保證了 ACID 中的一致性(C)和隔離性(I),但并不保證原子性(A)和持久性(D)。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看