日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis 和 I/O 多路复用

發布時間:2025/3/20 数据库 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis 和 I/O 多路复用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最近在看 UNIX 網絡編程并研究了一下 Redis 的實現,感覺 Redis 的源代碼十分適合閱讀和分析,其中 I/O 多路復用(mutiplexing)部分的實現非常干凈和優雅,在這里想對這部分的內容進行簡單的整理。

幾種 I/O 模型

為什么 Redis 中要使用 I/O 多路復用這種技術呢?

首先,Redis 是跑在單線程中的,所有的操作都是按照順序線性執行的,但是由于讀寫操作等待用戶輸入或輸出都是阻塞的,所以 I/O 操作在一般情況下往往不能直接返回,這會導致某一文件的 I/O 阻塞導致整個進程無法對其它客戶提供服務,而?I/O 多路復用就是為了解決這個問題而出現的。

Blocking I/O

先來看一下傳統的阻塞 I/O 模型到底是如何工作的:當使用?read?或者?write?對某一個文件描述符(File Descriptor 以下簡稱 FD)進行讀寫時,如果當前 FD 不可讀或不可寫,整個 Redis 服務就不會對其它的操作作出響應,導致整個服務不可用。

這也就是傳統意義上的,也就是我們在編程中使用最多的阻塞模型:

阻塞模型雖然開發中非常常見也非常易于理解,但是由于它會影響其他 FD 對應的服務,所以在需要處理多個客戶端任務的時候,往往都不會使用阻塞模型。

I/O 多路復用

雖然還有很多其它的 I/O 模型,但是在這里都不會具體介紹。

阻塞式的 I/O 模型并不能滿足這里的需求,我們需要一種效率更高的 I/O 模型來支撐 Redis 的多個客戶(redis-cli),這里涉及的就是 I/O 多路復用模型了:

在 I/O 多路復用模型中,最重要的函數調用就是?select,該方法的能夠同時監控多個文件描述符的可讀可寫情況,當其中的某些文件描述符可讀或者可寫時,select?方法就會返回可讀以及可寫的文件描述符個數。

關于?select?的具體使用方法,在網絡上資料很多,這里就不過多展開介紹了;

與此同時也有其它的 I/O 多路復用函數?epoll/kqueue/evport,它們相比?select?性能更優秀,同時也能支撐更多的服務。

Reactor 設計模式

Redis 服務采用 Reactor 的方式來實現文件事件處理器(每一個網絡連接其實都對應一個文件描述符)

文件事件處理器使用 I/O 多路復用模塊同時監聽多個 FD,當?accept、read、write?和?close?文件事件產生時,文件事件處理器就會回調 FD 綁定的事件處理器。

雖然整個文件事件處理器是在單線程上運行的,但是通過 I/O 多路復用模塊的引入,實現了同時對多個 FD 讀寫的監控,提高了網絡通信模型的性能,同時也可以保證整個 Redis 服務實現的簡單。

I/O 多路復用模塊

I/O 多路復用模塊封裝了底層的?select、epoll、avport?以及?kqueue?這些 I/O 多路復用函數,為上層提供了相同的接口。

在這里我們簡單介紹 Redis 是如何包裝?select?和?epoll?的,簡要了解該模塊的功能,整個 I/O 多路復用模塊抹平了不同平臺上 I/O 多路復用函數的差異性,提供了相同的接口:

  • static int aeApiCreate(aeEventLoop *eventLoop)
  • static int aeApiResize(aeEventLoop *eventLoop, int setsize)
  • static void aeApiFree(aeEventLoop *eventLoop)
  • static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
  • static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask)
  • static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)

同時,因為各個函數所需要的參數不同,我們在每一個子模塊內部通過一個?aeApiState?來存儲需要的上下文信息:

// select typedef struct aeApiState {fd_set rfds, wfds;fd_set _rfds, _wfds; } aeApiState;// epoll typedef struct aeApiState {int epfd;struct epoll_event *events; } aeApiState;

這些上下文信息會存儲在?eventLoop?的?void *state?中,不會暴露到上層,只在當前子模塊中使用。

封裝 select 函數

select?可以監控 FD 的可讀、可寫以及出現錯誤的情況。

在介紹 I/O 多路復用模塊如何對?select?函數封裝之前,先來看一下?select?函數使用的大致流程:

int fd = /* file descriptor */fd_set rfds; FD_ZERO(&rfds); FD_SET(fd, &rfds)for ( ; ; ) {select(fd+1, &rfds, NULL, NULL, NULL);if (FD_ISSET(fd, &rfds)) {/* file descriptor `fd` becomes readable */} }
  • 初始化一個可讀的?fd_set?集合,保存需要監控可讀性的 FD;
  • 使用?FD_SET?將?fd?加入?rfds;
  • 調用?select?方法監控?rfds?中的 FD 是否可讀;
  • 當?select?返回時,檢查 FD 的狀態并完成對應的操作。
  • 而在 Redis 的?ae_select?文件中代碼的組織順序也是差不多的,首先在?aeApiCreate?函數中初始化?rfds?和?wfds:

    static int aeApiCreate(aeEventLoop *eventLoop) {aeApiState *state = zmalloc(sizeof(aeApiState));if (!state) return -1;FD_ZERO(&state->rfds);FD_ZERO(&state->wfds);eventLoop->apidata = state;return 0; }

    而?aeApiAddEvent?和?aeApiDelEvent?會通過?FD_SET?和?FD_CLR?修改?fd_set?中對應 FD 的標志位:

    static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {aeApiState *state = eventLoop->apidata;if (mask & AE_READABLE) FD_SET(fd,&state->rfds);if (mask & AE_WRITABLE) FD_SET(fd,&state->wfds);return 0; }

    整個?ae_select?子模塊中最重要的函數就是?aeApiPoll,它是實際調用?select?函數的部分,其作用就是在 I/O 多路復用函數返回時,將對應的 FD 加入?aeEventLoop?的?fired?數組中,并返回事件的個數:

    static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {aeApiState *state = eventLoop->apidata;int retval, j, numevents = 0;memcpy(&state->_rfds,&state->rfds,sizeof(fd_set));memcpy(&state->_wfds,&state->wfds,sizeof(fd_set));retval = select(eventLoop->maxfd+1,&state->_rfds,&state->_wfds,NULL,tvp);if (retval > 0) {for (j = 0; j <= eventLoop->maxfd; j++) {int mask = 0;aeFileEvent *fe = &eventLoop->events[j];if (fe->mask == AE_NONE) continue;if (fe->mask & AE_READABLE && FD_ISSET(j,&state->_rfds))mask |= AE_READABLE;if (fe->mask & AE_WRITABLE && FD_ISSET(j,&state->_wfds))mask |= AE_WRITABLE;eventLoop->fired[numevents].fd = j;eventLoop->fired[numevents].mask = mask;numevents++;}}return numevents; }

    封裝 epoll 函數

    Redis 對?epoll?的封裝其實也是類似的,使用?epoll_create?創建?epoll?中使用的?epfd:

    static int aeApiCreate(aeEventLoop *eventLoop) {aeApiState *state = zmalloc(sizeof(aeApiState));if (!state) return -1;state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize);if (!state->events) {zfree(state);return -1;}state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */if (state->epfd == -1) {zfree(state->events);zfree(state);return -1;}eventLoop->apidata = state;return 0; }

    在?aeApiAddEvent?中使用?epoll_ctl?向?epfd?中添加需要監控的 FD 以及監聽的事件:

    static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {aeApiState *state = eventLoop->apidata;struct epoll_event ee = {0}; /* avoid valgrind warning *//* If the fd was already monitored for some event, we need a MOD* operation. Otherwise we need an ADD operation. */int op = eventLoop->events[fd].mask == AE_NONE ?EPOLL_CTL_ADD : EPOLL_CTL_MOD;ee.events = 0;mask |= eventLoop->events[fd].mask; /* Merge old events */if (mask & AE_READABLE) ee.events |= EPOLLIN;if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;ee.data.fd = fd;if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;return 0; }

    由于?epoll?相比?select?機制略有不同,在?epoll_wait?函數返回時并不需要遍歷所有的 FD 查看讀寫情況;在?epoll_wait?函數返回時會提供一個?epoll_event?數組:

    typedef union epoll_data {void *ptr;int fd; /* 文件描述符 */uint32_t u32;uint64_t u64; } epoll_data_t;struct epoll_event {uint32_t events; /* Epoll 事件 */epoll_data_t data; };

    其中保存了發生的?epoll?事件(EPOLLIN、EPOLLOUT、EPOLLERR?和?EPOLLHUP)以及發生該事件的 FD。

    aeApiPoll?函數只需要將?epoll_event?數組中存儲的信息加入?eventLoop?的?fired?數組中,將信息傳遞給上層模塊:

    static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {aeApiState *state = eventLoop->apidata;int retval, numevents = 0;retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);if (retval > 0) {int j;numevents = retval;for (j = 0; j < numevents; j++) {int mask = 0;struct epoll_event *e = state->events+j;if (e->events & EPOLLIN) mask |= AE_READABLE;if (e->events & EPOLLOUT) mask |= AE_WRITABLE;if (e->events & EPOLLERR) mask |= AE_WRITABLE;if (e->events & EPOLLHUP) mask |= AE_WRITABLE;eventLoop->fired[j].fd = e->data.fd;eventLoop->fired[j].mask = mask;}}return numevents; }

    子模塊的選擇

    因為 Redis 需要在多個平臺上運行,同時為了最大化執行的效率與性能,所以會根據編譯平臺的不同選擇不同的 I/O 多路復用函數作為子模塊,提供給上層統一的接口;在 Redis 中,我們通過宏定義的使用,合理的選擇不同的子模塊:

    #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

    因為?select?函數是作為 POSIX 標準中的系統調用,在不同版本的操作系統上都會實現,所以將其作為保底方案:

    Redis 會優先選擇時間復雜度為 $O(1)$ 的 I/O 多路復用函數作為底層實現,包括 Solaries 10 中的?evport、Linux 中的?epoll?和 macOS/FreeBSD 中的?kqueue,上述的這些函數都使用了內核內部的結構,并且能夠服務幾十萬的文件描述符。

    但是如果當前編譯環境沒有上述函數,就會選擇?select?作為備選方案,由于其在使用時會掃描全部監聽的描述符,所以其時間復雜度較差 $O(n)$,并且只能同時服務 1024 個文件描述符,所以一般并不會以?select?作為第一方案使用。

    總結

    Redis 對于 I/O 多路復用模塊的設計非常簡潔,通過宏保證了 I/O 多路復用模塊在不同平臺上都有著優異的性能,將不同的 I/O 多路復用函數封裝成相同的 API 提供給上層使用。

    整個模塊使 Redis 能以單進程運行的同時服務成千上萬個文件描述符,避免了由于多進程應用的引入導致代碼實現復雜度的提升,減少了出錯的可能性。

    Reference

    • Select-Man-Pages
    • Reactor-Pattern
    • epoll vs kqueue

    https://draveness.me/redis-io-multiplexing

    總結

    以上是生活随笔為你收集整理的Redis 和 I/O 多路复用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 日韩精品一二三四区 | 欧美激情片在线观看 | 一级片aaa | 福利在线一区 | 精品熟妇无码av免费久久 | 一二区在线视频 | 狠狠干香蕉 | 风间由美一区二区三区 | 中文字幕第五页 | 国产精品啊啊啊 | 亚洲av永久无码精品一区二区国产 | 97影音| 午夜影院在线观看视频 | 99xav| 国内精品久 | 色干综合 | ww黄色| 日韩美女爱爱 | 日韩深夜福利 | 三级黄色在线播放 | 国产aa毛片| 亚洲国产成人在线视频 | 成人免费毛片嘿嘿连载 | 国产人妻精品午夜福利免费 | 毛片基地免费观看 | 国产高清自拍av | 色欲狠狠躁天天躁无码中文字幕 | 亚洲三级图片 | av在线资源| 五十路六十路 | 日韩精品一区二区三区色欲av | 欧美精品日韩少妇 | 尤物一区二区 | 日韩精品欧美 | 97超碰人人网 | 波多野结衣女同 | 吃奶摸下的激烈视频 | 中文毛片 | 亚洲AV无码国产成人久久 | 天堂va欧美va亚洲va老司机 | 中文字幕被公侵犯的漂亮人妻 | 亚洲色图 校园春色 | 91成人在线观看国产 | 激情九月天 | 欧美乱妇狂野欧美视频 | 不卡国产视频 | 久久新| 日本视频网 | 梦梦电影免费高清在线观看 | 艳妇乳肉亭妇荡乳av | 九九热免费在线 | 精品国产乱码久久久久 | 国产日本欧美在线观看 | 免费污视频 | 一区不卡在线 | 奇米一区 | 探花视频在线观看 | xxxx18日本| 黄色国产在线播放 | av资源在线播放 | √资源天堂中文在线视频 | 无码人妻精品一区二区蜜桃视频 | 天天插天天狠天天透 | 香蕉视频免费在线 | 久久精品国产亚洲av嫖农村妇女 | 国产免费av一区二区三区 | 绿帽人妻精品一区二区 | 精品久久久国产 | 国产污视频网站 | 青青草国产在线视频 | 欧美黑人一区二区 | 狠狠干狠狠艹 | 成人免费毛片足控 | 国内少妇毛片视频 | 人与嘼交av免费 | 日韩欧美福利 | 岛国成人在线 | 亚洲人xxx日本人18 | 色婷婷久久久 | 久久伊人精品视频 | 黄色三级小视频 | 伊伊成人网| 国产欧美精品区一区二区三区 | 91干网| 天堂网亚洲 | 久久国产精品二区 | 精品乱子伦一区二区三区 | 午夜成人鲁丝片午夜精品 | 欧美综合网站 | 久久久久久久人妻无码中文字幕爆 | 日韩一级色片 | 国产真人真事毛片 | 国模婷婷| 国产精品一区二区在线播放 | 色综合色综合网色综合 | 欧美激情性生活 | 日韩精品一卡二卡 | 黑人导航 | 色中色综合|