<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          破玩意 | Redis 為什么那么快

          共 5081字,需瀏覽 11分鐘

           ·

          2021-05-23 05:21

          低并發(fā)編程
          戰(zhàn)略上藐視技術(shù),戰(zhàn)術(shù)上重視技術(shù)

          我是個(gè) redis 服務(wù),我馬上就要啟動(dòng)了
          因?yàn)槲业闹魅苏诳刂婆_(tái)輸入:
          ./redis-server


          宏觀上看下我的流程

          突然,主人按下了回車(chē)鍵,不得了了。
          shell 程序把我的程序加載到了內(nèi)存,開(kāi)始執(zhí)行我的 main 方法,一切就從這里開(kāi)始了。
          int main(int argc, char **argv) {
             ...
             initServer();
              ...
             aeCreateFileEvent(fd, acceptHandler, ...);
             ...
             aeMain();
             ...
          }
          不要覺(jué)得我這里很復(fù)雜,其實(shí)主要就三大步。
          第一步,我通過(guò) listenToPort() 方法創(chuàng)建了一個(gè) TCP 連接。

          我的這個(gè)方法真是見(jiàn)名知意,而且如果展開(kāi)看就更會(huì)發(fā)現(xiàn)沒(méi)什么神秘的,就是 socket bind listen 標(biāo)準(zhǔn)三步走,建立了一個(gè) TCP 監(jiān)聽(tīng),返回了一個(gè)文件描述符 fd。
          第二步,我通過(guò) aeCreateFileEvent() 方法,將上面那個(gè)創(chuàng)建了 TCP 連接返回的文件描述符 fd,加入到一個(gè)叫 aeFileEvent的鏈表中。

          同時(shí)將這個(gè)文件描述符綁定一個(gè)函數(shù) acceptHandler,這樣當(dāng)有客戶端連接進(jìn)來(lái)時(shí),便會(huì)執(zhí)行這個(gè)函數(shù)。

          第三步,我通過(guò) aeMain() 方法,將上面的 aeFileEvent 鏈表中的文件描述符,統(tǒng)統(tǒng)作為 select 的入?yún)ⅲ@是 IO 多路復(fù)用模式,如果不太了解的同學(xué)請(qǐng)閱讀,《你管這破玩意叫 IO 多路復(fù)用?》。

          好了,其實(shí)就是開(kāi)啟了一個(gè) TCP 監(jiān)聽(tīng),然后如果有客戶端進(jìn)來(lái)的話,讓他執(zhí)行 acceptHandler 函數(shù)而已。
          之后我就一直死等著客戶端連接了。
          void aeMain(aeEventLoop *eventLoop)
          {
              eventLoop->stop = 0;
              while (!eventLoop->stop)
                  aeProcessEvents(eventLoop, AE_ALL_EVENTS);
          }

          ?

          展開(kāi)體驗(yàn)下我的具體工作

          此時(shí),另外一個(gè)人啟動(dòng)了一個(gè) redis-client,連接到了我。
          redis-cli -h host -p port
          那么我頭上的 fd 就會(huì)感知有數(shù)據(jù)讀入,并執(zhí)行 acceptHandler 方法。
          static void acceptHandler(...) {
             ...
              cfd = anetAccept(...);
             ...
              c = createClient(cfd))
             ...
          }
          可以看到,當(dāng)有新客戶端連接進(jìn)來(lái)時(shí),便會(huì)調(diào)用 createClient 創(chuàng)建一個(gè)專(zhuān)屬的 client 為其服務(wù)。
          static redisClient *createClient(int fd) {
             ...
             aeCreateFileEvent(c->fd, readQueryFromClient, ...);
             ...
          }
          這里又可以看到,所謂的專(zhuān)屬服務(wù),其實(shí)仍然是這個(gè) aeCreateFileEvent 函數(shù)。
          這個(gè)上面說(shuō)了,這個(gè)函數(shù)的功能就是把文件描述符掛在鏈表上,然后分配一個(gè)處理函數(shù)。
          當(dāng)然,這回的處理函數(shù)不再是處理新客戶端連接的 acceptHandler,而是處理具體客戶端傳來(lái)的 redis 命令的函數(shù) readQueryFromClient

          不難想象,如果再來(lái)一個(gè)客戶端,又來(lái)一個(gè)客戶端... 那么不斷將新客戶端的文件描述符掛上去即可,而監(jiān)聽(tīng)新客戶端連接的,始終是最上面那個(gè)文件描述符。

          好了,服務(wù)端開(kāi)啟了監(jiān)聽(tīng),客戶端也連上了服務(wù)端,此時(shí)我仍然在死等狀態(tài),只不過(guò)等的不只是新客戶端連接到達(dá),還在等待已經(jīng)連接上的客戶端發(fā)來(lái)命令。

          請(qǐng)注意,這里的死等,只有一個(gè)線程,循環(huán)調(diào)用 aeProcessEvents 函數(shù),用 select 的方式監(jiān)聽(tīng)多個(gè)文件描述符。放上剛剛 main 方法的第三步,幫大家回憶一下。
          void aeMain(aeEventLoop *eventLoop)
          {
              eventLoop->stop = 0;
              while (!eventLoop->stop)
                  aeProcessEvents(eventLoop, AE_ALL_EVENTS);
          }
          當(dāng)有新客戶端建立連接時(shí),會(huì)觸發(fā) acceptHandler 函數(shù)執(zhí)行,多出一個(gè)等待數(shù)據(jù)的描述符。

          當(dāng)有客戶端數(shù)據(jù)傳來(lái)時(shí),會(huì)觸發(fā) readQueryFromClient 函數(shù)執(zhí)行,完成這個(gè)命令的操作。

          注意,由于只有一個(gè)線程在監(jiān)聽(tīng)這些描述符,并做處理。所以即使客戶端并發(fā)地發(fā)送命令,后面仍然是依次取出命令,順序執(zhí)行

          這也就是我們常常說(shuō)的,redis 是單線程的,命令與命令之間是順序執(zhí)行,無(wú)需考慮線程安全的問(wèn)題。

          為了方便大家吹牛,我來(lái)拔高一下

          大家發(fā)現(xiàn)沒(méi),我的啟動(dòng)過(guò)程,其實(shí)就分成兩個(gè)大的部分。
          一個(gè)是監(jiān)聽(tīng)客戶端的請(qǐng)求,就是用 IO 多路復(fù)用的方式,監(jiān)聽(tīng)多個(gè)文件描述符,就剛剛那個(gè) aeMain() 方法干的事嘛。
          一個(gè)是執(zhí)行相應(yīng)的函數(shù)去處理這個(gè)請(qǐng)求,具體執(zhí)行什么函數(shù)就是出現(xiàn)多次的 aeCreateFileEvent() 方法去綁定的,這個(gè)相應(yīng)的函數(shù)說(shuō)得高大上一點(diǎn),叫做事件處理器

          這里所謂的連接應(yīng)答處理器,就是剛剛監(jiān)聽(tīng)連接的文件描述符所綁定的函數(shù) acceptHandler
          所謂的命令請(qǐng)求處理器,就是監(jiān)聽(tīng)客戶端命令(讀事件)的文件描述符綁定的函數(shù) readQueryFromClient
          所謂的命令回復(fù)處理器,就是后面要提到的,監(jiān)聽(tīng)客戶端響應(yīng)(寫(xiě)事件)的文件描述符綁定的函數(shù) sendReplyToClient
          這種一個(gè)負(fù)責(zé)響應(yīng) IO 事件,一個(gè)負(fù)責(zé)交給相應(yīng)的事件處理器去處理,就叫做 Reactor 模式
          Redis 正是基于 Reactor 模式開(kāi)發(fā)了自己的文件事件處理器,實(shí)現(xiàn)了高性能的網(wǎng)絡(luò)通信模型,并且保持了 Redis 內(nèi)部單線程設(shè)計(jì)的簡(jiǎn)單性
          有點(diǎn)擔(dān)心這句話吹牛的逼格不夠,其實(shí)我是參考了《Redis 設(shè)計(jì)與實(shí)現(xiàn)》,截圖給大家。

          ?

          具體怎么執(zhí)行一個(gè) Redis 命令

          現(xiàn)在,我們通過(guò)一個(gè)已建立好連接的客戶端,發(fā)一個(gè) redis 命令。
          <client 6379> set dibingfa niubi
          此時(shí) readQueryFromClient 函數(shù)將被執(zhí)行。
          這個(gè)函數(shù)會(huì)去一張表中尋找命令所對(duì)應(yīng)的函數(shù),這部分用的編碼技巧叫命令模式。
          static struct redisCommand cmdTable[] = {
              {"get",getCommand,2,REDIS_CMD_INLINE},
              {"set",setCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
              {"setnx",setnxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
              {"del",delCommand,-2,REDIS_CMD_INLINE},
              {"exists",existsCommand,2,REDIS_CMD_INLINE},
             ...
          }
          找到了 set 命令對(duì)應(yīng)的函數(shù)就是 setCommand
          這個(gè)函數(shù),最終就會(huì)一步步地將 key 和 value 分別存儲(chǔ)起來(lái),這部分的源碼細(xì)節(jié),可以閱讀我之前的文章,《Redis 數(shù)據(jù)結(jié)構(gòu)之字符串的那些騷操作》
          而關(guān)于 redis 數(shù)據(jù)結(jié)構(gòu)與底層對(duì)應(yīng)的編碼結(jié)構(gòu),可以閱讀我之前的文章,《面試官問(wèn)我 redis 的數(shù)據(jù)類(lèi)型,我回答了 8 種》
          處理完命令后,就要發(fā)送響應(yīng)給客戶端了。
          static void setCommand(redisClient *c) {
             ...
              addReply(c, nx ? shared.cone : shared.ok);
          }
          這個(gè)響應(yīng),并不是直接同步寫(xiě)回去,當(dāng)然也不是開(kāi)啟一個(gè)線程去異步寫(xiě)回去。
          它仍然是調(diào)用那個(gè)萬(wàn)惡的 aeCreateFileEvent 函數(shù),將 sendReplyToClient 函數(shù)掛在需要響應(yīng)的客戶端連接的文件描述符上。
          static void addReply(redisClient *c, robj *obj) {
             ...
              aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
              sendReplyToClient, c, NULL) == AE_ERR);
          }
          好了,這回上一小節(jié)挖的坑,終于補(bǔ)上了。
          以上這些個(gè)破玩意,就是我的啟動(dòng)過(guò)程啦,我是不是很可愛(ài)。


          后記





          整篇文章我好像沒(méi)講 Redis 為啥那么快,因?yàn)槲腋杏X(jué)這個(gè)問(wèn)題問(wèn)得不好。

          你可以從接收網(wǎng)絡(luò)請(qǐng)求的 IO 多路復(fù)用角度說(shuō)起,也可以從事件處理器驅(qū)動(dòng)的 Reactor 模式說(shuō)起,還可以從具體處理命令時(shí)的數(shù)據(jù)結(jié)構(gòu)說(shuō)起,比如單單是字符串背后的 sds 其實(shí)就做了很多的巧妙設(shè)計(jì)。

          如果我是面試官,我會(huì)具體讓面試者聊聊 Redis 的啟動(dòng)流程,或者 Redis 處理命令的整個(gè)流程。

          這里面可挖的點(diǎn)挺多的,如果能談笑風(fēng)生,那自然是技術(shù)水平還不錯(cuò)。

          另外,你會(huì)發(fā)現(xiàn)本文出現(xiàn)的很多唬人的術(shù)語(yǔ),比如 Reactor 模式,事件處理器等,看一遍 Redis 源碼后你會(huì)發(fā)現(xiàn)真的非常簡(jiǎn)單。

          毫不客氣地說(shuō),一切絲毫不談具體實(shí)現(xiàn),和你堆砌一大堆唬人名詞的文章或者人,都是在耍流氓。

          本文我參考的是 Redis3.0.0 源碼,但成文時(shí)用的講解代碼是 Redis1.0.0,整個(gè)網(wǎng)絡(luò)模塊的設(shè)計(jì)是完全一樣的。


          專(zhuān)屬附贈(zèng)


          我整理了一份 Redis 啟動(dòng)流程的極簡(jiǎn)版附注釋代碼的精美 PDF。

          大家可以加我好友獲取(低并發(fā)編程 菜單欄),由于我比較懶,直接把它放在朋友圈第一條了,大家直接看我朋友圈獲取即可,哈哈哈。

          瀏覽 47
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中文字幕视频在线观看 | 天天爽天天爽天天爽天天爽 | 91成人久久久 | 120分钟婬片免费看 | 久久艹伊人 |