最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺談Redis的IO多路復(fù)用

 更新時(shí)間:2026年02月06日 10:34:01   作者:kevinzeng  
本文主要介紹了淺談Redis的IO多路復(fù)用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

面試回答

創(chuàng)建epoll實(shí)例,每一個(gè)客戶端(socket)都對(duì)應(yīng)一個(gè)FD(文件描述符),將這些FD注冊(cè)到epoll中,并為FD綁定回調(diào)函數(shù),主線程通過epoll_await()獲取等待就緒事件,epoll返回就緒的FD和事件,內(nèi)核將就緒的 FD 拷貝到用戶態(tài)。redis主線程逐一處理(其實(shí)就是一個(gè)死循環(huán))。

可以講講select、poll、epoll

為什么redis 6.0 之后網(wǎng)絡(luò)IO的處理改成多線程的方式了

  • 第一個(gè)缺點(diǎn):因?yàn)橹挥幸粋€(gè)進(jìn)程,無法充分利用 多核 CPU 的性能
  • 第二個(gè)缺點(diǎn):handler對(duì)象在業(yè)務(wù)處理時(shí),整個(gè)進(jìn)程是無法處理其他連接的事件的,如果業(yè)務(wù)處理耗時(shí)比較長(zhǎng),那么就造成響應(yīng)的延遲;

什么是handler在單 Reactor 單線程模型中,Handler(事件處理器)在處理某個(gè)連接的業(yè)務(wù)邏輯時(shí),會(huì)獨(dú)占整個(gè)線程。 因?yàn)?Redis 6.0 之前的網(wǎng)絡(luò)模型是單線程的,所以當(dāng) Handler 正在處理一個(gè)耗時(shí)操作時(shí),其他連接的事件(讀、寫、accept)都無法被處理,從而導(dǎo)致整體響應(yīng)變慢。

Redis為了跨平臺(tái)兼容,將不同系統(tǒng)的多路復(fù)用接口(epoll/kqueue/select等)封裝成了統(tǒng)一的aeEventLoop(事件循環(huán))框架,核心源碼集中在ae.c/ae.h和對(duì)應(yīng)系統(tǒng)的實(shí)現(xiàn)文件(如ae_epoll.c/ae_kqueue.c)中。

下面我會(huì)從核心數(shù)據(jù)結(jié)構(gòu)、初始化流程、事件注冊(cè)、事件等待/處理四個(gè)維度,結(jié)合關(guān)鍵源碼片段拆解Redis IO多路復(fù)用的實(shí)現(xiàn)。

一、先明確核心源碼文件

Redis的IO多路復(fù)用核心代碼在以下文件(Redis 6.x/7.x版本):

  • src/ae.h:定義事件循環(huán)、事件對(duì)象的核心數(shù)據(jù)結(jié)構(gòu);
  • src/ae.c:事件循環(huán)的通用邏輯(跨平臺(tái));
  • src/ae_epoll.c:Linux下epoll的具體實(shí)現(xiàn);
  • src/ae_kqueue.c:macOS/FreeBSD下kqueue的實(shí)現(xiàn);
  • src/ae_select.c:兼容select/poll的實(shí)現(xiàn)。

二、核心數(shù)據(jù)結(jié)構(gòu)(ae.h)

Redis先定義了統(tǒng)一的抽象層,屏蔽不同多路復(fù)用器的差異,核心結(jié)構(gòu)如下:

1. 事件對(duì)象(aeFileEvent)

表示一個(gè)文件描述符(FD)對(duì)應(yīng)的監(jiān)聽事件(讀/寫):

// ae.h
typedef struct aeFileEvent {
    // 事件類型:AE_READABLE(讀事件) / AE_WRITABLE(寫事件)
    int mask; 
    // 讀事件回調(diào)函數(shù)(客戶端發(fā)命令時(shí)觸發(fā))
    aeFileProc *rfileProc;
    // 寫事件回調(diào)函數(shù)(給客戶端返回結(jié)果時(shí)觸發(fā))
    aeFileProc *wfileProc;
    // 私有數(shù)據(jù)(通常指向客戶端連接結(jié)構(gòu)client)
    void *clientData;
} aeFileEvent;

2. 就緒事件(aeFiredEvent)

存儲(chǔ)epoll返回的“就緒FD+事件”,供后續(xù)處理:

// ae.h
typedef struct aeFiredEvent {
    int fd;     // 就緒的文件描述符
    int mask;   // 就緒的事件類型(AE_READABLE/AE_WRITABLE)
} aeFiredEvent;

3. 事件循環(huán)(aeEventLoop)

整個(gè)IO多路復(fù)用的核心上下文,管理所有監(jiān)聽事件和就緒事件:

// ae.h
typedef struct aeEventLoop {
    int maxfd;                          // 當(dāng)前監(jiān)聽的最大FD
    int setsize;                        // 最大支持的FD數(shù)量(可配置)
    long long timeEventNextId;          // 時(shí)間事件ID(定時(shí)任務(wù)用)
    time_t lastTime;                    // 最后一次處理時(shí)間事件的時(shí)間
    aeFileEvent *events;                // 所有注冊(cè)的文件事件(數(shù)組,下標(biāo)=FD)
    aeFiredEvent *fired;                // 就緒的文件事件(數(shù)組)
    aeTimeEvent *timeEventHead;         // 時(shí)間事件鏈表(定時(shí)任務(wù))
    int stop;                           // 事件循環(huán)停止標(biāo)志
    void *apidata;                      // 多路復(fù)用器私有數(shù)據(jù)(如epoll的fd)
    aeBeforeSleepProc *beforesleep;     // 事件循環(huán)休眠前的回調(diào)
    aeBeforeSleepProc *aftersleep;      // 事件循環(huán)喚醒后的回調(diào)
} aeEventLoop;

三、核心流程(源碼級(jí)拆解)

以Linux下epoll實(shí)現(xiàn)為例,拆解Redis IO多路復(fù)用的完整流程:

1. 事件循環(huán)初始化(aeCreateEventLoop)

// ae.c
aeEventLoop *aeCreateEventLoop(int setsize) {
    aeEventLoop *eventLoop = zmalloc(sizeof(aeEventLoop));
    eventLoop->setsize = setsize;
    eventLoop->maxfd = -1;
    // 初始化事件數(shù)組:下標(biāo)=FD,存儲(chǔ)該FD的監(jiān)聽事件
    eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
    // 初始化就緒事件數(shù)組:存儲(chǔ)epoll返回的就緒事件
    eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
    eventLoop->stop = 0;
    eventLoop->lastTime = time(NULL);
    
    // 初始化具體的多路復(fù)用器(epoll)
    // aeApiCreate會(huì)調(diào)用epoll_create()創(chuàng)建epoll實(shí)例
    if (aeApiCreate(eventLoop) == -1) {
        zfree(eventLoop->events);
        zfree(eventLoop->fired);
        zfree(eventLoop);
        return NULL;
    }
    return eventLoop;
}

// ae_epoll.c:epoll的初始化實(shí)現(xiàn)
static int aeApiCreate(aeEventLoop *eventLoop) {
    aeApiState *state = zmalloc(sizeof(aeApiState));
    // 創(chuàng)建epoll實(shí)例,返回epoll_fd
    state->epfd = epoll_create(1024); // 1024是歷史參數(shù),現(xiàn)在無意義
    eventLoop->apidata = state;
    return (state->epfd == -1) ? -1 : 0;
}

核心邏輯

  • 創(chuàng)建事件循環(huán)對(duì)象,初始化事件數(shù)組(監(jiān)聽事件)和就緒事件數(shù)組;
  • 調(diào)用aeApiCreate創(chuàng)建epoll實(shí)例,返回的epoll_fd存儲(chǔ)在eventLoop->apidata中。

2. 注冊(cè)事件(aeCreateFileEvent)

當(dāng)Redis監(jiān)聽端口(6379)或新建客戶端連接時(shí),會(huì)注冊(cè)FD的監(jiān)聽事件:

// ae.c
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData)
{
    if (fd >= eventLoop->setsize) {
        errno = ERANGE;
        return AE_ERR;
    }
    aeFileEvent *fe = &eventLoop->events[fd];

    // 調(diào)用epoll_ctl注冊(cè)事件到epoll實(shí)例
    if (aeApiAddEvent(eventLoop, fd, mask) == -1)
        return AE_ERR;
    
    // 設(shè)置事件的回調(diào)函數(shù)和私有數(shù)據(jù)
    fe->mask |= mask;
    if (mask & AE_READABLE) fe->rfileProc = proc;
    if (mask & AE_WRITABLE) fe->wfileProc = proc;
    fe->clientData = clientData;
    if (fd > eventLoop->maxfd)
        eventLoop->maxfd = fd;
    return AE_OK;
}

// ae_epoll.c:epoll添加事件的實(shí)現(xiàn)
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
    aeApiState *state = eventLoop->apidata;
    struct epoll_event ee = {0}; // epoll_event結(jié)構(gòu)體
    // 獲取該FD已注冊(cè)的事件(避免重復(fù)注冊(cè))
    int op = eventLoop->events[fd].mask == AE_NONE ?
            EPOLL_CTL_ADD : EPOLL_CTL_MOD;

    // 轉(zhuǎn)換Redis事件掩碼為epoll事件掩碼
    ee.events = 0;
    mask |= eventLoop->events[fd].mask; // 合并已有事件
    if (mask & AE_READABLE) ee.events |= EPOLLIN;
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
    ee.data.fd = fd;

    // 調(diào)用epoll_ctl注冊(cè)/修改事件
    if (epoll_ctl(state->epfd, op, fd, &ee) == -1) return -1;
    return 0;
}

核心邏輯

  • 將FD和監(jiān)聽事件(讀/寫)注冊(cè)到epoll實(shí)例(epoll_ctl的EPOLL_CTL_ADD/EPOLL_CTL_MOD);
  • 為該FD綁定回調(diào)函數(shù)(比如讀事件回調(diào)readQueryFromClient,處理客戶端命令)。

3. 事件循環(huán)(aeMain)

Redis啟動(dòng)后,主線程進(jìn)入無限循環(huán),等待并處理就緒事件:

// ae.c
void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    // 無限循環(huán),直到stop被置1(Redis關(guān)閉)
    while (!eventLoop->stop) {
        // 休眠前的回調(diào)(比如處理定時(shí)任務(wù)、統(tǒng)計(jì))
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        // 核心:等待就緒事件,返回就緒的事件數(shù)量
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

// ae.c:處理事件的核心函數(shù)
int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
    int processed = 0, numevents;

    // 調(diào)用aeApiPoll(epoll_wait)等待就緒事件
    numevents = aeApiPoll(eventLoop, tvp);
    processed += numevents;

    // 遍歷所有就緒事件,逐個(gè)處理
    for (int j = 0; j < numevents; j++) {
        aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
        int mask = eventLoop->fired[j].mask;
        int fd = eventLoop->fired[j].fd;
        int rfired = 0;

        // 處理讀事件
        if (fe->mask & mask & AE_READABLE) {
            rfired = 1;
            // 調(diào)用讀事件回調(diào)(比如readQueryFromClient)
            fe->rfileProc(eventLoop, fd, fe->clientData, mask);
        }
        // 處理寫事件
        if (fe->mask & mask & AE_WRITABLE) {
            if (!rfired || fe->wfileProc != fe->rfileProc) {
                // 調(diào)用寫事件回調(diào)(比如sendReplyToClient)
                fe->wfileProc(eventLoop, fd, fe->clientData, mask);
            }
        }
        processed++;
    }
    // 處理時(shí)間事件(定時(shí)任務(wù),如過期鍵清理)
    ...
    return processed;
}

// ae_epoll.c:epoll_wait等待就緒事件
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    aeApiState *state = eventLoop->apidata;
    int retval;
    // 調(diào)用epoll_wait,阻塞等待就緒事件,結(jié)果存入eventLoop->fired
    retval = epoll_wait(state->epfd, eventLoop->fired, eventLoop->setsize,
            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
    return retval;
}

核心邏輯

  1. 主線程調(diào)用epoll_wait阻塞等待就緒事件(無事件時(shí)休眠,消耗極低);

  2. epoll返回就緒的FD和事件,存入eventLoop->fired數(shù)組;

  3. 遍歷就緒事件數(shù)組,根據(jù)事件類型(讀/寫)調(diào)用對(duì)應(yīng)的回調(diào)函數(shù):

    1. 讀事件:readQueryFromClient(讀取客戶端命令,解析并執(zhí)行);
    2. 寫事件:sendReplyToClient(將命令執(zhí)行結(jié)果返回給客戶端);
  4. 循環(huán)往復(fù),直到Redis停止。

4. 事件刪除(aeDeleteFileEvent)

當(dāng)客戶端斷開連接時(shí),Redis會(huì)刪除該FD的監(jiān)聽事件:

// ae.c
void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask) {
    if (fd >= eventLoop->setsize) return;
    aeFileEvent *fe = &eventLoop->events[fd];
    if (fe->mask == AE_NONE) return;

    // 從epoll中刪除事件
    aeApiDelEvent(eventLoop, fd, mask);
    // 更新事件掩碼
    fe->mask = fe->mask & (~mask);
    if (fd == eventLoop->maxfd && fe->mask == AE_NONE) {
        // 更新maxfd,優(yōu)化后續(xù)遍歷
        while (eventLoop->maxfd >= 0 && eventLoop->events[eventLoop->maxfd].mask == AE_NONE)
            eventLoop->maxfd--;
    }
}

// ae_epoll.c
static int aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) {
    aeApiState *state = eventLoop->apidata;
    struct epoll_event ee = {0};
    int mask = eventLoop->events[fd].mask & (~delmask);

    ee.events = 0;
    if (mask & AE_READABLE) ee.events |= EPOLLIN;
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
    ee.data.fd = fd;
    int op = mask == AE_NONE ? EPOLL_CTL_DEL : EPOLL_CTL_MOD;
    // 調(diào)用epoll_ctl刪除/修改事件
    if (epoll_ctl(state->epfd, op, fd, &ee) == -1) return -1;
    return 0;
}

四、核心設(shè)計(jì)亮點(diǎn)(源碼層面)

  1. 抽象層封裝:通過aeApiCreate/aeApiAddEvent等接口,將epoll/kqueue/select的差異封裝,實(shí)現(xiàn)跨平臺(tái)兼容;
  2. 數(shù)組映射FDeventLoop->events數(shù)組下標(biāo)直接對(duì)應(yīng)FD,無需遍歷查找,通過FD能O(1)找到對(duì)應(yīng)的事件對(duì)象;
  3. 非阻塞IO:Redis在讀寫FD時(shí)均設(shè)置為非阻塞模式(fcntl(fd, F_SETFL, O_NONBLOCK)),即使epoll通知就緒,讀寫也不會(huì)阻塞;
  4. 事件復(fù)用:讀/寫事件可獨(dú)立注冊(cè)/刪除,比如執(zhí)行完命令后注冊(cè)寫事件,返回結(jié)果后刪除寫事件,避免無效觸發(fā)。

總結(jié)

  1. Redis IO多路復(fù)用的核心是aeEventLoop事件循環(huán)框架,封裝了epoll等底層多路復(fù)用接口,對(duì)外提供統(tǒng)一的事件注冊(cè)/處理能力;
  2. 源碼層面的核心流程:初始化事件循環(huán)→注冊(cè)FD事件→epoll_wait等待就緒→遍歷就緒事件→調(diào)用回調(diào)處理→循環(huán)往復(fù);
  3. 關(guān)鍵設(shè)計(jì):FD與事件數(shù)組下標(biāo)映射(O(1)查找)、跨平臺(tái)抽象層、非阻塞IO,保證了單線程處理海量連接的高效性。

這些源碼邏輯也是Redis單線程能支撐10萬+ QPS的核心——所有開銷都集中在“處理就緒事件”,無線程切換、無鎖競(jìng)爭(zhēng),且內(nèi)存操作極快。

到此這篇關(guān)于淺談Redis的IO多路復(fù)用的文章就介紹到這了,更多相關(guān)Redis IO多路復(fù)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 關(guān)于Redis?bigkeys命令會(huì)阻塞問題的解決

    關(guān)于Redis?bigkeys命令會(huì)阻塞問題的解決

    這篇文章主要介紹了關(guān)于Redis?bigkeys命令會(huì)阻塞問題的解決,今天分享一次Redis引發(fā)的線上事故,避免再次踩雷,實(shí)現(xiàn)快速入門,需要的朋友可以參考下
    2023-03-03
  • Redis處理高并發(fā)之布隆過濾器詳解

    Redis處理高并發(fā)之布隆過濾器詳解

    這篇文章主要為大家介紹了Redis處理高并發(fā)之布隆過濾器詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • Redis中群集三種模式的實(shí)現(xiàn)

    Redis中群集三種模式的實(shí)現(xiàn)

    Redis群集有三種模式,分別是主從同步/復(fù)制、哨兵模式、Cluster,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-11-11
  • 使用Redis實(shí)現(xiàn)微信步數(shù)排行榜功能

    使用Redis實(shí)現(xiàn)微信步數(shù)排行榜功能

    這篇文章主要介紹了使用Redis實(shí)現(xiàn)微信步數(shù)排行榜功能,本文通過圖文實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-06-06
  • Redis主從復(fù)制分步講解使用

    Redis主從復(fù)制分步講解使用

    Redis因?yàn)槠涓咝阅芎鸵子眯栽谖覀兒蠖说姆?wù)中發(fā)揮了巨大的作用,并且很多重要功能的實(shí)現(xiàn)都會(huì)依賴redis,本篇我們來了解Redis高可用主從復(fù)制,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2022-09-09
  • Linux下redis的安裝與使用圖文教程

    Linux下redis的安裝與使用圖文教程

    這篇文章主要介紹了Linux下redis的安裝與使用,結(jié)合圖文形式分析了Linux環(huán)境下redis的下載、編譯、安裝、部署、訪問等相關(guān)操作技巧,需要的朋友可以參考下
    2019-08-08
  • 詳解Redis緩存與Mysql如何保證雙寫一致

    詳解Redis緩存與Mysql如何保證雙寫一致

    緩存和數(shù)據(jù)庫如何保證數(shù)據(jù)的一致是個(gè)很經(jīng)典的問題,這篇文章就來和大家一起探討一下Redis緩存與Mysql如何保證雙寫一致,感興趣的小伙伴可以參考下
    2023-12-12
  • Redis如何從海量key中查詢出某一固定前綴的key

    Redis如何從海量key中查詢出某一固定前綴的key

    當(dāng)Redis存儲(chǔ)一億key時(shí),使用keys指令可能因返回全部key導(dǎo)致服務(wù)器卡頓,而scan指令通過游標(biāo)分批獲取,避免阻塞,適合生產(chǎn)環(huán)境,需注意重復(fù)結(jié)果可用hashSet去重,count參數(shù)可調(diào)整返回?cái)?shù)量但非強(qiáng)制
    2025-07-07
  • Linux環(huán)境搭建Redis6.x集群環(huán)境詳解(主從復(fù)制)

    Linux環(huán)境搭建Redis6.x集群環(huán)境詳解(主從復(fù)制)

    本文詳細(xì)介紹了如何搭建和管理一個(gè)Redis集群,包括主從復(fù)制、集群配置、批量腳本、集群?jiǎn)?dòng)和關(guān)閉、節(jié)點(diǎn)的增刪、投票機(jī)制以及主節(jié)點(diǎn)宕機(jī)演示,通過這些步驟,可以實(shí)現(xiàn)Redis集群的高可用性和性能擴(kuò)展
    2026-03-03
  • Redis集群的關(guān)閉與重啟操作

    Redis集群的關(guān)閉與重啟操作

    這篇文章主要介紹了Redis集群的關(guān)閉與重啟操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07

最新評(píng)論

桦南县| 宣恩县| 普宁市| 交口县| 沙湾县| 咸丰县| 南开区| 米泉市| 忻城县| 渑池县| 平果县| 金湖县| 峨山| 吉木萨尔县| 黔西县| 蕉岭县| 浮山县| 石嘴山市| 蒲江县| 姜堰市| 名山县| 梁山县| 浦江县| 合江县| 东乌| 南昌县| 普洱| 辉县市| 大余县| 文昌市| 英吉沙县| 浙江省| 高密市| 陆川县| 云南省| 浙江省| 连城县| 邵武市| 从江县| 锦屏县| 阳西县|