日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

数据库

Redis源码剖析(六)事务模块

發(fā)布時(shí)間:2024/4/19 数据库 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis源码剖析(六)事务模块 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Redis允許客戶端開啟事務(wù)模式,在事務(wù)模式中,客戶端輸入的命令不會(huì)立即執(zhí)行而是被保存在事務(wù)隊(duì)列中,只有當(dāng)客戶端輸入事務(wù)運(yùn)行命令時(shí),Redis才會(huì)將事務(wù)隊(duì)列中的所有命令按照FIFO的順序一個(gè)個(gè)執(zhí)行

一個(gè)事務(wù)從開始到結(jié)束通常會(huì)經(jīng)歷三個(gè)階段

  • 事務(wù)開始
  • 命令入隊(duì)
  • 事務(wù)執(zhí)行

事務(wù)命令

客戶端可以使用MULTI命令開啟事務(wù),隨后服務(wù)器會(huì)根據(jù)這個(gè)客戶端輸入的不同命令執(zhí)行不同的操作

  • 如果客戶端發(fā)送的命令為EXEC,DISCARD,WATCH,MULTI四個(gè)命令中的一個(gè),那么服務(wù)器立刻執(zhí)行這個(gè)命令,同時(shí)是否關(guān)閉事務(wù)取決于每個(gè)命令的功能
  • 如果客戶端發(fā)送的命令為上述四個(gè)命令之外的其他命令,那么服務(wù)器不會(huì)立刻執(zhí)行輸入的命令,而是將命令存放在事務(wù)隊(duì)列中
127.0.0.1:6379> MULTI //開啟事務(wù) OK 127.0.0.1:6379> set db redis QUEUED //表示命令被添加到事務(wù)隊(duì)列中 127.0.0.1:6379> get db QUEUED 127.0.0.1:6379> set name roc QUEUED 127.0.0.1:6379> get name QUEUED 127.0.0.1:6379> EXEC //執(zhí)行事務(wù)隊(duì)列中的命令 1) OK //set db redis的執(zhí)行結(jié)果 2) "redis" //get db的執(zhí)行結(jié)果 3) OK //set name roc的執(zhí)行結(jié)果 4) "roc" //get name的執(zhí)行結(jié)果 127.0.0.1:6379>

存儲(chǔ)結(jié)構(gòu)

要想當(dāng)輸入EXEC時(shí)一次性執(zhí)行之前輸入的所有命令,就需要將之前的命令保存起來(lái),Redis采用數(shù)組保存所有命令信息,在client的定義中可以找到

//server.h typedef struct client {...multiState mstate; /* 事務(wù)屬性,保存事務(wù)隊(duì)列以及事務(wù)狀態(tài) */... } client;

multiState是事務(wù)屬性結(jié)構(gòu),它保存著事務(wù)隊(duì)列以及事務(wù)隊(duì)列中命令個(gè)數(shù),定義如下

//server.h /* 事務(wù)屬性 */ typedef struct multiState {multiCmd *commands; /* 事務(wù)隊(duì)列,保存多條命令 */ int count; /* 事務(wù)隊(duì)列中命令個(gè)數(shù) */ ... } multiState;

commands是一個(gè)事務(wù)隊(duì)列,實(shí)際上是一個(gè)multiCmd類型的數(shù)組,multiCmd結(jié)構(gòu)保存一條命令的信息

//server.h /* 保存一條命令的信息 */ typedef struct multiCmd {robj **argv; /* 命令關(guān)鍵字和參數(shù) */int argc; /* 命令參數(shù)個(gè)數(shù) */struct redisCommand *cmd; /* 命令結(jié)構(gòu),主要包含命令處理函數(shù) */ } multiCmd;

可以看到,multiCmd中保存的實(shí)際上就是執(zhí)行一條命令需要的三個(gè)信息,分別是

  • 命令關(guān)鍵字和參數(shù)
  • 參數(shù)個(gè)數(shù)
  • 命令處理函數(shù)

在客戶端client的定義中也可以找到這三個(gè)變量的定義

//server.h typedef struct client {...int argc; robj **argv; struct redisCommand *cmd; ... } client;

所以大體可以猜測(cè),當(dāng)要執(zhí)行事務(wù)隊(duì)列中的命令時(shí),只需要遍歷事務(wù)隊(duì)列,依次將這三個(gè)變量賦值給client中的對(duì)應(yīng)變量,然后調(diào)用對(duì)應(yīng)命令處理函數(shù)即可。后面會(huì)看到,事實(shí)也正是如此

事務(wù)實(shí)現(xiàn)

開啟事務(wù)

在Redis內(nèi)部,事務(wù)的開啟十分簡(jiǎn)單,僅僅是將客戶端的事務(wù)標(biāo)志位打開,表示進(jìn)行事務(wù)狀態(tài),隨后的大多數(shù)操作都會(huì)先判斷該標(biāo)志位以確定是將命令添加到事務(wù)隊(duì)列中,還是執(zhí)行命令

//multi.c /* 開啟事務(wù) */ void multiCommand(client *c) {/* 若已經(jīng)開啟,則報(bào)錯(cuò) */if (c->flags & CLIENT_MULTI) {addReplyError(c,"MULTI calls can not be nested");return;}/* 設(shè)置CLIENT_MULTI標(biāo)志代表客戶端已經(jīng)開啟事務(wù) */c->flags |= CLIENT_MULTI;addReply(c,shared.ok); }

添加命令到事務(wù)隊(duì)列

在第一篇服務(wù)器與客戶端交互流程中得知,當(dāng)客戶端輸入命令后,會(huì)存在一個(gè)解析命令的操作,將命令參數(shù),參數(shù)個(gè)數(shù)以及命令處理函數(shù)找到,然后執(zhí)行call函數(shù),在這個(gè)函數(shù)中調(diào)用命令處理函數(shù)執(zhí)行命令。但是一旦開啟事務(wù)功能,Redis就不能再執(zhí)行命令了,如上所述,應(yīng)該將命令添加到事務(wù)隊(duì)列中。

在processCommand函數(shù)中,可以看到對(duì)于這兩種情況的判斷

/* 處理客戶端輸入的命令 */ int processCommand(client *c) {.../* 從命令字典中查找該命令名字,返回redisCommand結(jié)構(gòu),其中包含命令處理函數(shù) */c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);.../* 如果客戶端開啟事務(wù),則不執(zhí)行命令而是將命令添加到事務(wù)隊(duì)列中 */if (c->flags & CLIENT_MULTI &&c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&c->cmd->proc != multiCommand && c->cmd->proc != watchCommand){queueMultiCommand(c);/* 回復(fù)客戶端當(dāng)前命令已經(jīng)添加到事務(wù)隊(duì)列中 */addReply(c,shared.queued);} else {/* 沒(méi)有開啟事務(wù),執(zhí)行執(zhí)行命令 */call(c,CMD_CALL_FULL);...}return C_OK; }

將命令添加到事務(wù)隊(duì)列中由queueMultiCommand函數(shù)完成,函數(shù)首先創(chuàng)建一個(gè)multiCmd對(duì)象,這個(gè)結(jié)構(gòu)在上面提到過(guò),保存著執(zhí)行一條命令需要的三個(gè)元素,分別是命令參數(shù),參數(shù)個(gè)數(shù)以及命令處理函數(shù)。而multiState結(jié)構(gòu)是保存事務(wù)隊(duì)列的結(jié)構(gòu)(實(shí)際是數(shù)組),在這里需要將新的命令添加到這個(gè)數(shù)組中

/* 將當(dāng)前命令添加到客戶端的事務(wù)隊(duì)列中 */ void queueMultiCommand(client *c) {multiCmd *mc;int j;/* mstate是multiState類型,保存事務(wù)中所有命令的信息* 每增加一條命令到事務(wù)隊(duì)列中,都需要為原事務(wù)隊(duì)列重新申請(qǐng)n+1大小的空間* 多的那一個(gè)用來(lái)存儲(chǔ)當(dāng)前命令*/c->mstate.commands = zrealloc(c->mstate.commands,sizeof(multiCmd)*(c->mstate.count+1));mc = c->mstate.commands+c->mstate.count;/* 保存執(zhí)行一條命令所需的三個(gè)元素 */mc->cmd = c->cmd;mc->argc = c->argc;mc->argv = zmalloc(sizeof(robj*)*c->argc);/* 將參數(shù)復(fù)制到multiCmd結(jié)構(gòu)中 */memcpy(mc->argv,c->argv,sizeof(robj*)*c->argc);/* 因?yàn)閰?shù)是robj*類型,所以引用計(jì)數(shù)加一 */for (j = 0; j < c->argc; j++)incrRefCount(mc->argv[j]);/* 事務(wù)隊(duì)列中命令個(gè)數(shù)加一 */c->mstate.count++; }

執(zhí)行命令

當(dāng)客戶端輸入EXEC命令后,Redis會(huì)從客戶端的事務(wù)隊(duì)列中取出命令,按照保存的先后順序一個(gè)個(gè)執(zhí)行,由于事務(wù)隊(duì)列中有命令的所有信息,所以可以執(zhí)行調(diào)用處理函數(shù)。這部分操作由execCommand函數(shù)執(zhí)行,因?yàn)閳?zhí)行一條命令是從客戶端對(duì)象client中取出命令參數(shù),參數(shù)個(gè)數(shù)以及命令處理函數(shù),所以這里就直接將隊(duì)列中的命令信息復(fù)制給客戶端對(duì)象的對(duì)應(yīng)變量,然后和執(zhí)行正常命令一樣調(diào)用call函數(shù)

/* 啟動(dòng)事務(wù)命令 */ void execCommand(client *c) {int j;robj **orig_argv;int orig_argc;struct redisCommand *orig_cmd;int must_propagate = 0; /* Need to propagate MULTI/EXEC to AOF / slaves? *//* CLIENT_MULTI標(biāo)識(shí)代表當(dāng)前客戶端是否開啟事務(wù),如果沒(méi)有開啟,執(zhí)行EXEC指令是沒(méi)有意義的 */if (!(c->flags & CLIENT_MULTI)) {addReplyError(c,"EXEC without MULTI");return;}/* CLIENT_DIRTY_CAS標(biāo)識(shí)代表客戶端監(jiān)視的鍵是否被修改過(guò)* 如果被修改過(guò),那么執(zhí)行事務(wù)就不再安全,直接返回 */if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) {addReply(c, c->flags & CLIENT_DIRTY_EXEC ? shared.execaborterr :shared.nullmultibulk);discardTransaction(c);goto handle_monitor;}/* Exec all the queued commands *//* 開始執(zhí)行事務(wù),監(jiān)視任務(wù)就可以結(jié)束了,將該客戶端的監(jiān)視字典清空 */unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles *//* 臨時(shí)保存客戶端當(dāng)前參數(shù)信息 */orig_argv = c->argv;orig_argc = c->argc;orig_cmd = c->cmd;addReplyMultiBulkLen(c,c->mstate.count);/* 開始執(zhí)行客戶端數(shù)據(jù)隊(duì)列(數(shù)組)中的命令 */for (j = 0; j < c->mstate.count; j++) {/* 將每個(gè)命令作為客戶端當(dāng)前的參數(shù)信息(這就是為什么需要臨時(shí)保存以前參數(shù)的原因) */c->argc = c->mstate.commands[j].argc;c->argv = c->mstate.commands[j].argv;c->cmd = c->mstate.commands[j].cmd;.../* 開始執(zhí)行命令 */call(c,CMD_CALL_FULL);/* Commands may alter argc/argv, restore mstate. *//* call執(zhí)行的命令可能會(huì)改變事務(wù)隊(duì)列中的當(dāng)前命令,這里是確保其不被改變 */c->mstate.commands[j].argc = c->argc;c->mstate.commands[j].argv = c->argv;c->mstate.commands[j].cmd = c->cmd;}/* 還原客戶端以前的參數(shù)信息 */c->argv = orig_argv;c->argc = orig_argc;c->cmd = orig_cmd;... }

函數(shù)有點(diǎn)長(zhǎng),不過(guò)還算好理解,先判斷客戶端是否開啟事務(wù)(利用CLIENT_MULTI標(biāo)記),然后清空客戶端的監(jiān)視鏈表(后面會(huì)提到),最后遍歷事務(wù)隊(duì)列,依次執(zhí)行每個(gè)命令

需要注意的是,Redis在執(zhí)行事務(wù)隊(duì)列中的命令時(shí)保證即使某條命令是錯(cuò)誤的,也不會(huì)影響到其他命令的執(zhí)行,即如果中間某條命令執(zhí)行出錯(cuò),那么后面的命令仍然會(huì)繼續(xù)執(zhí)行。另外,Redis不支持事務(wù)回滾功能,即不支持將已執(zhí)行的命令撤銷

小結(jié)

Redis的事務(wù)功能還是比較簡(jiǎn)單的,另外需要提的是,由于Redis是運(yùn)行在單線程下的,而且對(duì)于客戶端的監(jiān)聽是基于io多路復(fù)用函數(shù)的,所以對(duì)于客戶端的響應(yīng)是串行的,不會(huì)出現(xiàn)當(dāng)執(zhí)行某個(gè)客戶端事務(wù)隊(duì)列中的命令時(shí)切換到另一個(gè)客戶端的情況。這保證了事務(wù)執(zhí)行具有原子性,另外Redis設(shè)計(jì)與實(shí)現(xiàn)書中還講到Redis的事務(wù)具有一致性,隔離性和耐久性,有興趣的話可以翻閱書籍查看

總結(jié)

以上是生活随笔為你收集整理的Redis源码剖析(六)事务模块的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。