1. 程式人生 > 實用技巧 >有了這個IDEA外掛,從此不用再開Postman了

有了這個IDEA外掛,從此不用再開Postman了

事務

事務指的程式中一系列嚴密的邏輯操作,其中包含的操作必須要完成,否則在每個操作中的更改都會被撤銷。

舉個簡單的例子:一群鴨子過河,要麼都過去,要麼都不過去。

事務的特性

  1. 原子性(Atomicity):操作這些指令時,要麼全部執行成功,要麼全部不執行。只要其中一個指令執行失敗,所有的指令都執行失敗,資料進行回滾,回到執行指令前的資料狀態。
  2. 一致性(Consistency):事務的執行使資料從一個狀態轉換為另一個狀態,但是對於整個資料的完整性保持穩定。(可理解為:即A賬戶只要減去了100,B賬戶則必定加上了100)
  3. 隔離性(Isolation):隔離性是當多個使用者併發訪問資料庫時,比如操作同一張表時,資料庫為每一個使用者開啟的事務,不能被其他事務的操作所幹擾,多個併發事務之間要相互隔離。
  4. 永續性(Durability):當事務正確完成後,它對於資料的改變是永久性的。

Redis事務

Redis事務:把多個redis命令放到佇列,然後一次性的順序執行。並且在執行過程中不會被中斷,執行完所有佇列命令後才執行其他客戶端其他命令。

下面舉個簡單的例子,MULTI 開啟一個事務, 然後將多個命令入隊到事務中, 最後由 EXEC命令觸發事務, 一併執行事務中的所有命令

一個事務從開始到執行經歷三個階段

  1. 開始事務
  2. 命令入隊
  3. 執行事務

開始事務

MULTI 命令的執行標誌著事務的開始,這個命令做的就是, 將客戶端的REDIS_MULTI選項開啟, 讓客戶端從非事務狀態切換到事務狀態。

命令入隊

當客戶端處於非事務狀態下,所有傳送給服務端的命令會立即被執行。

但客戶端切換到事務狀態後,服務端接受到客戶端的命令不會立即執行,而是把這些命令放到事務佇列裡,然後返回QUEUED, 表示命令已入隊。

可以由一下流程圖表示:

事務佇列是一個數組, 每個陣列項是都包含三個屬性:

  1. 要執行的命令(cmd)
  2. 命令的引數(argv)
  3. 引數的個數(argc)

以上圖命令為例子,那麼程式將為客戶端建立以下事務佇列:

陣列索引 cmd argv argc
0 SET ["name","xiaoming"] 2
1 SET ["age","25"] 2
2 INCR ["age"] 1

執行事務

客戶端進入到事務狀態後,客戶端傳送的命令不會直接被執行,而是會放到事務佇列裡。

但並不是所有命令都會放到事務佇列,如EXEC、 DISCARD、 MULTI和 WATCH這四個命令,無視事務狀態,直接被伺服器執行。

如果客戶端正處於事務狀態, 那麼當 EXEC命令執行時, 伺服器根據客戶端所儲存的事務佇列, 以先進先出(FIFO)的方式執行事務佇列中的命令: 最先入隊的命令最先執行, 而最後入隊的命令最後執行。

執行事務中的命令所得的結果會以 FIFO 的順序儲存到一個回覆佇列中。

如上圖,程式將為佇列中的命令建立如下回復佇列:

陣列索引 回覆型別 回覆內容
0 status code reply OK
1 status code reply OK
2 integer reply 26

當事務佇列裡的所有命令被執行完之後, EXEC命令會將回復佇列作為自己的執行結果返回給客戶端, 客戶端從事務狀態返回到非事務狀態, 至此, 事務執行完畢。

事務過程虛擬碼:

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)

DISCARD、MULTI、WATCH命令

  • DISCRAD 命令用於取消一個事務, 它清空客戶端的整個事務佇列, 然後將客戶端從事務狀態調整回非事務狀態, 最後返回字串OK給客戶端, 說明事務已被取消。
  • MULTI命令開啟一個事務,Redis 的事務是不可巢狀的, 當客戶端已經處於事務狀態, 而客戶端又再向伺服器傳送 MULTI時, 伺服器只是簡單地向客戶端傳送一個錯誤, 然後繼續等待其他命令的入隊。 MULTI命令的傳送不會造成整個事務失敗, 也不會修改事務佇列中已有的資料。
  • WATCH命令只能在客戶端進入事務狀態之前執行, 在事務狀態下發送 WATCH命令會引發一個錯誤, 但它不會造成整個事務失敗, 也不會修改事務佇列中已有的資料(和前面處理 MULTI的情況一樣)

帶 WATCH 的事務

WATCH 命令用於在事務開始之前監視任意數量的鍵: 當呼叫 EXEC命令執行事務時, 如果任意一個被監視的鍵已經被其他客戶端修改了, 那麼整個事務不再執行, 直接返回失敗。

如下圖例子:

客戶端A

此時客戶端B修改了name

客戶端A執行事務失敗

watch命令實現

在每個代表資料庫的redis.h/redisDb結構型別中, 都儲存了一個watched_keys字典, 字典的鍵是這個資料庫被監視的鍵, 而字典的值則是一個連結串列, 連結串列中儲存了所有監視這個鍵的客戶端。

如下圖:

WATCH命令的作用, 就是將當前客戶端和要監視的鍵在watched_keys中進行關聯。

舉個例子,如果客戶端client5執行 WATCH key1 key2 時,上圖將變成下面這樣。

通過watched_keys字典, 如果程式想檢查某個鍵是否被監視, 那麼它只要檢查字典中是否存在這個鍵即可; 如果程式要獲取監視某個鍵的所有客戶端, 那麼只要取出鍵的值(一個連結串列), 然後對連結串列進行遍歷即可。

WATCH 觸發

任何對Redis鍵值的修改操作成功後,multi.c/touchWatchedKey函式都會被呼叫,它檢查資料庫的watched_keys字典, 看是否有客戶端在監視已經被命令修改的鍵, 如果有的話, 程式將所有監視這個/這些被修改鍵的客戶端的REDIS_DIRTY_CAS選項開啟:

當客戶端傳送 EXEC命令、觸發事務執行時, 伺服器會對客戶端的狀態進行檢查:

  • 如果客戶端的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字典中和這個客戶端相關的資料都會被清除。

Redis事務的ACID性質

todo

參考文獻

https://cloud.tencent.com/developer/article/1133074

http://redisdoc.com/topic/transaction.html

reids設計與實現