<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    jinfeng_wang

    G-G-S,D-D-U!

    BlogJava 首頁(yè) 新隨筆 聯(lián)系 聚合 管理
      400 Posts :: 0 Stories :: 296 Comments :: 0 Trackbacks
    http://mp.weixin.qq.com/s/M_8JYKounmZWHPOXVJFNuQ?utm_source=tuicool&utm_medium=referral  


    綜述


    最近筆者閱讀并研究Redis源碼,在Redis客戶端與服務(wù)器端交互這個(gè)內(nèi)容點(diǎn)上,需要參考網(wǎng)上一些文章,但是遺憾的是發(fā)現(xiàn)大部分文章都斷斷續(xù)續(xù)的非系統(tǒng)性的,不能給讀者此交互流程的整體把握。所以這里我嘗試,站在源碼的角度,將Redis client/server 交互流程盡可能簡(jiǎn)單地展現(xiàn)給大家,同時(shí)也站在DBA的角度給出一些日常工作中注意事項(xiàng)。 

    Redis client/server 交互步驟分為以下6個(gè)步驟:

    Step 1:Client 發(fā)起socket 連接

    Step 2:Server 接受socket連接

    Step 3:客戶端 開始寫入

    Step 4:server 端接收寫入

    Step 5:server 返回寫入結(jié)果

    Step 6:Client收到返回結(jié)果 

    注:為使文章盡可能簡(jiǎn)潔,這里只討論客戶端命令寫入的過程,不討論客戶端命令讀取的流程。 

    在進(jìn)一步閱讀和了解互動(dòng)流程之前,請(qǐng)大家確保已經(jīng)熟練掌握了Linux Socket 建立流程和epoll I/O 多路復(fù)用技術(shù)兩個(gè)技術(shù)點(diǎn),這對(duì)文章內(nèi)容的理解至關(guān)重要。(這兩個(gè)技術(shù)點(diǎn)在文末的附錄有講解,想復(fù)習(xí)的同學(xué)請(qǐng)先翻到文末)


    交互的整體流程


    在介紹6個(gè)步驟之前,首先看一下Redis client/server 交互流程整體的程序執(zhí)行流程圖:


    上圖中6個(gè)步驟分別用不同的顏色箭頭表示,并且最終結(jié)果也用相對(duì)應(yīng)的顏色標(biāo)識(shí)。 

    首先看看綠色框里面的循環(huán)執(zhí)行的方法,最末是epoll_wait方法,即等待事件產(chǎn)生的方法。然后再看第2、4、5步驟的末尾都有epoll_ctl方法,即epoll事件注冊(cè)函數(shù)。關(guān)于epoll的相關(guān)技術(shù)解析請(qǐng)參看文末一段。 

    在這里的循環(huán)還有個(gè)beforeSleep方法,其實(shí)它跟我們這次討論的話題沒有太大的關(guān)系。但是還是想給大家介紹一下。

    beforeSleep方法主要做以下幾件事:

    1. 執(zhí)行一次快速的主動(dòng)過期檢查,檢查是否有過期的key

    2. 當(dāng)有客戶端阻塞時(shí),向所有從庫(kù)發(fā)送ACK請(qǐng)求

    3. unblock 在同步復(fù)制時(shí)候被阻塞的客戶端

    4. 嘗試執(zhí)行之前被阻塞客戶端的命令

    5. 將AOF緩沖區(qū)的內(nèi)容寫入到AOF文件中

    6. 如果是集群,將會(huì)根據(jù)需要執(zhí)行故障遷移、更新節(jié)點(diǎn)狀態(tài)、保存node.conf 配置文件。 

    如此,Redis整個(gè)事件管理機(jī)制就比較清楚了。接下來(lái)進(jìn)一步探討并理解事件是如何觸發(fā)并創(chuàng)建。

     

    交互的六大步驟


    下面正式開始介紹Redis client/server 交互的6大步驟


    Step 1:Client 發(fā)起socket 連接


    這里以redis-cli 客戶端為例,當(dāng)執(zhí)行以下語(yǔ)句時(shí): 

    [root@zbdba redis-3.0]# ./src/redis-cli -p 6379 -h 127.0.0.1

    127.0.0.1:6379> 

    客戶端會(huì)做如下操作:

    (1) 獲取客戶端參數(shù),如端口、ip地址、dbnum、socket等

    也就是我們執(zhí)行./src/redis-cli --help 中列出的參數(shù) 

    (2) 根據(jù)用戶指定參數(shù)確定客戶端處于哪種模式

    目前共有8種模式 

    • Latency mode

    • Slave mode

    • Get RDB mode

    • Pipe mode

    • Find big keys

    • Stat mode

    • Scan mode

    • Intrinsic latency mode

    例如:stat 模式 

    [root@zbdba redis-3.0]# ./src/redis-cli -p 6379 -h 127.0.0.1 --stat

    ------- data ------ --------------------- load -------------------- - child -

    keys       mem      clients blocked requests            connections         

    1          817.18K  2       0       1 (+0)              2           

    1          817.18K  2       0       2 (+1)              2           

    1          817.18K  2       0       3 (+1)              2           

    1          817.18K  2       0       4 (+1)              2           

    1          817.18K  2       0       5 (+1)              2           

    1          817.18K  2       0       6 (+1)              2           

    我們這里沒有指定,就是默認(rèn)的模式。

    (3 ) 進(jìn)入上圖中step1的cliConnect 方法,cliConnect主要包含redisConnect、redisConnectUnix方法。這兩個(gè)方法分別用于TCP Socket連接以及Unix Socket連接,Unix Socket用于同一主機(jī)進(jìn)程間的通信。我們上面是采用的TCP Socket連接方式也就是我們平常生產(chǎn)環(huán)境常用的方式,這里不討論Unix Socket連接方式,如果要使用Unix Socket連接方式,需要配置unixsocket 參數(shù),并且按照下面方式進(jìn)行連接: 

    [root@zbdba redis-3.0]# ./src/redis-cli -s /tmp/redis.sock

    redis /tmp/redis.sock> 

    (4) 進(jìn)入redisContextInit方法,redisContextInit方法用于創(chuàng)建一個(gè)Context結(jié)構(gòu)體保存在內(nèi)存中,如下: 

    /* Context for a connection to Redis */

    typedef struct redisContext {

        int err; /* Error flags, 0 when there is no error */

        char errstr[128]; /* String representation of error when applicable */

        int fd;

        int flags;

        char *obuf; /* Write buffer */

        redisReader *reader; /* Protocol reader */

    } redisContext; 

    主要用于保存客戶端的一些東西,最重要的就是 write buffer和redisReader,write buffer 用于保存客戶端的寫入,redisReader用于保存協(xié)議解析器的一些狀態(tài)。 

    (5) 進(jìn)入redisContextConnectTcp 方法,開始獲取IP地址和端口用于建立連接,主要方法如下: 

    s = socket(p->ai_family,p->ai_socktype,p->ai_protocol

    connect(s,p->ai_addr,p->ai_addrlen) 

    到此客戶端向服務(wù)端發(fā)起建立socket連接,并且等待服務(wù)器端響應(yīng)。

    當(dāng)然cliConnect方法中還會(huì)調(diào)用cliAuth方法用于權(quán)限驗(yàn)證、cliSelect用于db選擇,這里不著重討論。

     

    Step 2:Server 接受socket連接


    服務(wù)器接收客戶端的請(qǐng)求首先是從epoll_wait取出相關(guān)的事件,然后進(jìn)入上圖中step2中的方法,執(zhí)行acceptTcpHandler或者acceptUnixHandler方法,那么這兩個(gè)方法對(duì)應(yīng)的事件是在什么時(shí)候注冊(cè)的呢?他們是在服務(wù)器端初始化的時(shí)候創(chuàng)建。下面看看服務(wù)器端在初始化的時(shí)候與socket相關(guān)的地方

    (1) 打開TCP監(jiān)聽端口 

        if (server.port != 0 &&

            listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)

            exit(1); 

    (2) 打開unix 本地端口 

      if (server.unixsocket != NULL) {

            unlink(server.unixsocket); /* don't care if this fails */

            server.sofd = anetUnixServer(server.neterr,server.unixsocket,

                server.unixsocketperm, server.tcp_backlog);

            if (server.sofd == ANET_ERR) {

                redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);

                exit(1);

            }

            anetNonBlock(NULL,server.sofd);

        } 

    (3) 為TCP連接關(guān)聯(lián)連接應(yīng)答處理器(accept) 

        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.");

                }

        } 

    (4) 為Unix Socket關(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.");

    在1/2步驟涉及到的方法中是Linux Socket的常規(guī)操作,獲取IP地址,端口。最終通過socket、bind、listen方法建立起Socket監(jiān)聽。也就是上圖中acceptTcpHandler和acceptUnixHandler下面對(duì)應(yīng)的方法。 

    在3/4步驟涉及到的方法中采用aeCreateFileEvent 方法創(chuàng)建相關(guān)的連接應(yīng)答處理器,在客戶端請(qǐng)求連接的時(shí)候觸發(fā)。

    所以現(xiàn)在整個(gè)socket連接建立流程就比較清楚了,如下:

    1. 服務(wù)器初始化建立socket監(jiān)聽

    2. 服務(wù)器初始化創(chuàng)建相關(guān)連接應(yīng)答處理器,通過epoll_ctl注冊(cè)事件

    3. 客戶端初始化創(chuàng)建socket connect 請(qǐng)求

    4. 服務(wù)器接受到請(qǐng)求,用epoll_wait方法取出事件

    5. 服務(wù)器執(zhí)行事件中的方法(acceptTcpHandler/acceptUnixHandler)并接受socket連接 

    至此客戶端和服務(wù)器端的socket連接已經(jīng)建立,但是此時(shí)服務(wù)器端還繼續(xù)做了2件事: 

    1. 采用createClient方法在服務(wù)器端為客戶端創(chuàng)建一個(gè)client,因?yàn)镮/O復(fù)用所以需要為每個(gè)客戶端維持一個(gè)狀態(tài)。這里的client也在內(nèi)存中分配了一塊區(qū)域,用于保存它的一些信息,如套接字描述符、默認(rèn)數(shù)據(jù)庫(kù)、查詢緩沖區(qū)、命令參數(shù)、認(rèn)證狀態(tài)、回復(fù)緩沖區(qū)等。這里提醒一下DBA同學(xué)關(guān)于client-output-buffer-limit設(shè)置,設(shè)置不恰當(dāng)將會(huì)引起客戶端中斷。

    2. 采用aeCreateFileEvent方法在服務(wù)器端創(chuàng)建一個(gè)文件讀事件并且綁定readQueryFromClient方法。 

    可以從圖中得知,aeCreateFileEvent 調(diào)用aeApiAddEvent方法最終通過epoll_ctl 方法進(jìn)行注冊(cè)事件。


    Step 3 :客戶端開始寫入

    客戶端在與服務(wù)器端建立好socket連接之后,開始執(zhí)行上圖中step3的repl方法。從圖中可知repl方法接受輸入輸出主要是采用linenoise插件。當(dāng)然這是針對(duì)redis-cli客戶端哦。linenoise 是一款優(yōu)秀的命令行編輯庫(kù),被廣泛的運(yùn)用在各種DB上,如Redis、MongoDB,這里不詳細(xì)討論。客戶端寫入流程分為以下幾步:

    (1) linenoise等待接受用戶輸入

    (2) linenoise 將用戶輸入內(nèi)容傳入cliSendCommand方法,cliSendCommand方法會(huì)判斷命令是否為特殊命令,如:

    •    help

    •    info

    •    cluster nodes

    •    cluster info

    •    client list

    •    shutdown

    •    monitor

    •    subscribe

    •    psubscribe

    •    sync

    •    psync

    客戶端會(huì)根據(jù)以上命令設(shè)置對(duì)應(yīng)的輸出格式以及客戶端的模式,因?yàn)檫@里我們是普通寫入,所以不會(huì)涉及到以上的情況。

    (3) cliSendCommand方法會(huì)調(diào)用redisAppendCommandArgv方法,redisAppendCommandArgv方法會(huì)調(diào)用redisFormatCommandArgv和__redisAppendCommand方法

    redisFormatCommandArgv方法用于將客戶端輸入的內(nèi)容格式化成redis協(xié)議:

    例如:

    set zbdba jingbo

    *3\r\n$3\r\n set\r\n $5\r\n zbdba\r\n $6\r\n jingbo

    __redisAppendCommand方法用于將命令寫入到outbuf中

    接著客戶端進(jìn)入下一個(gè)流程,將outbuf內(nèi)容寫入到套接字描述符上并傳輸?shù)椒?wù)器端。 

    (4) 進(jìn)入redisGetReply方法,該方法下主要有redisGetReplyFromReader和redisBufferWrite 方法,redisGetReplyFromReader主要用于讀取掛起的回復(fù),redisBufferWrite 方法用于將當(dāng)前outbuf中的內(nèi)容寫入到套接字描述符中,并傳輸內(nèi)容。

    主要方法如下:

    nwritten = write(c->fd,c->obuf,sdslen(c->obuf));


    Step 4:server 端接收寫入


    服務(wù)器端依然在進(jìn)行事件循環(huán),在客戶端發(fā)來(lái)內(nèi)容的時(shí)候觸發(fā),對(duì)應(yīng)的文件讀取事件。這就是之前創(chuàng)建socket連接的時(shí)候建立的事件,該事件綁定的方法是readQueryFromClient 。此時(shí)進(jìn)入step4的readQueryFromClient 方法。

    readQueryFromClient 方法用于讀取客戶端的發(fā)送的內(nèi)容。它的執(zhí)行步驟如下:

    (1) 在readQueryFromClient方法中從服務(wù)器端套接字描述符中讀取客戶端的內(nèi)容到服務(wù)器端初始化client的查詢緩沖中,主要方法如下: 

    nread = read(fd, c->querybuf+qblen, readlen); 

    (2) 交給processInputBuffer處理,processInputBuffer 主要包含兩個(gè)方法,processInlineBuffer和processCommand。processInlineBuffer方法用于采用Redis協(xié)議解析客戶端內(nèi)容并生成對(duì)應(yīng)的命令并傳給processCommand 方法,processCommand方法則用于執(zhí)行該命令

    (3) processCommand方法會(huì)以下操作:

    •    處理是否為quit命令。

    •    對(duì)命令語(yǔ)法及參數(shù)會(huì)進(jìn)行檢查。

    •    這里如果采取認(rèn)證也會(huì)檢查認(rèn)證信息。

    •    如果Redis為集群模式,這里將進(jìn)行hash計(jì)算key所屬slot并進(jìn)行轉(zhuǎn)向操作。

    •    如果設(shè)置最大內(nèi)存,那么檢查內(nèi)存是否超過限制,如果超過限制會(huì)根據(jù)相應(yīng)的內(nèi)存策略刪除符合條件的鍵來(lái)釋放內(nèi)存

    •    如果這是一個(gè)主服務(wù)器,并且這個(gè)服務(wù)器之前執(zhí)行bgsave發(fā)生了錯(cuò)誤,那么不執(zhí)行命令

    •    如果min-slaves-to-write開啟,如果沒有足夠多的從服務(wù)器將不會(huì)執(zhí)行命令

             注:所以DBA在此的設(shè)置非常重要,建議不是特殊場(chǎng)景不要設(shè)置。

    •    如果這個(gè)服務(wù)器是一個(gè)只讀從庫(kù)的話,拒絕寫入命令。

    •    在訂閱于發(fā)布模式的上下文中,只能執(zhí)行訂閱和退訂相關(guān)的命令

    •    當(dāng)這個(gè)服務(wù)器是從庫(kù),master_link down 并且slave-serve-stale-data 為 no 只允許info 和slaveof命令

    •    如果服務(wù)器正在載入數(shù)據(jù)到數(shù)據(jù)庫(kù),那么只執(zhí)行帶有REDIS_CMD_LOADING標(biāo)識(shí)的命令

    •    lua腳本超時(shí),只允許執(zhí)行限定的操作,比如shutdown、script kill 等 

    (4) 最后進(jìn)入call方法。

    call方法會(huì)調(diào)用setCommand,因?yàn)檫@里我們執(zhí)行的set zbdba jingbo,set 命令對(duì)應(yīng)setCommand 方法,Redis服務(wù)器端在開始初始化的時(shí)候就會(huì)初始化命令表,命令表如下:  

    struct redisCommand redisCommandTable[] = {

        {"get",getCommand,2,"r",0,NULL,1,1,1,0,0},

        {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},

        {"setnx",setnxCommand,3,"wm",0,NULL,1,1,1,0,0},

        {"setex",setexCommand,4,"wm",0,NULL,1,1,1,0,0},

        {"psetex",psetexCommand,4,"wm",0,NULL,1,1,1,0,0},

        {"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},

        {"strlen",strlenCommand,2,"r",0,NULL,1,1,1,0,0},

        {"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0},

        {"exists",existsCommand,2,"r",0,NULL,1,1,1,0,0},

        {"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0},

        ....

    }

    所以如果是其他的命令會(huì)調(diào)用其他相對(duì)應(yīng)的方法。call方法還會(huì)做一些事件,比如發(fā)送命令到從庫(kù)、發(fā)送命令到aof、計(jì)算命令執(zhí)行的時(shí)間。 

    (5) setCommand方法,setCommand方法會(huì)調(diào)用setGenericCommand方法,該方法首先會(huì)判斷該key是否已經(jīng)過期,最后調(diào)用setKey方法。

    這里需要說(shuō)明一點(diǎn)的是,通過以上的分析。Redis的key過期包括主動(dòng)檢測(cè)以及被動(dòng)監(jiān)測(cè) 。

    主動(dòng)監(jiān)測(cè)

    • 在beforeSleep方法中執(zhí)行key快速過期檢查,檢查模式為ACTIVE_EXPIRE_CYCLE_FAST。周期為每個(gè)事件執(zhí)行完成時(shí)間到下一次事件循環(huán)開始。

    • 在serverCron方法中執(zhí)行key過期檢查,這是key過期檢查主要的地方,檢查模式為ACTIVE_EXPIRE_CYCLE_SLOW,serverCron方法執(zhí)行周期為1秒鐘執(zhí)行server.hz 次,hz默認(rèn)為10,所以約100ms執(zhí)行一次。hz設(shè)置越大過期鍵刪除就越精準(zhǔn),但是cpu使用率會(huì)越高,這里我們線上Redis采用的默認(rèn)值。Redis主要是在這個(gè)方法里刪除大部分的過期鍵。

    被動(dòng)監(jiān)測(cè)

    • 使用內(nèi)存超過最大內(nèi)存被迫根據(jù)相應(yīng)的內(nèi)存策略刪除符合條件的key。

    • 在key寫入之前進(jìn)行被動(dòng)檢查,檢查key是否過期,過期就進(jìn)行刪除。

    • 還有一種不友好的方式,就是randomkey命令,該命令隨機(jī)從Redis獲取鍵,每次獲取到鍵的時(shí)候會(huì)檢查該鍵是否過期。 

    以上主要是讓運(yùn)維的同學(xué)更加清楚Redis的key過期刪除機(jī)制。

    (6) 進(jìn)入setKey方法,setKey方法最終會(huì)調(diào)用dbAdd方法,其實(shí)最終就是將該鍵值對(duì)存入服務(wù)器端維護(hù)的一個(gè)字典中,該字典是在服務(wù)器初始化的時(shí)候創(chuàng)建,用于存儲(chǔ)服務(wù)器的相關(guān)信息,其中包括各種數(shù)據(jù)類型的鍵值存儲(chǔ)。完成了寫入方法時(shí)候,此時(shí)服務(wù)器端會(huì)給客戶端返回結(jié)果。 

    (7) 進(jìn)入prepareClientToWrite方法然后通過調(diào)用_addReplyToBuffer方法將返回結(jié)果寫入到outbuf中(客戶端連接時(shí)創(chuàng)建的client) 

    (8) 通過aeCreateFileEvent方法注冊(cè)文件寫事件并綁定sendReplyToClient方法

     

    Step 5:server 返回寫入結(jié)果


    此時(shí)按照慣例,aeMain主函數(shù)循環(huán),監(jiān)測(cè)到新注冊(cè)的事件,調(diào)用sendReplyToClient方法。sendReplyToClient方法主要包含兩個(gè)操作:

    (1) 將outbuf內(nèi)容寫入到套接字描述符并傳輸?shù)娇蛻舳耍饕椒ㄈ缦拢?/span> 

    nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen); 

    (2) aeDeleteFileEvent 用于刪除 文件寫事件

     

    Step 6:Client收到返回結(jié)果


    客戶端接收到服務(wù)器端的返回調(diào)用redisBufferRead方法,該方法主要用于從socket中讀取數(shù)據(jù)。主要方法如下: 

    nread = read(c->fd,buf,sizeof(buf)); 

    并且將讀取的數(shù)據(jù)交由redisReaderFeed方法,該方法主要用于將數(shù)據(jù)交給回復(fù)解析器處理,也就是cliFormatReplyRaw,該方法將回復(fù)內(nèi)容格式化。最終通過

    fwrite(out,sdslen(out),1,stdout);

    方法返回給客戶端并打印展示給用戶。  

    至此整個(gè)寫入流程完成。以上還有很多細(xì)節(jié)沒有說(shuō)到,感興趣的朋友可以自行閱讀源碼。


    結(jié)語(yǔ)


    在深入了解一個(gè)DB的時(shí)候,我的第一步就是去理解它執(zhí)行一條命令執(zhí)行的整個(gè)流程,這樣就能對(duì)它整個(gè)運(yùn)行流程較為熟悉,接著我們可以去深入各個(gè)細(xì)節(jié)的部分,比如Redis的相關(guān)數(shù)據(jù)結(jié)構(gòu)、持久化以及高可用相關(guān)的東西。寫這篇文章的初衷就是希望我們更加輕松的走好這第一步。這里還需要提醒的是,在我們進(jìn)行Redis源碼閱讀的時(shí)候最關(guān)鍵的是需要靈活的使用GDB調(diào)試工具,它能幫我們更好地去理順相關(guān)執(zhí)行步驟,從而讓我們更加容易理解其實(shí)現(xiàn)原理。


    附錄:兩個(gè)相關(guān)重要知識(shí)點(diǎn)


    A:Linux Socket 建立流程 


    linux socket建立過程如上圖所示。在Linux編程時(shí),無(wú)論是操作文件還是網(wǎng)絡(luò)操作時(shí)都是通過文件描述符來(lái)進(jìn)行讀寫的,但是他們有一點(diǎn)區(qū)別,這里我們不具體討論,我們將網(wǎng)絡(luò)操作時(shí)就稱為套接字描述符。大家可以自行用c寫一個(gè)簡(jiǎn)單的demo,這里就不詳細(xì)說(shuō)明了。

    這里列出幾個(gè)重要的方法: 

    int socket(int family,int type,int protocol);

    int connect(int sockfd,const struct sockaddr * servaddr,socklen_taddrlen);

    int bind(int sockfd,const struct sockaddr * myaddr,socklen_taddrlen);

    int listen(int sockfd,int backlog);

    int accept(int sockfd,struct sockaddr *cliaddr,socklen_t * addrlen); 

    Redis client/server 也是基于linux socket連接進(jìn)行交互,并且最終調(diào)用以上方法綁定IP,監(jiān)聽端口最終與客戶端建立連接。 

    B:epoll I/O 多路復(fù)用技術(shù)

    這里重點(diǎn)介紹一下epoll,因?yàn)?span style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;">Redis事件管理器核心實(shí)現(xiàn)基本依賴于它。首先來(lái)看epoll是什么,它能做什么? 

    epoll是在Linux 2.6內(nèi)核中引進(jìn)的,是一種強(qiáng)大的I/O多路復(fù)用技術(shù),上面我們已經(jīng)說(shuō)到在進(jìn)行網(wǎng)絡(luò)操作的時(shí)候是通過文件描述符來(lái)進(jìn)行讀寫的,那么平常我們就是一個(gè)進(jìn)程操作一個(gè)文件描述符。然而epoll可以通過一個(gè)文件描述符管理多個(gè)文件描述符,并且不阻塞I/O。這使得我們單進(jìn)程可以操作多個(gè)文件描述符,這就是Redis在高并發(fā)性能還如此強(qiáng)大的原因之一。

    下面簡(jiǎn)單介紹epoll 主要的三個(gè)方法: 

    1. int epoll_create(int size) //創(chuàng)建一個(gè)epoll句柄用于監(jiān)聽文件描述符FD,size用于告訴內(nèi)核這個(gè)監(jiān)聽的數(shù)目一共有多大。該epoll句柄創(chuàng)建后在操作系統(tǒng)層面只會(huì)占用一個(gè)fd值,但是它可以監(jiān)聽size+1 個(gè)文件描述符。

    2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)  //epoll事件注冊(cè)函數(shù)

    3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)  //等待事件的產(chǎn)生 

    Redis 的事件管理器主要是基于epoll機(jī)制,先采用 epoll_ctl方法 注冊(cè)事件,然后再使用epoll_wait方法取出已經(jīng)注冊(cè)的事件。

    我們知道Redis支持多種平臺(tái),那么Redis在這方面是如何兼容其他平臺(tái)的呢?Redis會(huì)根據(jù)操作系統(tǒng)的類型選擇對(duì)應(yīng)的IO多路復(fù)用實(shí)現(xiàn)。

    #ifdef HAVE_EVPORT

    #include "ae_evport.c"

    #else

        #ifdef HAVE_EPOLL

        #include "ae_epoll.c"

        #else

            #ifdef HAVE_KQUEUE

            #include "ae_kqueue.c"

            #else

            #include "ae_select.c"

            #endif

        #endif

    #endif

     

    ae_evport.c sun solaris

    ae_poll.c linux

    ae_select.c unix/linux epoll是select的加強(qiáng)版

    ae_kqueue BSD/Apple

    以上只是簡(jiǎn)單的介紹,大家需要詳細(xì)了解了epoll機(jī)制才能更好的理解后面的東西。 

     

    參考:

    http://redis.io/

    https://github.com/antirez/redis

    http://www.tenouk.com/Module39a.html


    注:本文版權(quán)歸InfoQ所有,煩請(qǐng)任何轉(zhuǎn)載先征得InfoQ同意,謝謝。


    冬天霧霾天,小編提醒大家出門記得帶口罩哦~~~

    posted on 2016-12-14 20:34 jinfeng_wang 閱讀(177) 評(píng)論(0)  編輯  收藏 所屬分類: 2016-REDIS
    主站蜘蛛池模板: 国产大片免费网站不卡美女| 亚洲产国偷V产偷V自拍色戒| 久久亚洲精品国产精品婷婷| 久久久久久成人毛片免费看| 久久亚洲AV永久无码精品| 日韩亚洲人成在线综合| 99热在线精品免费全部my| 亚洲精品视频观看| 玖玖在线免费视频| 亚洲开心婷婷中文字幕| EEUSS影院WWW在线观看免费| 亚洲成A∨人片天堂网无码| 亚洲hairy多毛pics大全| 丁香花在线观看免费观看| 亚洲大片免费观看| 久视频精品免费观看99| 内射干少妇亚洲69XXX| 日韩免费视频一区二区| 亚洲国产美女精品久久久久∴| 亚洲一级片免费看| 亚洲一区二区三区在线视频| 午夜成人无码福利免费视频| 亚洲AV无码成H人在线观看| 国产精品成人亚洲| 免费国产成人高清视频网站 | 亚洲中文字幕无码mv| 成人免费的性色视频| 亚洲无限乱码一二三四区| 最近2019免费中文字幕6| 久久夜色精品国产噜噜亚洲AV| 免费女人高潮流视频在线观看| 久久久亚洲精品国产| 少妇人妻偷人精品免费视频| 久久久久久a亚洲欧洲AV| 午夜不卡久久精品无码免费| 亚洲精品私拍国产福利在线| 99热精品在线免费观看| 亚洲沟沟美女亚洲沟沟| av免费不卡国产观看| 亚洲中文字幕乱码AV波多JI| 国内精品免费视频自在线|