麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁(yè) > 數(shù)據(jù)庫(kù) > Redis > 正文

Redis學(xué)習(xí)教程之命令的執(zhí)行過(guò)程詳解

2020-03-17 12:34:41
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

前言

之前寫(xiě)了一系列文章,已經(jīng)很深入的探討了 Redis 的數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)庫(kù)的實(shí)現(xiàn),key的過(guò)期策略以及 Redis 是怎么處理事件的。所以距離 Redis 的單機(jī)實(shí)現(xiàn)只差最后一步了,就是 Redis 是怎么處理 client 發(fā)來(lái)的命令并返回結(jié)果的,所以我們就仔細(xì)討論一下 Redis 是怎么執(zhí)行命令的。

閱讀這篇文章你將會(huì)了解到:

  • Redis 是怎么執(zhí)行遠(yuǎn)程客戶(hù)端發(fā)來(lái)的命令的

Redis client(客戶(hù)端)

Redis 是單線(xiàn)程應(yīng)用,它是如何與多個(gè)客戶(hù)端簡(jiǎn)歷網(wǎng)絡(luò)鏈接并處理命令的?
由于 Redis 是基于 I/O 多路復(fù)用技術(shù),為了能夠處理多個(gè)客戶(hù)端的請(qǐng)求,Redis 在本地為每一個(gè)鏈接到 Redis 服務(wù)器的客戶(hù)端創(chuàng)建了一個(gè) redis/47032.html">redisClient 的數(shù)據(jù)結(jié)構(gòu),這個(gè)數(shù)據(jù)結(jié)構(gòu)包含了每個(gè)客戶(hù)端各自的狀態(tài)和執(zhí)行的命令。 Redis 服務(wù)器使用一個(gè)鏈表來(lái)維護(hù)多個(gè) redisClient 數(shù)據(jù)結(jié)構(gòu)。

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

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

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

typedef struct redisClient { // 客戶(hù)端狀態(tài)標(biāo)志 int flags;  /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */  // 套接字描述符 int fd; // 當(dāng)前正在使用的數(shù)據(jù)庫(kù) redisDb *db; // 當(dāng)前正在使用的數(shù)據(jù)庫(kù)的 id (號(hào)碼) int dictid; // 客戶(hù)端的名字 robj *name;  /* As set by CLIENT SETNAME */ // 查詢(xún)緩沖區(qū) sds querybuf; // 查詢(xún)緩沖區(qū)長(zhǎng)度峰值 size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size */ // 參數(shù)數(shù)量 int argc; // 參數(shù)對(duì)象數(shù)組 robj **argv; // 記錄被客戶(hù)端執(zhí)行的命令 struct redisCommand *cmd, *lastcmd; // 請(qǐng)求的類(lèi)型:內(nèi)聯(lián)命令還是多條命令 int reqtype; // 剩余未讀取的命令內(nèi)容數(shù)量 int multibulklen; /* number of multi bulk arguments left to read */ // 命令內(nèi)容的長(zhǎng)度 long bulklen;  /* length of bulk argument in multi bulk request */ // 回復(fù)鏈表 list *reply; // 回復(fù)鏈表中對(duì)象的總大小 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 并非指遠(yuǎn)程的客戶(hù)端,而是一個(gè) Redis 服務(wù)本地的數(shù)據(jù)結(jié)構(gòu),我們可以理解這個(gè) redisClient 是遠(yuǎn)程客戶(hù)端的一個(gè)映射或者代理。

flags

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

querybuf

querybuf 是一個(gè) sds 動(dòng)態(tài)字符串類(lèi)型,所謂 buf 說(shuō)明是它只是一個(gè)緩沖區(qū),用于存儲(chǔ)沒(méi)有被解析的命令。

argc & argv

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

cmd

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

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

buf & bufpos & reply

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

其他參數(shù)

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

客戶(hù)端的鏈接和斷開(kāi)

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

一個(gè)客戶(hù)端很可能被多種原因斷開(kāi)。

總體分為幾種類(lèi)型:

  • 客戶(hù)端主動(dòng)退出或者被 kill。
  • timeout 超時(shí)。
  • Redis 為了自我保護(hù),會(huì)斷開(kāi)發(fā)的數(shù)據(jù)超過(guò)限制大小的客戶(hù)端。
  • Redis 為了自我保護(hù),會(huì)斷需要返回的數(shù)據(jù)超過(guò)限制大小的客戶(hù)端。

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

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

  1. 讀取嵌套字中的數(shù)據(jù),寫(xiě)入 querybuf。
  2. 解析 querybuf 中的命令,記錄到 argc 和 argv 中。
  3. 根據(jù) argv[0] 查找對(duì)應(yīng)的 recommand。
  4. 執(zhí)行 recommand 對(duì)應(yīng)的實(shí)現(xiàn)函數(shù)。
  5. 執(zhí)行以后將結(jié)果存入 buf & bufpos & reply 中,返回給調(diào)用方。

Redis Server (服務(wù)端)

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

redisServer 的啟動(dòng)

在了解redisServer 的工作機(jī)制的工作機(jī)制之前,需要了解 redisServer 的啟動(dòng)做了什么:

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

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

我們只關(guān)注 initServer() 這個(gè)函數(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)答客戶(hù)端的 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."); //...}

篇幅限制,我們省略了很多與本編文章無(wú)關(guān)的代碼,保留了核心邏輯代碼。

在上一篇文章中 《Redis 中的事件驅(qū)動(dòng)模型》 我們講解過(guò),redis 使用不同的事件處理器,處理不同的事件。

在這段代碼里面:

  • 初始化了事件處理器的 eventLoop
  • 向 eventLoop 中注冊(cè)了兩個(gè)事件處理器 acceptTcpHandler 和 acceptUnixHandler,分別處理遠(yuǎn)程的鏈接和本地鏈接。

redisClient 的創(chuàng)建

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

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

acceptCommonHandler 事件處理器的作用是:

  • 調(diào)用 createClient() 方法創(chuàng)建 redisClient
  • 檢查已經(jīng)創(chuàng)建的 redisClient 是否超過(guò) server 允許的數(shù)量的上限
  • 如果超過(guò)上限就拒絕遠(yuǎn)程連接
  • 否則創(chuàng)建 redisClient 創(chuàng)建成功
  • 并更新連接的統(tǒng)計(jì)次數(shù),更新 redisClinet 的 flags 字段

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

處理命令

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

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

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

然后調(diào)用函數(shù) processInputBuffer 來(lái)處理客戶(hù)端的請(qǐng)求。在 processInputBuffer 中有幾個(gè)核心函數(shù):

  • processInlineBuffer 和 processMultibulkBuffer 解析 querybuf 中的命令,記錄到 argc 和 argv 中。
  • processCommand 根據(jù) argv[0] 查找對(duì)應(yīng)的 recommen,執(zhí)行 recommend 對(duì)應(yīng)的執(zhí)行函數(shù)。在執(zhí)行之前還會(huì)驗(yàn)證命令的正確性。將結(jié)果存入 buf & bufpos & reply 中

返回?cái)?shù)據(jù)

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

processCommand -> addReply -> prepareClientToWrite

在 prepareClientToWrite 中我們有見(jiàn)到了熟悉的代碼:

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

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

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

總結(jié)

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

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

好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)VEVB武林網(wǎng)的支持。


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到Redis頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 九九热视频免费 | 亚洲精品午夜电影 | 欧美视频一二三区 | 中文字幕在线观看91 | 欧美成人黄色片 | 久久99精品视频在线观看 | 粉嫩粉嫩一区二区三区在线播放 | 国产外围在线 | 久久亚洲国产午夜精品理论片 | 97干色| www.17c亚洲蜜桃 | 欧美一级理论 | 主播粉嫩国产在线精品 | 少妇的肉体的满足毛片 | 免费黄色欧美视频 | 亚洲国产网址 | 国产日本在线 | h色网站免费观看 | 中国fx性欧美xxxx | 男人天堂免费 | 久久精品爱 | 亚洲国产精久久久久久久 | 午夜精品福利视频 | 亚洲欧美日韩免费 | 一色视频 | 97久久日一线二线三线 | 一级一级一级一级毛片 | 欧美日本91精品久久久久 | 一区二区三区日韩在线 | 欧美一级片免费在线观看 | 毛片区 | 49vvv| 国产精品久久久久影院老司 | 91福利国产在线观一区二区 | 免费一区区三区四区 | 国产精品久久国产精品 | 国产资源在线视频 | 亚洲男人天堂 | 一级毛片在线免费播放 | 久久国产精品网 | 亚洲精品在线观看免费 |