自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

Redis作為一個(gè)內(nèi)存型數(shù)據(jù)庫(kù),同樣支持傳統(tǒng)數(shù)據(jù)庫(kù)的事務(wù)特性。這篇文章會(huì)從源代碼角度來(lái)分析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è)命令來(lái)實(shí)現(xiàn)事務(wù)的功能。

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

multi、exec和discard

multi命令告訴Redis客戶端要開始一個(gè)事物,然后Redis會(huì)返回一個(gè)OK,接下來(lái)所有的命令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è)樂(lè)觀鎖,可以在exec執(zhí)行之前,監(jiān)視任意數(shù)量的數(shù)據(jù)庫(kù)key,并在exec命令執(zhí)行的時(shí)候,檢測(cè)被監(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還沒(méi)有執(zhí)行exec命令,接下來(lái)在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"  

接下來(lái)在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ì)檢測(cè)到name字段已經(jīng)被其他客戶端修改了,所以拒絕執(zhí)行事務(wù)中所有的命令,直接返回nil表示執(zhí)行失敗。這個(gè)時(shí)候獲取到的name的值還是在client2中設(shè)置的rio。

Why

multi

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

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

  1. void multiCommand(client *c) { 
  2.  
  3.     // 1. 如果檢測(cè)到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. 檢測(cè)發(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ā)送過(guò)來(lái)的命令后都會(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)沒(méi)用上。

假設(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èn)題,比如我往事務(wù)中添加的命令是個(gè)不存在的命令,或者命令使用方式,比如命令參數(shù)不對(duì),這個(gè)時(shí)候這個(gè)命令會(huì)被加入事務(wù)嗎?

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

 

從上面代碼可以看到,processCommand()在對(duì)要執(zhí)行的命令進(jìn)行的一系列檢查的時(shí)候如果有任何一項(xiàng)檢測(cè)失敗都會(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ì)檢測(cè)對(duì)應(yīng)的client是否處于事務(wù)的上下文中,如果是的話就給對(duì)應(yīng)的client的flags字段開啟CLIENT_DIRTY_EXEC標(biāo)志位。

也就是說(shuō),如果命令在加入事務(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ò)返回,說(shuō)明在Redis事務(wù)中不能調(diào)用watch命令。

接下來(lái)對(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è)列表的尾部,如果不存在,表示還沒(méi)有任何一個(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)測(cè)情況如下圖所示:

 

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

 

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

 

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

 

對(duì)于key1,目前還沒(méi)有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ù)中檢測(cè)被監(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ù)但是還沒(méi)有執(zhí)行exec命令,這個(gè)時(shí)候client_B執(zhí)行了set name slogen這個(gè)命令,整個(gè)過(guò)程如下:

時(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ù)庫(kù)中有數(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í)候說(shuō)過(guò),如果有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ù)庫(kù)內(nèi)容的命令最后都會(huì)調(diào)用signalModifiedKey()這個(gè)方法,而在signalModifiedKey()會(huì)給所有的監(jiān)視這個(gè)key的client增加CLIENT_DIRTY_CAS標(biāo)志位。

exec命令

exec命令用來(lái)執(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,表示沒(méi)有執(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,表示沒(méi)有執(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ù)來(lái)說(shuō),事務(wù)隊(duì)列中的命令要么全部執(zhí)行完成,要么一個(gè)都不執(zhí)行,因此Redis的事務(wù)是具有原子性的。

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

Consistency:一致性

事務(wù)的一致性是指事務(wù)的執(zhí)行結(jié)果必須是使事務(wù)從一個(gè)一致性狀態(tài)變到另一個(gè)一致性狀態(tài),無(wú)論事務(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í)行過(guò)程中有一個(gè)活多個(gè)命令錯(cuò)誤執(zhí)行失敗,服務(wù)器也不會(huì)中斷事務(wù)的執(zhí)行,會(huì)繼續(xù)執(zhí)行事務(wù)中剩下的命令,并且已經(jīng)執(zhí)行的命令不會(huì)受任何影響。出錯(cuò)的命令將不會(huì)執(zhí)行,也就不會(huì)對(duì)數(shù)據(jù)庫(kù)做出修改,因此這種情況下事物的一致性也不會(huì)受到影響。
  4. 服務(wù)器宕機(jī)。服務(wù)器宕機(jī)的情況下的一致性可以根據(jù)服務(wù)器使用的持久化方式來(lái)分析。
    1. 無(wú)持久化模式下,事務(wù)是一致的。這種情況下重啟之后的數(shù)據(jù)庫(kù)沒(méi)有任何數(shù)據(jù),因此總是一致的。
    2. RDB模式下,事務(wù)也是一致的。服務(wù)器宕機(jī)重啟之后可以根據(jù)RDB文件來(lái)恢復(fù)數(shù)據(jù),從而將數(shù)據(jù)庫(kù)還原到一個(gè)一致的狀態(tài)。如果找不到可以使用的RDB文件,那么重啟之后數(shù)據(jù)庫(kù)是空白的,那也是一致的。
    3. AOF模式下,事務(wù)也是一致的。服務(wù)器宕機(jī)重啟之后可以根據(jù)AOF文件來(lái)恢復(fù)數(shù)據(jù),從而將數(shù)據(jù)庫(kù)還原到一個(gè)一直的狀態(tài)。如果找不到可以使用的AOF文件,那么重啟之后數(shù)據(jù)庫(kù)是空白的,那么也是一致的。

Isolation:隔離性

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

Durability:持久性

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

  • 在單純的內(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é)任編輯:龐桂玉 來(lái)源: 數(shù)據(jù)庫(kù)開發(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ù)庫(kù)命令

2020-09-23 10:00:26

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

2013-11-05 09:19:38

代碼庫(kù)源碼

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-10-18 08:41:20

Redis ACID事務(wù)

2021-11-26 00:04:01

RedisLua 腳本

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ù)庫(kù)NoSQL

2012-08-06 16:09:40

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

2024-12-30 07:20:00

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

2020-05-27 20:45:31

Redis底層數(shù)據(jù)

2023-07-10 08:43:53

SpringIDEA
點(diǎn)贊
收藏

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