欧美成人午夜免费全部完,亚洲午夜福利精品久久,а√最新版在线天堂,另类亚洲综合区图片小说区,亚洲欧美日韩精品色xxx

扣丁學(xué)堂Java開發(fā)培訓(xùn)之Redis命令執(zhí)行過程詳解

2018-09-14 10:21:27 1240瀏覽

今天扣丁學(xué)堂Java培訓(xùn)老師給大家介紹一下關(guān)于Redis學(xué)習(xí)教程之命令執(zhí)行過程的詳細(xì)介紹,首先Redis是單線程應(yīng)用,它是如何與多個客戶端簡歷網(wǎng)絡(luò)鏈接并處理命令的?由于Redis是基于I/O多路復(fù)用技術(shù),為了能夠處理多個客戶端的請求,Redis在本地為每一個鏈接到Redis服務(wù)器的客戶端創(chuàng)建了一個redisClient的數(shù)據(jù)結(jié)構(gòu),這個數(shù)據(jù)結(jié)構(gòu)包含了每個客戶端各自的狀態(tài)和執(zhí)行的命令。Redis服務(wù)器使用一個鏈表來維護多個redisClient數(shù)據(jù)結(jié)構(gòu)。



在服務(wù)器端用一個鏈表來管理所有的redisClient。

struct redisServer {
 //...
 list *clients;  /* List of active clients */
 //...
}

所以我就看看redisClient包含的數(shù)據(jù)結(jié)構(gòu)和重要參數(shù):

typedef struct redisClient {
 
 // 客戶端狀態(tài)標(biāo)志
 int flags;  /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */
  
 // 套接字描述符
 int fd;
 
 // 當(dāng)前正在使用的數(shù)據(jù)庫
 redisDb *db;
 
 // 當(dāng)前正在使用的數(shù)據(jù)庫的 id (號碼)
 int dictid;
 
 // 客戶端的名字
 robj *name;  /* As set by CLIENT SETNAME */
 
 // 查詢緩沖區(qū)
 sds querybuf;
 
 // 查詢緩沖區(qū)長度峰值
 size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size */
 
 // 參數(shù)數(shù)量
 int argc;
 
 // 參數(shù)對象數(shù)組
 robj **argv;
 
 // 記錄被客戶端執(zhí)行的命令
 struct redisCommand *cmd, *lastcmd;
 
 // 請求的類型:內(nèi)聯(lián)命令還是多條命令
 int reqtype;
 
 // 剩余未讀取的命令內(nèi)容數(shù)量
 int multibulklen; /* number of multi bulk arguments left to read */
 
 // 命令內(nèi)容的長度
 long bulklen;  /* length of bulk argument in multi bulk request */
 
 // 回復(fù)鏈表
 list *reply;
 
 // 回復(fù)鏈表中對象的總大小
 unsigned long reply_bytes; /* Tot bytes of objects in reply list */
 
 // 已發(fā)送字節(jié),處理 short write 用
 int sentlen;  /* Amount of bytes already sent in the current
    buffer or object being sent. */
 
 // 回復(fù)偏移量
 int bufpos;
 // 回復(fù)緩沖區(qū)
 char buf[REDIS_REPLY_CHUNK_BYTES];
 // ...
}

這里需要特別的注意,redisClient并非指遠程的客戶端,而是一個Redis服務(wù)本地的數(shù)據(jù)結(jié)構(gòu),我們可以理解這個redisClient是遠程客戶端的一個映射或者代理。

flags

flags表示了目前客戶端的角色,以及目前所處的狀態(tài)。他比較特殊可以單獨表示一個狀態(tài)或者多個狀態(tài)。

querybuf

querybuf是一個sds動態(tài)字符串類型,所謂buf說明是它只是一個緩沖區(qū),用于存儲沒有被解析的命令。

argc&argv

上文的querybuf是一個沒有處理過的命令,當(dāng)Redis將querybuf命令解析以后,會將得出的參數(shù)個數(shù)和以及參數(shù)分別保存在argc和argv中。argv是一個redisObject的數(shù)組。

cmd

Redis使用一個字典保存了所有的redisCommand。key是redisCommand的名字,值就是一個redisCommand結(jié)構(gòu),這個結(jié)構(gòu)保存了命令的實現(xiàn)函數(shù),命令的標(biāo)志,命令應(yīng)該給定的參數(shù)個數(shù),命令的執(zhí)行次數(shù)和總消耗時長等統(tǒng)計信息,cmd是一個redisCommand。

當(dāng)Redis解析出argv和argc后,會根據(jù)數(shù)組argv[0],到字典中查詢出對應(yīng)的redisCommand。上文的例子中Redis就會去字典去查找SET這個命令對應(yīng)的redisCommand。redis會執(zhí)行redisCommand中命令的實現(xiàn)函數(shù)。

buf&bufpos&reply

buf是一個長度為REDIS_REPLY_CHUNK_BYTES的數(shù)組。Redis執(zhí)行相應(yīng)的操作以后,就會將需要返回的返回的數(shù)據(jù)存儲到buf中,bufpos用于記錄buf中已用的字節(jié)數(shù)數(shù)量,當(dāng)需要恢復(fù)的數(shù)據(jù)大于REDIS_REPLY_CHUNK_BYTES時,redis就會是用reply這個鏈表來保存數(shù)據(jù)。

其他參數(shù)

其他參數(shù)大家看注釋就能明白,就是字面的意思。省略的參數(shù)基本上涉及Redis集群管理的參數(shù),在之后的文章中會繼續(xù)講解。

客戶端的鏈接和斷開

上文說過redisServer是用一個鏈表來維護所有的redisClient狀態(tài),每當(dāng)有一個客戶端發(fā)起鏈接以后,就會在Redis中生成一個對應(yīng)的redisClient數(shù)據(jù)結(jié)構(gòu),增加到clients這個鏈表之后。

一個客戶端很可能被多種原因斷開。

總體分為幾種類型:

客戶端主動退出或者被kill。

timeout超時。

Redis為了自我保護,會斷開發(fā)的數(shù)據(jù)超過限制大小的客戶端。

Redis為了自我保護,會斷需要返回的數(shù)據(jù)超過限制大小的客戶端。

調(diào)用總結(jié)

當(dāng)客戶端和服務(wù)器端的嵌套字變得可讀的時候,服務(wù)器將會調(diào)用命令請求處理器來執(zhí)行以下操作:

讀取嵌套字中的數(shù)據(jù),寫入querybuf。

解析querybuf中的命令,記錄到argc和argv中。

根據(jù)argv[0]查找對應(yīng)的recommand。

執(zhí)行recommand對應(yīng)的實現(xiàn)函數(shù)。

執(zhí)行以后將結(jié)果存入buf&bufpos&reply中,返回給調(diào)用方。

RedisServer(服務(wù)端)

上文是從redisClient的角度來觀察命令的執(zhí)行,文章接下來的部分將會從Redis的代碼層面,微觀的觀察Redis是怎么實現(xiàn)命令的執(zhí)行的。

redisServer的啟動

在了解redisServer的工作機制的工作機制之前,需要了解redisServer的啟動做了什么:

可以繼續(xù)觀察Redis的main()函數(shù)。

int main(int argc, char **argv) {
 //...
 // 創(chuàng)建并初始化服務(wù)器數(shù)據(jù)結(jié)構(gòu)
 initServer();
 //...
}

我們只關(guān)注initServer()這個函數(shù),他負(fù)責(zé)初始化服務(wù)器的數(shù)據(jù)結(jié)構(gòu)。繼續(xù)跟蹤代碼:

void initServer() {
 //...
 //創(chuàng)建eventLoop
 server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);
 /* Create an event handler for accepting new connections in TCP and Unix
 * domain sockets. */
 // 為 TCP 連接關(guān)聯(lián)連接應(yīng)答(accept)處理器
 // 用于接受并應(yīng)答客戶端的 connect() 調(diào)用
 for (j = 0; j < server.ipfd_count; j++) {
 if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
  acceptTcpHandler,NULL) == AE_ERR)
  {
  redisPanic(
   "Unrecoverable error creating server.ipfd file event.");
  }
 }
 
 // 為本地套接字關(guān)聯(lián)應(yīng)答處理器
 if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
 acceptUnixHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.sofd file event.");
 //...
}

在這段代碼里面:

初始化了事件處理器的eventLoop

向eventLoop中注冊了兩個事件處理器acceptTcpHandler和acceptUnixHandler,分別處理遠程的鏈接和本地鏈接。

redisClient的創(chuàng)建

當(dāng)有一個遠程客戶端連接到Redis的服務(wù)器,會觸發(fā)acceptTcpHandler事件處理器.

acceptTcpHandler事件處理器,會創(chuàng)建一個鏈接。然后繼續(xù)調(diào)用acceptCommonHandler。

acceptCommonHandler事件處理器的作用是:

調(diào)用createClient()方法創(chuàng)建redisClient

檢查已經(jīng)創(chuàng)建的redisClient是否超過server允許的數(shù)量的上限

如果超過上限就拒絕遠程連接

否則創(chuàng)建redisClient創(chuàng)建成功

并更新連接的統(tǒng)計次數(shù),更新redisClinet的flags字段

這個時候Redis在服務(wù)端創(chuàng)建了redisClient數(shù)據(jù)結(jié)構(gòu),這個時候遠程的客戶端就在redisServer中創(chuàng)建了一個代理。遠程的客戶端就與Redis服務(wù)器建立了聯(lián)系,就可以向服務(wù)器發(fā)送命令了。

處理命令

在createClient()行數(shù)中:

// 綁定讀事件到事件 loop (開始接收命令請求)
if (aeCreateFileEvent(server.el,fd,AE_READABLE,readQueryFromClient, c) == AE_ERR)

向eventLoop中注冊了readQueryFromClient。readQueryFromClient的作用就是從client中讀取客戶端的查詢緩沖區(qū)內(nèi)容。

然后調(diào)用函數(shù)processInputBuffer來處理客戶端的請求。在processInputBuffer中有幾個核心函數(shù):

processInlineBuffer和processMultibulkBuffer解析querybuf中的命令,記錄到argc和argv中。

processCommand根據(jù)argv[0]查找對應(yīng)的recommen,執(zhí)行recommend對應(yīng)的執(zhí)行函數(shù)。在執(zhí)行之前還會驗證命令的正確性。將結(jié)果存入buf&bufpos&reply中

返回數(shù)據(jù)

萬事具備了,執(zhí)行完了命令就需要把數(shù)據(jù)返回給遠程的調(diào)用方。調(diào)用鏈如下

processCommand->addReply->prepareClientToWrite

在prepareClientToWrite中我們有見到了熟悉的代碼:

aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,sendReplyToClient, c) == AE_ERR) return REDIS_ERR;

向eventloop綁定了sendReplyToClient事件處理器。

在sendReplyToClient中觀察代碼發(fā)現(xiàn),如果bufpos大于0,將會把buf發(fā)送給遠程的客戶端,如果鏈表reply的長度大于0,就會將遍歷鏈表reply,發(fā)送給遠程的客戶端,這里需要注意的是,為了避免reply數(shù)據(jù)量過大,就會過度的占用資源引起Redis相應(yīng)慢。為了解決這個問題,當(dāng)寫入的總數(shù)量大于REDIS_MAX_WRITE_PER_EVENT時,Redis將會臨時中斷寫入,記錄操作的進度,將處理時間讓給其他操作,剩余的內(nèi)容等下次繼續(xù)。這樣的套路我們一路走來看過太多了。

遠程客戶端連接到redis后,redis服務(wù)端會為遠程客戶端創(chuàng)建一個redisClient作為代理。redis會讀取嵌套字中的數(shù)據(jù),寫入querybuf中。解析querybuf中的命令,記錄到argc和argv中。根據(jù)argv[0]查找對應(yīng)的recommand。執(zhí)行recommend對應(yīng)的執(zhí)行函數(shù)。執(zhí)行以后將結(jié)果存入buf&bufpos&reply中。返回給調(diào)用方。返回數(shù)據(jù)的時候,會控制寫入數(shù)據(jù)量的大小,如果過大會分成若干次。保證redis的相應(yīng)時間。

Redis作為單線程應(yīng)用,一直貫徹的思想就是,每個步驟的執(zhí)行都有一個上限(包括執(zhí)行時間的上限或者文件尺寸的上限)一旦達到上限,就會記錄下當(dāng)前的執(zhí)行進度,下次再執(zhí)行。保證了Redis能夠及時響應(yīng)不發(fā)生阻塞。

以上就是關(guān)于Redis學(xué)習(xí)教程之命令執(zhí)行過程的詳細(xì)介紹,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,扣丁學(xué)堂不僅有專業(yè)的Java培訓(xùn)班供大家學(xué)習(xí),還有與時俱進的課程體系和大量的Java視頻教程讓學(xué)員免費觀看學(xué)習(xí),想要學(xué)好Java開發(fā)的小伙伴快到扣丁學(xué)堂來了解詳情吧??鄱W(xué)堂Java技術(shù)交流群:670348138。

扣丁學(xué)堂微信公眾號

【關(guān)注微信公眾號獲取更多學(xué)習(xí)資料】



查看更多關(guān)于“Java開發(fā)資訊”的相關(guān)文章>>

標(biāo)簽: Java培訓(xùn) Java視頻教程 Java多線程 Java面試題 Java學(xué)習(xí)視頻 Java開發(fā)

熱門專區(qū)

暫無熱門資訊

課程推薦

微信
微博
15311698296

全國免費咨詢熱線

郵箱:codingke@1000phone.com

官方群:148715490

北京千鋒互聯(lián)科技有限公司版權(quán)所有   北京市海淀區(qū)寶盛北里西區(qū)28號中關(guān)村智誠科創(chuàng)大廈4層
京ICP備2021002079號-2   Copyright ? 2017 - 2022
返回頂部 返回頂部