redis——事務

      網友投稿 561 2025-04-01

      Redis 事務可以一次執行多個命令, 并且帶有以下三個重要的保證:

      批量操作在發送 EXEC 命令前被放入隊列緩存。

      redis——事務

      收到 EXEC 命令后進入事務執行,事務中任意命令執行失敗,其余的命令依然被執行。

      在事務執行過程,其他客戶端提交的命令請求不會插入到事務執行命令序列中。

      一個事務從開始到執行會經歷以下三個階段:

      開始事務。

      命令入隊。

      執行事務。

      以下是一個事務的例子, 它先以?MULTI?開始一個事務, 然后將多個命令入隊到事務中, 最后由?EXEC?命令觸發事務, 一并執行事務中的所有命令:

      redis 127.0.0.1:6379> MULTI

      OK

      redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"

      QUEUED

      redis 127.0.0.1:6379> GET book-name

      QUEUED

      redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"

      QUEUED

      redis 127.0.0.1:6379> SMEMBERS tag

      QUEUED

      redis 127.0.0.1:6379> EXEC

      1) OK

      2) "Mastering C++ in 21 days"

      3) (integer) 3

      4) 1) "Mastering Series"

      2) "C++"

      3) "Programming"

      詳細介紹:

      事務開始

      MULTI?命令的執行標志著事務的開始:

      redis> MULTI

      OK

      MULTI?命令可以將執行該命令的客戶端從非事務狀態切換至事務狀態, 這一切換是通過在客戶端狀態的?flags?屬性中打開?Redis_MULTI?標識來完成的,?MULTI?命令的實現可以用以下偽代碼來表示:

      def MULTI():

      # 打開事務標識

      client.flags |= REDIS_MULTI

      # 返回 OK 回復

      replyOK()

      命令入隊

      當一個客戶端處于非事務狀態時, 這個客戶端發送的命令會立即被服務器執行:

      redis> SET "name" "Practical Common Lisp" OK redis> GET "name" "Practical Common Lisp" redis> SET "author" "Peter Seibel" OK redis> GET "author" "Peter Seibel"

      與此不同的是, 當一個客戶端切換到事務狀態之后, 服務器會根據這個客戶端發來的不同命令執行不同的操作:

      如果客戶端發送的命令為?EXEC?、?DISCARD?、?WATCH?、?MULTI?四個命令的其中一個, 那么服務器立即執行這個命令。

      與此相反, 如果客戶端發送的命令是?EXEC?、?DISCARD?、?WATCH?、?MULTI?四個命令以外的其他命令, 那么服務器并不立即執行這個命令, 而是將這個命令放入一個事務隊列里面, 然后向客戶端返回?QUEUED?回復。

      事務隊列

      每個 Redis 客戶端都有自己的事務狀態, 這個事務狀態保存在客戶端狀態的?mstate?屬性里面:

      typedef struct redisClient {

      // ...

      // 事務狀態

      multiState mstate; /* MULTI/EXEC state */

      // ...

      } redisClient;

      事務狀態包含一個事務隊列, 以及一個已入隊命令的計數器 (也可以說是事務隊列的長度):

      typedef struct multiState {

      // 事務隊列,FIFO 順序

      multiCmd *commands;

      // 已入隊命令計數

      int count;

      } multiState;

      事務隊列是一個?multiCmd?類型的數組, 數組中的每個?multiCmd?結構都保存了一個已入隊命令的相關信息, 包括指向命令實現函數的指針, 命令的參數, 以及參數的數量:

      typedef struct multiCmd {

      // 參數

      robj **argv;

      // 參數數量

      int argc;

      // 命令指針

      struct redisCommand *cmd;

      } multiCmd;

      事務隊列以先進先出(FIFO)的方式保存入隊的命令: 較先入隊的命令會被放到數組的前面, 而較后入隊的命令則會被放到數組的后面。

      舉個例子, 如果客戶端執行以下命令:

      redis> MULTI OK redis> SET "name" "Practical Common Lisp" QUEUED redis> GET "name" QUEUED redis> SET "author" "Peter Seibel" QUEUED redis> GET "author" QUEUED

      那么服務器將為客戶端創建事務狀態:

      最先入隊的?SET?命令被放在了事務隊列的索引?0?位置上。

      第二入隊的?GET?命令被放在了事務隊列的索引?1?位置上。

      第三入隊的另一個?SET?命令被放在了事務隊列的索引?2?位置上。

      最后入隊的另一個?GET?命令被放在了事務隊列的索引?3?位置上。

      執行事務

      當一個處于事務狀態的客戶端向服務器發送?EXEC?命令時, 這個?EXEC?命令將立即被服務器執行: 服務器會遍歷這個客戶端的事務隊列, 執行隊列中保存的所有命令, 最后將執行命令所得的結果全部返回給客戶端。

      EXEC?命令的實現原理可以用以下偽代碼來描述:

      def EXEC():

      # 創建空白的回復隊列

      reply_queue = []

      # 遍歷事務隊列中的每個項

      # 讀取命令的參數,參數的個數,以及要執行的命令

      for argv, argc, cmd in client.mstate.commands:

      # 執行命令,并取得命令的返回值

      reply = execute_command(cmd, argv, argc)

      # 將返回值追加到回復隊列末尾

      reply_queue.append(reply)

      # 移除 REDIS_MULTI 標識,讓客戶端回到非事務狀態

      client.flags &= ~REDIS_MULTI

      # 清空客戶端的事務狀態,包括:

      # 1)清零入隊命令計數器

      # 2)釋放事務隊列

      client.mstate.count = 0

      release_transaction_queue(client.mstate.commands)

      # 將事務的執行結果返回給客戶端

      send_reply_to_client(client, reply_queue)

      WATCH命令的實現

      WATCH命令是一個樂觀鎖,它可以在EXEC命令執行之前,監視任意數量的數據庫鍵,并在EXEC執行后,檢查被監視的鍵是否至少有一個被修改,如果是,服務器拒絕執行事務,并向客戶端返回代表事務執行失敗的回復。

      /* Redis database representation. There are multiple databases identified

      * by integers from 0 (the default database) up to the max configured

      * database. The database number is the 'id' field in the structure. */

      typedef struct redisDb {

      dict *dict; /* The keyspace for this DB 數據庫鍵空間,保存數據庫中所有的鍵值對*/

      dict *expires; /* Timeout of keys with a timeout set 保存過期時間*/

      dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */

      dict *ready_keys; /* Blocked keys that received a PUSH 已經準備好數據的阻塞狀態的key*/

      dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS 事物模塊,用于保存被WATCH命令所監控的鍵*/

      // 當內存不足時,Redis會根據LRU算法回收一部分鍵所占的空間,而該eviction_pool是一個長為16數組,保存可能被回收的鍵

      // eviction_pool中所有鍵按照idle空轉時間,從小到大排序,每次回收空轉時間最長的鍵

      struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */

      // 數據庫ID

      int id; /* Database ID */

      // 鍵的平均過期時間

      long long avg_ttl; /* Average TTL, just for stats */

      } redisDb;

      在每個代表數據庫的 server.h/redisDb?結構類型中, 都保存了一個?watched_keys?字典, 字典的鍵是這個數據庫被監視的鍵, 而字典的值則是一個鏈表, 鏈表中保存了所有監視這個鍵的客戶端。比如說,以下字典就展示了一個?watched_keys?字典的例子:

      每個key后掛著監視自己的客戶端。

      監控的觸發

      在任何對數據庫鍵空間(key space)進行修改的命令成功執行之后 (比如?FLUSHDB?、?SET?、?DEL?、?LPUSH?、?SADD?、?ZREM?,諸如此類),?multi.c/touchWatchedKey?函數都會被調用 (修改命令會調用signalModifiedKey()函數來處理數據庫中的鍵被修改的情況,該函數直接調用touchWatchedKey()函數)—— 它檢查數據庫的?watched_keys?字典, 看是否有客戶端在監視已經被命令修改的鍵, 如果有的話, 程序將所有監視這個/這些被修改鍵的客戶端的?REDIS_DIRTY_CAS?選項打開:

      /* "Touch" a key, so that if this key is being WATCHed by some client the

      * next EXEC will fail. */

      // Touch 一個 key,如果該key正在被監視,那么客戶端會執行EXEC失敗

      void touchWatchedKey(redisDb *db, robj *key) {

      list *clients;

      listIter li;

      listNode *ln;

      // 字典為空,沒有任何鍵被監視

      if (dictSize(db->watched_keys) == 0) return;

      // 獲取所有監視這個鍵的客戶端

      clients = dictFetchValue(db->watched_keys, key);

      // 沒找到返回

      if (!clients) return;

      /* Mark all the clients watching this key as CLIENT_DIRTY_CAS */

      /* Check if we are already watching for this key */

      // 遍歷所有客戶端,打開他們的 REDIS_DIRTY_CAS 標識

      listRewind(clients,&li);

      while((ln = listNext(&li))) {

      client *c = listNodeValue(ln);

      // 設置CLIENT_DIRTY_CAS標識

      c->flags |= CLIENT_DIRTY_CAS;

      }

      }

      事務的ACID性質

      在傳統的關系式數據庫中,常常用?ACID 性質來檢驗事務功能的安全性。

      redis事物總是具有前三個性質。

      a)原子性atomicity:redis事務保證事務中的命令要么全部執行要不全部不執行。

      但是redis不同于傳統關系型數據庫,不支持回滾,即使出現了錯誤,事務也會繼續執行下去。

      b)一致性consistency:redis事務可以保證命令失敗的情況下得以回滾,數據能恢復到沒有執行之前的樣子,是保證一致性的,除非redis進程意外終結。

      Redis 的一致性問題可以分為三部分來討論:入隊錯誤、執行錯誤、Redis 進程被終結。

      入隊錯誤

      在命令入隊的過程中,如果客戶端向服務器發送了錯誤的命令,比如命令的參數數量不對,等等, 那么服務器將向客戶端返回一個出錯信息, 并且將客戶端的事務狀態設為?REDIS_DIRTY_EXEC?。

      因此,帶有不正確入隊命令的事務不會被執行,也不會影響數據庫的一致性。

      執行錯誤

      如果命令在事務執行的過程中發生錯誤,比如說,對一個不同類型的 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 工具將部分成功的事務命令移除之后,才能再次啟動服務器。還原之后的數據總是一致的,而且數據也是最新的(直到事務執行之前為止)。

      c)隔離性Isolation:redis事務是嚴格遵守隔離性的,原因是redis是單進程單線程模式,可以保證命令執行過程中不會被其他客戶端命令打斷。

      因為redis使用單線程執行事務,并且保證不會中斷,所以肯定有隔離性。

      d)持久性Durability:持久性是指:當一個事務執行完畢,結果已經保存在永久介質里,比如硬盤,所以即使服務器后來停機了,結果也不會丟失

      redis事務是不保證持久性的,這是因為redis持久化策略中不管是RDB還是AOF都是異步執行的,不保證持久性是出于對性能的考慮。

      重點提煉

      事務提供了一種將多個命令打包, 然后一次性、有序地執行的機制。

      多個命令會被入隊到事務隊列中, 然后按先進先出(FIFO)的順序執行。

      事務在執行過程中不會被中斷, 當事務隊列中的所有命令都被執行完畢之后, 事務才會結束。

      帶有?WATCH?命令的事務會將客戶端和被監視的鍵在數據庫的?watched_keys?字典中進行關聯, 當鍵被修改時, 程序會將所有監視被修改鍵的客戶端的?REDIS_DIRTY_CAS?標志打開。

      只有在客戶端的?REDIS_DIRTY_CAS?標志未被打開時, 服務器才會執行客戶端提交的事務, 否則的話, 服務器將拒絕執行客戶端提交的事務。

      Redis 的事務總是保證 ACID 中的原子性、一致性和隔離性, 當服務器運行在 AOF 持久化模式下, 并且?appendfsync?選項的值為?always?時, 事務也具有耐久性。

      Redis 數據庫

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:wps 2019ppt 怎么添加布爾運算
      下一篇:excel表格怎么使用min函數(excel 中 怎么用min函數)
      相關文章
      亚洲综合色婷婷七月丁香| 亚洲av无码一区二区三区天堂 | 亚洲老妈激情一区二区三区| 无码一区二区三区亚洲人妻| 亚洲av日韩av永久无码电影| 亚洲一本一道一区二区三区| 亚洲精品免费网站| 亚洲AV日韩综合一区尤物| 国产精品高清视亚洲一区二区| 亚洲人成片在线观看| 亚洲人成网站在线观看播放动漫| 亚洲免费网站在线观看| 亚洲一区二区三区播放在线| 色在线亚洲视频www| tom影院亚洲国产一区二区| 久久亚洲精品国产精品婷婷 | 久久99亚洲综合精品首页| 亚洲色偷偷狠狠综合网| 国产AV无码专区亚洲AWWW| 中文字幕精品亚洲无线码一区应用| 亚洲综合伊人久久大杳蕉| 亚洲成AV人片在线观看ww| 无码乱人伦一区二区亚洲| 亚洲黄色三级视频| 亚洲女人影院想要爱| 国产精品亚洲精品| 亚洲成av人片天堂网无码】| 国产成人不卡亚洲精品91| 亚洲一区日韩高清中文字幕亚洲| 国产成人高清亚洲| 国产国拍亚洲精品mv在线观看| 亚洲国产精品久久| 亚洲另类图片另类电影| 一本色道久久88—综合亚洲精品| 色噜噜噜噜亚洲第一| 国产偷窥女洗浴在线观看亚洲| 国产亚洲精品国产| 亚洲毛片无码专区亚洲乱| 亚洲午夜精品一区二区麻豆| 18禁亚洲深夜福利人口| 久久久久亚洲精品无码网址|