1. 程式人生 > >Redis-Redi事務註意事項

Redis-Redi事務註意事項

exec blog 開始 重要 end pty 最重要的 alt send

技術分享

  當客戶端處於非事務狀態下時, 所有發送給服務器端的命令都會立即被服務器執行。但是, 當客戶端進入事務狀態之後, 服務器在收到來自客戶端的命令時, 不會立即執行命令, 而是將這些命令全部放進一個事務隊列裏, 然後返回 QUEUED , 表示命令已入隊。

  但其實並不是所有的命令都會被放進事務隊列, 其中的例外就是 EXEC 、 DISCARD 、 MULTI 和 WATCH 這四個命令 —— 當這四個命令從客戶端發送到服務器時, 它們會像客戶端處於非事務狀態一樣, 直接被服務器執行。

  事務的執行可以用如下偽代碼描述:

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)

  不過事務中的命令和普通命令在執行上還是有一點區別的,其中最重要的兩點是:

    非事務狀態下的命令以單個命令為單位執行,前一個命令和後一個命令的客戶端不一定是同一個。而事務狀態則是以一個事務為單位,執行 exec 後(放入隊列不算)會執行事務隊列中的所有命令,除非當前事務執行完畢,否則服務器不會中斷事務,也不會執行其他客戶端的其他命令執行 exec 後才進入真正執行事務狀態(不被中斷)

    在非事務狀態下,執行命令所得的結果會立即被返回給客戶端,而事務則是將所有命令的結果集合到回復隊列,再作為 EXEC 命令的結果返回給客戶端。

  DISCARD 命令用於取消一個事務, 它清空客戶端的整個事務隊列, 然後將客戶端從事務狀態調整回非事務狀態, 最後返回字符串 OK 給客戶端, 說明事務已被取消。

  Redis 的事務是不可嵌套的, 當客戶端已經處於事務狀態, 而客戶端又再向服務器發送 MULTI 時, 服務器只是簡單地向客戶端發送一個錯誤, 然後繼續等待其他命令的入隊。 MULTI 命令的發送不會造成整個事務失敗, 也不會修改事務隊列中已有的數據。

  WATCH 只能在客戶端進入事務狀態之前執行, 在事務狀態下發送 WATCH 命令會引發一個錯誤, 但它不會造成整個事務失敗, 也不會修改事務隊列中已有的數據(和前面處理 MULTI 的情況一樣)。該命令用於在事務開始之前監視任意數量的鍵,當調用 EXEC 命令執行事務時, 如果任意一個被監視的鍵已經被其他客戶端修改了, 那麽整個事務不再執行, 直接返回失敗。 即下圖中客戶端 A 將執行失敗

技術分享

  在時間 T4 ,客戶端 B 修改了 name 鍵的值, 當客戶端 A 在 T5 執行 EXEC 時,Redis 會發現 name 這個被監視的鍵已經被修改, 因此客戶端 A 的事務不會被執行,而是直接返回失敗。

  WATCH 實現:在每個代表數據庫的 redis.h/redisDb 結構類型中, 都保存了一個 watched_keys 字典, 字典的鍵是這個數據庫被監視的鍵, 而字典的值則是一個鏈表, 鏈表中保存了所有監視這個鍵的客戶端。

技術分享

  

  舉個例子, 如果當前客戶端為 client10086 , 那麽當客戶端執行 WATCH key1 key2 時, 前面展示的 watched_keys 將被修改成這個樣子:

技術分享

  在任何對數據庫鍵空間(key space)進行修改的命令成功執行之後 (比如 FLUSHDB 、 SET 、 DEL 、 LPUSH 、 SADD 、 ZREM ,諸如此類), 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()

  Redis 事務的 ACID 性質

Redis-Redi事務註意事項