精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Redis源碼學(xué)習(xí)之Redis事務(wù)

運(yùn)維 數(shù)據(jù)庫運(yùn)維 Redis
Redis作為一個(gè)內(nèi)存型數(shù)據(jù)庫,同樣支持傳統(tǒng)數(shù)據(jù)庫的事務(wù)特性。這篇文章會(huì)從源代碼角度來分析Redis中事務(wù)的實(shí)現(xiàn)原理。

Redis作為一個(gè)內(nèi)存型數(shù)據(jù)庫,同樣支持傳統(tǒng)數(shù)據(jù)庫的事務(wù)特性。這篇文章會(huì)從源代碼角度來分析Redis中事務(wù)的實(shí)現(xiàn)原理。

What

Redis事務(wù)提供了一種將多個(gè)命令請(qǐng)求打包,然后一次性、按照順序地執(zhí)行多個(gè)命令的機(jī)制,并且在事務(wù)執(zhí)行的期間,服務(wù)器不會(huì)中斷事務(wù)而去執(zhí)行其他不在事務(wù)中的命令請(qǐng)求,它會(huì)把事務(wù)中所有的命令都執(zhí)行完畢才會(huì)去執(zhí)行其他的命令。

How

Redis中提供了multi、discard、exec、watch、unwatch這幾個(gè)命令來實(shí)現(xiàn)事務(wù)的功能。

Redis的事務(wù)始于multi命令,之后跟著要在事務(wù)中執(zhí)行的命令,終于exec命令或者discard命令。加入事務(wù)中的所有命令會(huì)原子的執(zhí)行,中間不會(huì)穿插執(zhí)行其他沒有加入事務(wù)的命令。

multi、exec和discard

multi命令告訴Redis客戶端要開始一個(gè)事物,然后Redis會(huì)返回一個(gè)OK,接下來所有的命令Redis都不會(huì)立即執(zhí)行,只會(huì)返回QUEUED結(jié)果,直到遇到了exec命令才會(huì)去執(zhí)行之前的所有的命令,或者遇到了discard命令,會(huì)拋棄執(zhí)行之前加入事務(wù)的命令。

  1. 127.0.0.1:6379> get name 
  2.  
  3. (nil) 
  4.  
  5. 127.0.0.1:6379> get gender 
  6.  
  7. (nil) 
  8.  
  9. 127.0.0.1:6379> multi 
  10.  
  11. OK 
  12.  
  13. 127.0.0.1:6379> set name Slogen 
  14.  
  15. QUEUED 
  16.  
  17. 127.0.0.1:6379> set gender male 
  18.  
  19. QUEUED 
  20.  
  21. 127.0.0.1:6379> exec 
  22.  
  23. 1) OK 
  24.  
  25. 2) OK 
  26.  
  27. 127.0.0.1:6379> mget name gender 
  28.  
  29. 1) "Slogen" 
  30.  
  31. 2) "male"  

watch

watch命令是Redis提供的一個(gè)樂觀鎖,可以在exec執(zhí)行之前,監(jiān)視任意數(shù)量的數(shù)據(jù)庫key,并在exec命令執(zhí)行的時(shí)候,檢測被監(jiān)視的key是否至少有一個(gè)已經(jīng)被修改,如果是的話,服務(wù)器將拒絕執(zhí)行事務(wù),并向客戶端返回代表事務(wù)執(zhí)行失敗的空回復(fù)。

首先在client1執(zhí)行下列命令:

  1. 127.0.0.1:6379> get name 
  2.  
  3. (nil) 
  4.  
  5. 127.0.0.1:6379> watch name 
  6.  
  7. OK 
  8.  
  9. 127.0.0.1:6379> multi 
  10.  
  11. OK 
  12.  
  13. 127.0.0.1:6379> set name slogen 
  14.  
  15. QUEUED 
  16.  
  17. 127.0.0.1:6379> set gender male 
  18.  
  19. QUEUED 
  20.  
  21. 127.0.0.1:6379> get name 
  22.  
  23. QUEUED  

這個(gè)時(shí)候client還沒有執(zhí)行exec命令,接下來在client2下執(zhí)行下面命令修改name:

  1. 127.0.0.1:6379> set name rio 
  2.  
  3. OK 
  4.  
  5. 127.0.0.1:6379> get name 
  6.  
  7. "rio"  

接下來在client1下執(zhí)行exec命令:

  1. 127.0.0.1:6379> exec 
  2.  
  3. (nil) 
  4.  
  5. 127.0.0.1:6379> get name 
  6.  
  7. "rio"  

從執(zhí)行結(jié)果可以看到,在client1中執(zhí)行exec命令的時(shí)候,Redis會(huì)檢測到name字段已經(jīng)被其他客戶端修改了,所以拒絕執(zhí)行事務(wù)中所有的命令,直接返回nil表示執(zhí)行失敗。這個(gè)時(shí)候獲取到的name的值還是在client2中設(shè)置的rio。

Why

multi

Redis的事務(wù)始于multi命令,那么就從multi命令的源代碼開始分析。

當(dāng)Redis接收到客戶端發(fā)送過來的命令之后會(huì)執(zhí)行multiCommand()這個(gè)方法,這個(gè)方法在multi.c文件中。

  1. void multiCommand(client *c) { 
  2.  
  3.     // 1. 如果檢測到flags里面已經(jīng)包含了CLIENT_MULTI 
  4.  
  5.     // 表示對(duì)應(yīng)client已經(jīng)處于事務(wù)的上下文中,返回錯(cuò)誤 
  6.  
  7.     if (c->flags & CLIENT_MULTI) { 
  8.  
  9.         addReplyError(c,"MULTI calls can not be nested"); 
  10.  
  11.         return
  12.  
  13.     } 
  14.  
  15.     // 2. 開啟flags的CLIENT_MULTI標(biāo)識(shí) 
  16.  
  17.     c->flags |= CLIENT_MULTI; 
  18.  
  19.     // 3. 返回ok,告訴客戶端已經(jīng)成功開啟事務(wù) 
  20.  
  21.     addReply(c,shared.ok); 
  22.  
  23.  

從源代碼中可以看到,multiCommand()主要完成下面三件事:

  1. 檢測發(fā)送multi命令的client是否已經(jīng)處于事務(wù)中,如果是則直接返回錯(cuò)誤。從這里可以看到,Redis不支持事務(wù)嵌套執(zhí)行。
  2. 給對(duì)應(yīng)client的flags標(biāo)志位中增加MULTI_CLIENT標(biāo)志,表示已經(jīng)進(jìn)入事務(wù)中。
  3. 返回OK告訴客戶端已經(jīng)成功開啟事務(wù)。

從前面的文章中可以知道,Redis接收到所有的Client發(fā)送過來的命令后都會(huì)執(zhí)行到processCommand()這個(gè)方法中,在processCommand()中有下面這部分代碼: 

 

在processCommand()執(zhí)行實(shí)際的命令之前會(huì)先判斷對(duì)應(yīng)的client是否已經(jīng)處于事務(wù)的上下文中,如果是的話,且需要執(zhí)行的命令不是exec、discard、multi和watch這四個(gè)命令中的任何一個(gè),則調(diào)用queueMultiCommand()方法把需要執(zhí)行的命令加入隊(duì)列中,否則的話調(diào)用call()直接執(zhí)行命令。

queueMultiCommand()

Redis調(diào)用queueMultiCommand()方法把加入事務(wù)的命令加入Redis隊(duì)列中,實(shí)現(xiàn)如下: 

 

queueMultiCommand()方法主要是把要加入事務(wù)的命令封裝在multiCmd結(jié)構(gòu)的變量,然后放置到client->mstate.commands數(shù)組中去,multiCmd的定義如下:

  1. typedef struct multiCmd { 
  2.  
  3.     robj **argv; // 命令的參數(shù)數(shù)組 
  4.  
  5.     int argc; // 命令的參數(shù)個(gè)數(shù) 
  6.  
  7.     struct redisCommand *cmd; // 要執(zhí)行的命令 
  8.  
  9. } multiCmd;  

而mstate字段定義為:

  1. typedef struct client { 
  2.  
  3.     // 其他省略代碼 
  4.  
  5.     multiState mstate;      /* MULTI/EXEC state */ 
  6.  
  7. } client;  

multiState的結(jié)構(gòu)為:

  1. typedef struct multiState { 
  2.  
  3.     multiCmd *commands;     /* Array of MULTI commands */ 
  4.  
  5.     int count;              /* Total number of MULTI commands */ 
  6.  
  7.     int minreplicas;        /* MINREPLICAS for synchronous replication */ 
  8.  
  9.     time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */ 
  10.  
  11. } multiState;  
  • commands:multiCmd類型的數(shù)組,存放著事務(wù)中所有的要執(zhí)行的命令
  • count:當(dāng)前事務(wù)中所有已經(jīng)存放的命令的個(gè)數(shù)

另外兩個(gè)字段當(dāng)前版本中(3.2.28)沒用上。

假設(shè)當(dāng)前事務(wù)隊(duì)列中已經(jīng)存在set name slogen和lpush num 20這兩個(gè)命令的時(shí)候,client中的mstate的數(shù)據(jù)如下:

 

這個(gè)時(shí)候再往事務(wù)中添加get name這個(gè)命令的時(shí)候結(jié)構(gòu)圖如下: 

 

錯(cuò)誤命令:CLIENT_DIRTY_EXEC

那么有個(gè)問題,比如我往事務(wù)中添加的命令是個(gè)不存在的命令,或者命令使用方式,比如命令參數(shù)不對(duì),這個(gè)時(shí)候這個(gè)命令會(huì)被加入事務(wù)嗎?

前面說了,Redis接收到的所有的命令都是執(zhí)行到processCommand()這個(gè)方法,在實(shí)際執(zhí)行對(duì)應(yīng)的命令前,processCommand()方法都會(huì)對(duì)將要執(zhí)行的命令進(jìn)行一系列的檢查,代碼如下: 

 

從上面代碼可以看到,processCommand()在對(duì)要執(zhí)行的命令進(jìn)行的一系列檢查的時(shí)候如果有任何一項(xiàng)檢測失敗都會(huì)調(diào)用flagTransaction()函數(shù)然后返回對(duì)應(yīng)的信息給客戶端,flagTransaction()實(shí)現(xiàn)如下:

  1. void flagTransaction(client *c) { 
  2.  
  3.     if (c->flags & CLIENT_MULTI) 
  4.  
  5.         // 如果flags包含CLIENT_MULTI標(biāo)志位,表示已經(jīng)處于事務(wù)上下文中 
  6.  
  7.         // 則給對(duì)應(yīng)的client的flags開啟CLIENT_DIRTY_EXEC標(biāo)志位 
  8.  
  9.         c->flags |= CLIENT_DIRTY_EXEC; 
  10.  
  11.  

flagTransaction()方法會(huì)檢測對(duì)應(yīng)的client是否處于事務(wù)的上下文中,如果是的話就給對(duì)應(yīng)的client的flags字段開啟CLIENT_DIRTY_EXEC標(biāo)志位。

也就是說,如果命令在加入事務(wù)的時(shí)候由于各種原因,比如命令不存在,或者對(duì)應(yīng)的命令參數(shù)不正確,則對(duì)應(yīng)的命令不會(huì)被添加到mstate.commands數(shù)組中,且同時(shí)給對(duì)應(yīng)的client的flags字段開啟CLIENT_DIRTY_EXEC標(biāo)志位。

watch命令

當(dāng)client處于事務(wù)的上下文中時(shí),watch命令屬于可以被立即執(zhí)行的幾個(gè)命令之一,watch命令對(duì)應(yīng)的代碼為watchCommand()函數(shù),實(shí)現(xiàn)如下:

  1. void watchCommand(client *c) { 
  2.  
  3.     int j; 
  4.  
  5.   
  6.  
  7.     if (c->flags & CLIENT_MULTI) { 
  8.  
  9.         // 如果執(zhí)行watch命令的client處于事務(wù)的上下文中則直接返回 
  10.  
  11.         addReplyError(c,"WATCH inside MULTI is not allowed"); 
  12.  
  13.         return
  14.  
  15.     } 
  16.  
  17.     for (j = 1; j < c->argc; j++) 
  18.  
  19.         // 對(duì)傳入的每個(gè)要watch的可以調(diào)用watchForKey() 
  20.  
  21.         watchForKey(c,c->argv[j]); 
  22.  
  23.     addReply(c,shared.ok); 
  24.  
  25.  

watchCommand()方法會(huì)首先判斷執(zhí)行watch的命令是否已經(jīng)處于事務(wù)的上下文中,如果是的話則直接報(bào)錯(cuò)返回,說明在Redis事務(wù)中不能調(diào)用watch命令。

接下來對(duì)于watch命令傳入的所有的key,依次調(diào)用watchForKey()方法,定義如下:

 

watchForKey()方法會(huì)做下面幾件事:

  1. 判斷對(duì)應(yīng)的key是否已經(jīng)存在于client->watched_keys列表中,如果已經(jīng)存在則直接返回。client->watched_keys保存著對(duì)應(yīng)的client對(duì)象所有的要監(jiān)視的key。
  2. 如果不存在,則去client->db->watched_keys中查找所有的已經(jīng)監(jiān)視了這個(gè)key的client對(duì)象。client->db->watched_keys以dict的結(jié)構(gòu)保存了所有的監(jiān)視這個(gè)key的client列表。
  3. 如果第二步中的列表存在,則把執(zhí)行watch命令的client添加到這個(gè)列表的尾部,如果不存在,表示還沒有任何一個(gè)client監(jiān)視這個(gè)key,則新建一個(gè)列表,添加到client->db->watched_keys中,然后把執(zhí)行watch命令的client添加到新生成的列表的尾部。
  4. 把傳入的key封裝成一個(gè)watchedKey結(jié)構(gòu)的變量,添加到client->watched_key列表的最后面。

假設(shè)當(dāng)前client->db->watched_keys的監(jiān)測情況如下圖所示:

 

而client->watched_keys的監(jiān)測情況如下:

 

這個(gè)時(shí)候client_A執(zhí)行watch key1 key2 key3這個(gè)命令,執(zhí)行完命令之后client->db->watched_keys結(jié)果為 

 

而client->watched_keys結(jié)果為 

 

對(duì)于key1,目前還沒有client對(duì)key1進(jìn)行監(jiān)視,所以這個(gè)時(shí)候client_A會(huì)新建一個(gè)列表,把自己添加到這個(gè)列表中然后把映射關(guān)系添加到client->db->watched_keys中去,之后會(huì)把key1添加到client->watched_keys列表的最后。

對(duì)于key2,由于已經(jīng)存在于watched_keys列表中,所以會(huì)直接返回不做任何處理。

對(duì)于key3,由于client->db->watched_keys中已經(jīng)有client_B和client_C在監(jiān)視它,所以會(huì)直接把client_A添加到監(jiān)視列表的末尾之后再把key3添加到client_A的監(jiān)視列表中去。

修改數(shù)據(jù):CLIENT_DIRTY_CAS

watch命令的作用就是用在事務(wù)中檢測被監(jiān)視的key是否被其他的client修改了,如果已經(jīng)被修改,則阻止事務(wù)的執(zhí)行,那么這個(gè)功能是怎么實(shí)現(xiàn)的呢?

這里以set命令為例進(jìn)行分析。

假設(shè)client_A執(zhí)行了watch name這個(gè)命令然后執(zhí)行multi命令開啟了事務(wù)但是還沒有執(zhí)行exec命令,這個(gè)時(shí)候client_B執(zhí)行了set name slogen這個(gè)命令,整個(gè)過程如下:

時(shí)間 client_A client_B
T1 watch name  
T2 multi  
T3 get name  
T4   set name slogen
T5 exec  

  

在T4的時(shí)候client_B執(zhí)行了set命令修改了name,Redis收到set命令之后會(huì)執(zhí)行setCommand方法,實(shí)現(xiàn)如下:

  1. void setCommand(client *c) { 
  2.  
  3.     // 其他省略代碼 
  4.  
  5.     setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL); 
  6.  
  7.  

在setCommand()最后會(huì)調(diào)用setGenericCommand()方法,改方法實(shí)現(xiàn)如下:

  1. void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) { 
  2.  
  3.     // 其他省略代碼 
  4.  
  5.     setKey(c->db,key,val); 
  6.  
  7.     // 其他省略代碼 
  8.  
  9.  

在setGenericCommand()方法中會(huì)調(diào)用setKey()這個(gè)方法,接著看下setKey()這個(gè)方法:

  1. void setKey(redisDb *db, robj *key, robj *val) { 
  2.  
  3.     if (lookupKeyWrite(db,key) == NULL) { 
  4.  
  5.         dbAdd(db,key,val); 
  6.  
  7.     } else { 
  8.  
  9.         dbOverwrite(db,key,val); 
  10.  
  11.     } 
  12.  
  13.     incrRefCount(val); 
  14.  
  15.     removeExpire(db,key); 
  16.  
  17.     // 通知修改了key 
  18.  
  19.     signalModifiedKey(db,key); 
  20.  

在setKey()方法最后會(huì)調(diào)用signaleModifiedKey()通知redis數(shù)據(jù)庫中有數(shù)據(jù)被修改,signaleModifiedKey()方法實(shí)現(xiàn)如下:

  1. void signalModifiedKey(redisDb *db, robj *key) { 
  2.  
  3.     touchWatchedKey(db,key); 
  4.  
  5.  

可以看到signalModifiedKey()也僅僅是調(diào)用touchWatchedKey()方法,代碼如下:

  1. void touchWatchedKey(redisDb *db, robj *key) { 
  2.  
  3.     list *clients; 
  4.  
  5.     listIter li; 
  6.  
  7.     listNode *ln; 
  8.  
  9.   
  10.  
  11.     if (dictSize(db->watched_keys) == 0) return
  12.  
  13.     // 1. 從redisDb->watched_keys中找到對(duì)應(yīng)的client列表 
  14.  
  15.     clients = dictFetchValue(db->watched_keys, key); 
  16.  
  17.     if (!clients) return
  18.  
  19.   
  20.  
  21.     /* Mark all the clients watching this key as CLIENT_DIRTY_CAS */ 
  22.  
  23.     /* Check if we are already watching for this key */ 
  24.  
  25.     listRewind(clients,&li); 
  26.  
  27.     while((ln = listNext(&li))) { 
  28.  
  29.         // 2.依次遍歷client列表,給每個(gè)client的flags字段 
  30.  
  31.         // 開啟CLIENT_DIRTY_CAS標(biāo)識(shí)位 
  32.  
  33.         client *c = listNodeValue(ln); 
  34.  
  35.         c->flags |= CLIENT_DIRTY_CAS; 
  36.  
  37.     } 
  38.  
  39.  

touchWatchedKey()方法會(huì)做下面兩件事:

  1. 從redisDb->watched_keys中找到監(jiān)視這個(gè)key的client列表。前面在分析watch命令的時(shí)候說過,如果有client執(zhí)行了watch keys命令,那么redis會(huì)以鍵值對(duì)的形式把(key,client)的對(duì)應(yīng)關(guān)系保存在redisDb->watched_key這個(gè)字段里面。
  2. 對(duì)于第一步中找到的每個(gè)client對(duì)象,都會(huì)給這個(gè)client的flags 字段開啟CLIENT_DIRTY_CAS標(biāo)志位。

在Redis里面所有會(huì)修改數(shù)據(jù)庫內(nèi)容的命令最后都會(huì)調(diào)用signalModifiedKey()這個(gè)方法,而在signalModifiedKey()會(huì)給所有的監(jiān)視這個(gè)key的client增加CLIENT_DIRTY_CAS標(biāo)志位。

exec命令

exec命令用來執(zhí)行事務(wù),對(duì)應(yīng)的代碼為execCommand()這個(gè)方法,實(shí)現(xiàn)如下:

  1. void execCommand(client *c) { 
  2.  
  3.     int j; 
  4.  
  5.     robj **orig_argv; 
  6.  
  7.     int orig_argc; 
  8.  
  9.     struct redisCommand *orig_cmd; 
  10.  
  11.     int must_propagate = 0; /* Need to propagate MULTI/EXEC to AOF / slaves? */ 
  12.  
  13.   
  14.  
  15.     // 1. 判斷對(duì)應(yīng)的client是否屬于事務(wù)中 
  16.  
  17.     if (!(c->flags & CLIENT_MULTI)) { 
  18.  
  19.         addReplyError(c,"EXEC without MULTI"); 
  20.  
  21.         return
  22.  
  23.     } 
  24.  
  25.     /** 
  26.  
  27.      * 2. 檢查是否需要執(zhí)行事務(wù),在下面兩種情況下不會(huì)執(zhí)行事務(wù) 
  28.  
  29.      * 1) 有被watch的key被其他的客戶端修改了,對(duì)應(yīng)于CLIENT_DIRTY_CAS標(biāo)志位被開啟 
  30.  
  31.      * ,這個(gè)時(shí)候會(huì)返回一個(gè)nil,表示沒有執(zhí)行事務(wù) 
  32.  
  33.      * 2) 有命令在加入事務(wù)隊(duì)列的時(shí)候發(fā)生錯(cuò)誤,對(duì)應(yīng)于CLIENT_DIRTY_EXEC標(biāo)志位被開啟 
  34.  
  35.      * ,這個(gè)時(shí)候會(huì)返回一個(gè)execaborterr錯(cuò)誤 
  36.  
  37.      */ 
  38.  
  39.     if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) { 
  40.  
  41.         addReply(c, c->flags & CLIENT_DIRTY_EXEC ? shared.execaborterr : 
  42.  
  43.                                                   shared.nullmultibulk); 
  44.  
  45.         // 取消所有的事務(wù) 
  46.  
  47.         discardTransaction(c); 
  48.  
  49.         goto handle_monitor; 
  50.  
  51.     } 
  52.  
  53.   
  54.  
  55.     /* Exec all the queued commands */ 
  56.  
  57.     // 3. unwatch所有被這個(gè)client watch的key 
  58.  
  59.     unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */ 
  60.  
  61.     orig_argv = c->argv; 
  62.  
  63.     orig_argc = c->argc; 
  64.  
  65.     orig_cmd = c->cmd; 
  66.  
  67.     addReplyMultiBulkLen(c,c->mstate.count); 
  68.  
  69.     // 4. 依次執(zhí)行事務(wù)隊(duì)列中所有的命令 
  70.  
  71.     for (j = 0; j < c->mstate.count; j++) { 
  72.  
  73.         c->argc = c->mstate.commands[j].argc; 
  74.  
  75.         c->argv = c->mstate.commands[j].argv; 
  76.  
  77.         c->cmd = c->mstate.commands[j].cmd; 
  78.  
  79.   
  80.  
  81.         /* Propagate a MULTI request once we encounter the first write op. 
  82.  
  83.          * This way we'll deliver the MULTI/..../EXEC block as a whole and 
  84.  
  85.          * both the AOF and the replication link will have the same consistency 
  86.  
  87.          * and atomicity guarantees. */ 
  88.  
  89.         if (!must_propagate && !(c->cmd->flags & CMD_READONLY)) { 
  90.  
  91.             execCommandPropagateMulti(c); 
  92.  
  93.             must_propagate = 1; 
  94.  
  95.         } 
  96.  
  97.   
  98.  
  99.         call(c,CMD_CALL_FULL); 
  100.  
  101.   
  102.  
  103.         /* Commands may alter argc/argv, restore mstate. */ 
  104.  
  105.         c->mstate.commands[j].argc = c->argc; 
  106.  
  107.         c->mstate.commands[j].argv = c->argv; 
  108.  
  109.         c->mstate.commands[j].cmd = c->cmd; 
  110.  
  111.     } 
  112.  
  113.     c->argv = orig_argv; 
  114.  
  115.     c->argc = orig_argc; 
  116.  
  117.     c->cmd = orig_cmd; 
  118.  
  119.     // 5. 重置這個(gè)client對(duì)應(yīng)的事務(wù)相關(guān)的所有的數(shù)據(jù) 
  120.  
  121.     discardTransaction(c); 
  122.  
  123.     /* Make sure the EXEC command will be propagated as well if MULTI 
  124.  
  125.         * was already propagated. */ 
  126.  
  127.     if (must_propagate) server.dirty++; 
  128.  
  129.   
  130.  
  131. handle_monitor: 
  132.  
  133.     if (listLength(server.monitors) && !server.loading) 
  134.  
  135.         replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc); 
  136.  
  137.  

execCommand()方法會(huì)做下面幾件事:

  1. 判斷對(duì)應(yīng)的client是否已經(jīng)處于事務(wù)中,如果不是,則直接返回錯(cuò)誤。
  2. 判斷時(shí)候需要執(zhí)行事務(wù)中的命令。在下面兩種情況下不會(huì)執(zhí)行事務(wù)而是返回錯(cuò)誤。
    1. 有被監(jiān)視的key被其他的客戶端修改了,對(duì)應(yīng)于CLIENT_DIRTY_CAS標(biāo)志位被開啟,這個(gè)時(shí)候會(huì)返回一個(gè)nil,表示沒有執(zhí)行事務(wù)。
    2. 有命令在加入事務(wù)隊(duì)列的時(shí)候發(fā)生錯(cuò)誤,對(duì)應(yīng)于CLIENT_DIRTY_EXEC標(biāo)志位被開啟,這個(gè)時(shí)候會(huì)返回一個(gè)execaborterr錯(cuò)誤。
  3. unwatch所有被這個(gè)client監(jiān)視的key。
  4. 依次執(zhí)行事務(wù)隊(duì)列中所有的命令。
  5. 重置這個(gè)client對(duì)應(yīng)的事務(wù)相關(guān)的所有的數(shù)據(jù)。

discard

使用discard命令可以取消一個(gè)事務(wù),對(duì)應(yīng)的方法為discardCommand(),實(shí)現(xiàn)如下:

  1. void discardCommand(client *c) { 
  2.  
  3.     // 1. 檢查對(duì)應(yīng)的client是否處于事務(wù)中 
  4.  
  5.     if (!(c->flags & CLIENT_MULTI)) { 
  6.  
  7.         addReplyError(c,"DISCARD without MULTI"); 
  8.  
  9.         return
  10.  
  11.     } 
  12.  
  13.     // 2. 取消事務(wù) 
  14.  
  15.     discardTransaction(c); 
  16.  
  17.     addReply(c,shared.ok); 
  18.  
  19.  

discardCommand()方法首先判斷對(duì)應(yīng)的client是否處于事務(wù)中,如果不是則直接返回錯(cuò)誤,否則的話會(huì)調(diào)用discardTransaction()方法取消事務(wù),該方法實(shí)現(xiàn)如下:

  1. void discardTransaction(client *c) { 
  2.  
  3.     // 1. 釋放所有跟MULTI/EXEC狀態(tài)相關(guān)的資源     
  4.  
  5.     freeClientMultiState(c); 
  6.  
  7.     // 2. 初始化相應(yīng)的狀態(tài) 
  8.  
  9.     initClientMultiState(c); 
  10.  
  11.     // 3. 取消對(duì)應(yīng)client的3個(gè)標(biāo)志位 
  12.  
  13.     c->flags &= ~(CLIENT_MULTI|CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC); 
  14.  
  15.     // 4.unwatch所有已經(jīng)被watch的key 
  16.  
  17.     unwatchAllKeys(c); 
  18.  
  19.  

Other

Atomic:原子性

原子性是指一個(gè)事務(wù)(transaction)中的所有操作,要么全部完成,要么全部不完成,不會(huì)結(jié)束在中間某個(gè)環(huán)節(jié)。

對(duì)于Redis的事務(wù)來說,事務(wù)隊(duì)列中的命令要么全部執(zhí)行完成,要么一個(gè)都不執(zhí)行,因此Redis的事務(wù)是具有原子性的。

注意Redis不提供事務(wù)回滾機(jī)制。

Consistency:一致性

事務(wù)的一致性是指事務(wù)的執(zhí)行結(jié)果必須是使事務(wù)從一個(gè)一致性狀態(tài)變到另一個(gè)一致性狀態(tài),無論事務(wù)是否執(zhí)行成功。

  1. 命令加入事務(wù)隊(duì)列失敗(參數(shù)個(gè)數(shù)不對(duì)?命令不存在?),整個(gè)事務(wù)不會(huì)執(zhí)行。所以事務(wù)的一致性不會(huì)被影響。
  2. 使用了watch命令監(jiān)視的key只事務(wù)期間被其他客戶端修改,整個(gè)事務(wù)不會(huì)執(zhí)行。也不會(huì)影響事務(wù)的一致性。
  3. 命令執(zhí)行錯(cuò)誤。如果事務(wù)執(zhí)行過程中有一個(gè)活多個(gè)命令錯(cuò)誤執(zhí)行失敗,服務(wù)器也不會(huì)中斷事務(wù)的執(zhí)行,會(huì)繼續(xù)執(zhí)行事務(wù)中剩下的命令,并且已經(jīng)執(zhí)行的命令不會(huì)受任何影響。出錯(cuò)的命令將不會(huì)執(zhí)行,也就不會(huì)對(duì)數(shù)據(jù)庫做出修改,因此這種情況下事物的一致性也不會(huì)受到影響。
  4. 服務(wù)器宕機(jī)。服務(wù)器宕機(jī)的情況下的一致性可以根據(jù)服務(wù)器使用的持久化方式來分析。
    1. 無持久化模式下,事務(wù)是一致的。這種情況下重啟之后的數(shù)據(jù)庫沒有任何數(shù)據(jù),因此總是一致的。
    2. RDB模式下,事務(wù)也是一致的。服務(wù)器宕機(jī)重啟之后可以根據(jù)RDB文件來恢復(fù)數(shù)據(jù),從而將數(shù)據(jù)庫還原到一個(gè)一致的狀態(tài)。如果找不到可以使用的RDB文件,那么重啟之后數(shù)據(jù)庫是空白的,那也是一致的。
    3. AOF模式下,事務(wù)也是一致的。服務(wù)器宕機(jī)重啟之后可以根據(jù)AOF文件來恢復(fù)數(shù)據(jù),從而將數(shù)據(jù)庫還原到一個(gè)一直的狀態(tài)。如果找不到可以使用的AOF文件,那么重啟之后數(shù)據(jù)庫是空白的,那么也是一致的。

Isolation:隔離性

Redis 是單進(jìn)程程序,并且它保證在執(zhí)行事務(wù)時(shí),不會(huì)對(duì)事務(wù)進(jìn)行中斷,事務(wù)可以運(yùn)行直到執(zhí)行完所有事務(wù)隊(duì)列中的命令為止。因此,Redis 的事務(wù)是總是帶有隔離性的。

Durability:持久性

Redis事務(wù)并沒有提供任何的持久性功能,所以事務(wù)的持久性是由Redis本身所使用的持久化方式來決定的。

  • 在單純的內(nèi)存模式下,事務(wù)肯定是不持久的。
  • 在RDB模式下,服務(wù)器可能在事務(wù)執(zhí)行之后RDB文件更新之前的這段時(shí)間失敗,所以RDB模式下的Redis事務(wù)也是不持久的。
  • 在AOF的always模式下,事務(wù)的每條命令在執(zhí)行成功之后,都會(huì)立即調(diào)用fsync或fdatasync將事務(wù)數(shù)據(jù)寫入到AOF文件。但是,這種保存是由后臺(tái)線程進(jìn)行的,主線程不會(huì)阻塞直到保存成功,所以從命令執(zhí)行成功到數(shù)據(jù)保存到硬盤之間,還是有一段非常小的間隔,所以這種模式下的事務(wù)也是不持久的。
  • 其他AOF模式也和always模式類似,所以它們都是不持久的。

結(jié)論:Redis的事務(wù)滿足原子性、一致性和隔離性,但是不滿足持久性。

Reference

  • Redis源碼(3.2.28)
  • 《Redis設(shè)計(jì)與實(shí)現(xiàn)》 
責(zé)任編輯:龐桂玉 來源: 數(shù)據(jù)庫開發(fā)
相關(guān)推薦

2017-06-12 10:31:17

Redis源碼學(xué)習(xí)事件驅(qū)動(dòng)

2021-09-28 09:36:13

redisHash結(jié)構(gòu)

2022-02-25 08:55:19

BitMapRedis面試題

2022-02-10 09:04:18

RediSDS數(shù)據(jù)結(jié)構(gòu)

2024-01-18 11:54:44

Redis事務(wù)命令

2017-04-10 13:30:47

Redis數(shù)據(jù)庫命令

2020-09-23 10:00:26

Redis數(shù)據(jù)庫命令

2013-11-05 09:19:38

代碼庫源碼

2022-03-08 16:10:38

Redis事務(wù)機(jī)制

2021-11-08 11:21:18

redis 淘汰算法

2020-09-30 07:41:28

Redis工具 Redis-full

2021-11-26 00:04:01

RedisLua 腳本

2021-10-18 08:41:20

Redis ACID事務(wù)

2024-06-12 13:36:24

2022-12-05 08:41:39

Redis調(diào)試環(huán)境源碼

2019-07-16 09:20:11

Redis數(shù)據(jù)庫NoSQL

2012-08-06 16:09:40

Redis數(shù)據(jù)庫

2024-12-30 07:20:00

Redis數(shù)據(jù)庫MySQL

2023-07-10 08:43:53

SpringIDEA

2011-08-23 13:56:12

MySQLConnection
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

中文字字幕在线中文| 男人操女人免费| 91精东传媒理伦片在线观看| 99精品视频在线观看免费播放 | 国产在线精品一区二区中文| 精品无码久久久久| 亚洲国产欧美日韩在线观看第一区 | 蜜芽在线免费观看| 国产成人日日夜夜| 98精品国产自产在线观看| 女~淫辱の触手3d动漫| 国产亚洲一区二区手机在线观看| 国产精品无遮挡| 98国产高清一区| 日本黄色一级视频| 66视频精品| 亚洲福利视频专区| 日本xxxx黄色| 91在线三级| 亚洲国产成人私人影院tom | 中文字幕国产在线观看| 成人网18免费网站| 欧美电视剧在线看免费| 久激情内射婷内射蜜桃| 色欧美激情视频在线| 丁香激情综合国产| 国产成人97精品免费看片| 精品国产精品国产精品| 任你弄精品视频免费观看| 欧美日韩久久久久久| 少妇av一区二区三区无码| 最新97超碰在线| 99国产精品视频免费观看| 国产综合久久久久| 欧美在线观看不卡| 首页国产精品| 亚洲人成五月天| 折磨小男生性器羞耻的故事| 欧美爱爱视频| 欧美视频在线视频| 久草免费福利在线| 免费av不卡| 欧美国产97人人爽人人喊| 久久av二区| a级片在线免费看| 天使萌一区二区三区免费观看| 欧美黑人性视频| 国产麻豆视频在线观看| av亚洲在线观看| 日韩精品免费综合视频在线播放| 三级网站免费看| 亚洲精品tv| 欧美无砖砖区免费| 男人的天堂日韩| 波多野结衣亚洲一二三| 五月婷婷色综合| 超级碰在线观看| 黄色成年人视频在线观看| 国产亚洲一区二区三区在线观看| 国产综合动作在线观看| 风流少妇一区二区三区91| 国内国产精品久久| 91精品视频在线看| 中文字幕乱码在线观看| 久久只有精品| 国产精品极品尤物在线观看| 精品不卡一区二区| 99热在线精品观看| 欧美亚洲国产另类| 黑人精品无码一区二区三区AV| 99精品99| 欧美性受xxx| 日韩三级一区二区| 日韩精品亚洲一区| 国产精品久久久久久久久久新婚| 国产又粗又猛又爽又| 久久男女视频| 国产精品日本精品| 中文字幕+乱码+中文乱码www| 欧美一级专区| 国产成人精品久久| 中文字幕免费观看视频| 另类小说视频一区二区| 成人在线中文字幕| 中文字幕制服诱惑| 国产一区二区在线看| caoporn国产精品免费公开| 亚洲黄色a级片| 99这里只有久久精品视频| 欧美精品一区二区三区久久| 蜜桃视频在线免费| 中文乱码免费一区二区| 精品国产三级a∨在线| 亚洲小说区图片| 亚洲成人av免费| 日韩av一二三四| 国产一区二区三区影视| 欧美在线看片a免费观看| 国产九九在线观看| 欧美日本三级| 日韩久久精品电影| 青青草自拍偷拍| 在线精品在线| 国产精品精品国产| 国产ts变态重口人妖hd| 91日韩在线专区| 日韩欧美三级一区二区| 91三级在线| 色999日韩国产欧美一区二区| 欧在线一二三四区| 午夜视频在线观看精品中文| 日韩精品在线观| 美女三级黄色片| 国产精品一区亚洲| 成人国产精品一区二区| 人妻精品一区二区三区| 国产日韩欧美麻豆| 日韩人妻无码精品久久久不卡| 日本免费一区二区三区四区| 欧美一区二区成人6969| 国产ts在线观看| 精品国产一区二区三区| 欧美激情小视频| 国产69精品久久久久久久久久| 久久国产日韩欧美精品| 久久综合一区二区三区| sm国产在线调教视频| 91电影在线观看| 97中文字幕在线观看| 手机在线电影一区| 日韩免费观看av| 免费观看国产视频| 亚洲私人影院在线观看| 免费观看成人网| 精品午夜电影| 色中色综合影院手机版在线观看| 久久精品国产亚洲av麻豆蜜芽| av在线综合网| 久久综合亚洲精品| 国产成人午夜性a一级毛片| 精品视频久久久久久久| 精品在线观看一区| 蜜臀av亚洲一区中文字幕| 精品一区国产| 午夜av在线免费观看| 欧美日韩在线播放一区| 天天躁日日躁aaaxxⅹ| 欧美日韩中文| 国产在线98福利播放视频| 国产人成在线观看| 色爱区综合激月婷婷| 星空大象在线观看免费播放| 欧美福利电影在线观看| 91麻豆国产语对白在线观看| 亚洲三区在线播放| 午夜亚洲国产au精品一区二区| av地址在线观看| 四虎成人精品永久免费av九九| 国产精品444| 都市激情在线视频| 在线欧美日韩精品| 精品人妻无码一区二区三区换脸| 美女国产精品| 欧美一区二区视频在线| 你懂得影院夜精品a| 一区二区欧美久久| 久久影视中文字幕| 国产喷白浆一区二区三区| 99视频在线免费| 精品高清久久| 国产日产欧美a一级在线| www.在线视频.com| 欧美精品第1页| 亚洲天堂黄色片| 国产成人日日夜夜| 300部国产真实乱| 精品国产乱子伦一区二区| 欧美成人h版在线观看| 精品久久久中文字幕人妻| 1024成人网| 色欲欲www成人网站| 国产精品hd| 久久精品国产99精品国产亚洲性色| 伊人成综合网站| 一本一本久久a久久精品综合小说 一本一本久久a久久精品牛牛影视 | 色8久久精品久久久久久蜜| 中字幕一区二区三区乱码 | 哪个网站能看毛片| 久久成人高清| 国产精品丝袜一区二区三区| 激情成人四房播| 精品剧情在线观看| 男人日女人网站| 国产精品系列在线| 少妇极品熟妇人妻无码| 亚洲欧美日韩精品一区二区| 欧美午夜视频在线| 亚洲欧洲二区| 欧美三日本三级三级在线播放| 亚洲色图欧美色| 成人涩涩免费视频| 日本77777| 久久久噜噜噜久久狠狠50岁| 免费成人进口网站| 成人影院在线| 久久综合一区二区三区| 亚洲日本视频在线| 成人久久18免费网站图片| 欧产日产国产精品视频| 久久综合五月天| 成人av一区| 国产视频亚洲视频| 亚洲第一成年人网站| 5566中文字幕一区二区电影| 欧美一级淫片免费视频黄| 亚洲综合精品久久| 三级黄色录像视频| 国产精品欧美精品| 精品国产av无码| 91视频观看视频| av黄色一级片| 成人美女在线视频| 日本少妇一级片| 国产精品羞羞答答xxdd| 女同激情久久av久久| 日韩av一级片| 国产成人精品视频ⅴa片软件竹菊| 在线观看视频日韩| 日韩成人三级视频| 欧美成人国产| 国产卡一卡二在线| 午夜激情久久| 亚洲欧美国产精品桃花| 成人羞羞视频播放网站| 日韩精品久久一区二区三区| 亚洲综合图色| 麻豆成人在线播放| 羞羞色国产精品网站| 精品免费视频123区| 日韩aaa久久蜜桃av| 精品麻豆av| 亚瑟一区二区三区四区| 精品一区二区国产| 亚洲调教一区| 日韩av一区二区三区美女毛片| 国产一区二区三区探花| 日本一区不卡| 欧美亚洲国产激情| 亚洲视频电影| 亚洲综合五月| 欧美黄色免费网址| 99国产精品私拍| 欧美三级午夜理伦三级| 日韩电影在线一区二区| 午夜免费看视频| 国产在线视频精品一区| 91网址在线观看精品| 国产传媒久久文化传媒| 国产免费a级片| 91美女在线视频| 无码人妻丰满熟妇啪啪欧美| 中文字幕欧美日本乱码一线二线| 强制高潮抽搐sm调教高h| 夜夜揉揉日日人人青青一国产精品| 久久综合加勒比| 欧美午夜电影在线| 一级特黄aaa大片| 欧美一区二区视频在线观看2022| 亚洲国产精品久久久久久久| 日韩精品在线观看视频| 高清性色生活片在线观看| 久久精品99无色码中文字幕| 手机av在线播放| 欧美亚洲国产视频| 精品福利在线| 国产精品成人一区二区三区| 综合综合综合综合综合网| 一本色道久久99精品综合| 午夜国产一区| 国产精品免费成人| 国内精品视频666| 37p粉嫩大胆色噜噜噜| 亚洲欧美自拍偷拍| 成人精品在线看| 欧美精品乱码久久久久久按摩| 嫩草影院一区二区| 最好看的2019年中文视频| 丰满大乳少妇在线观看网站| 国产91精品最新在线播放| 成人av在线播放| 久久久久久艹| 午夜精品免费| 亚洲免费看av| 99在线精品免费| 欧美精品久久久久久久久46p| 黄网站色欧美视频| 国产亲伦免费视频播放| 亚洲片国产一区一级在线观看| 成人在线免费看片| 国产精品高潮呻吟久久av黑人| 国产亚洲观看| 翔田千里亚洲一二三区| 夜夜精品视频| 永久av免费在线观看| 国产亚洲欧美在线| 日韩女同强女同hd| 9191国产精品| 国产乱理伦片a级在线观看| 午夜精品久久17c| 亚洲图色一区二区三区| 亚洲国产欧美不卡在线观看| 国产欧美日韩一级| 极品白嫩少妇无套内谢| 中文字幕一区二区三区精华液 | 夜夜爽av福利精品导航| 久草福利在线观看| 亚洲欧美综合另类在线卡通| 丰满熟女人妻一区二区三 | av在线播放观看| 国产精品日韩av| 国产麻豆精品久久| 国产午夜伦鲁鲁| av在线一区二区三区| 精品午夜福利视频| 日韩午夜激情电影| 国产精品一卡二卡三卡| 国产精品久久久久久久7电影| 午夜精品福利影院| 成人中文字幕在线播放| 成人免费视频一区| 国产午夜福利精品| 精品国产制服丝袜高跟| 日本高清成人vr专区| 亚洲va欧美va国产综合剧情| 亚洲v在线看| 日本黄色www| 玉米视频成人免费看| 国产精品久久久久久在线| 日韩一区二区精品视频| 美女视频一区| 一区二区三区四区免费视频| 久久成人免费网| 日韩av毛片在线观看| 在线不卡中文字幕播放| 黄色网址免费在线观看| 亚洲综合色av| 国产精品国码视频| 成人免费毛片日本片视频| 欧美性色xo影院| 超碰免费在线| 91久久久久久久久久| 欧美日韩三级| 国产一级黄色录像| 日韩欧美在线看| xxxxx日韩| 91在线网站视频| 亚洲激情不卡| 波多野结衣av在线观看| 欧美日韩日日夜夜| 亚洲综合影视| 精品一区二区不卡| 美洲天堂一区二卡三卡四卡视频 | 国产福利小视频在线观看| 国产精品入口尤物| 欧美在线资源| 欧美精品黑人猛交高潮| 欧美无砖专区一中文字| a级在线观看| 裸体丰满少妇做受久久99精品| 青青草一区二区三区| 成年人av电影| 亚洲精品久久久久中文字幕二区| 国产v综合v| 国产精品无码电影在线观看| 久久综合久久综合久久| 亚洲中文字幕在线观看| 久久久久久久久久久91| 精品日韩欧美一区| 中文在线字幕观看| 在线免费不卡视频| 综合图区亚洲| 日韩三级在线播放| 国产成人精品三级| 免费精品一区二区| 欧美精品video| 日韩av久操| 亚洲少妇中文字幕| 欧美日韩一二三区| 不卡专区在线| 国产又大又长又粗又黄| 91麻豆精品秘密| 国产精品一区二区av白丝下载| 91a在线视频| 欧美1区视频| 欧美午夜激情影院| 亚洲国产精品999| 国产95亚洲| www.日日操| 五月天视频一区| 国产精品久久久久久福利|