欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis中事件驅(qū)動(dòng)模型示例詳解

 更新時(shí)間:2018年03月31日 09:02:30   作者:Zhengxin Diao  
Redis這個(gè)數(shù)據(jù)庫相信不用過多介紹了,大家應(yīng)該都知道,下面這篇文章主要給大家介紹了關(guān)于Redis中事件驅(qū)動(dòng)模型的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。

前言

Redis 是一個(gè)事件驅(qū)動(dòng)的內(nèi)存數(shù)據(jù)庫,服務(wù)器需要處理兩種類型的事件。

  • 文件事件
  • 時(shí)間事件

下面就會(huì)介紹這兩種事件的實(shí)現(xiàn)原理。

文件事件

Redis 服務(wù)器通過 socket 實(shí)現(xiàn)與客戶端(或其他redis服務(wù)器)的交互,文件事件就是服務(wù)器對(duì) socket 操作的抽象。 Redis 服務(wù)器,通過監(jiān)聽這些 socket 產(chǎn)生的文件事件并處理這些事件,實(shí)現(xiàn)對(duì)客戶端調(diào)用的響應(yīng)。

Reactor

Redis 基于 Reactor 模式開發(fā)了自己的事件處理器。

這里就先展開講一講 Reactor 模式??聪聢D:

“I/O 多路復(fù)用模塊”會(huì)監(jiān)聽多個(gè) FD ,當(dāng)這些FD產(chǎn)生,accept,read,write 或 close 的文件事件。會(huì)向“文件事件分發(fā)器(dispatcher)”傳送事件。

文件事件分發(fā)器(dispatcher)在收到事件之后,會(huì)根據(jù)事件的類型將事件分發(fā)給對(duì)應(yīng)的 handler。

我們順著圖,從上到下的逐一講解 Redis 是怎么實(shí)現(xiàn)這個(gè) Reactor 模型的。

I/O 多路復(fù)用模塊

Redis 的 I/O 多路復(fù)用模塊,其實(shí)是封裝了操作系統(tǒng)提供的 select,epoll,avport 和 kqueue 這些基礎(chǔ)函數(shù)。向上層提供了一個(gè)統(tǒng)一的接口,屏蔽了底層實(shí)現(xiàn)的細(xì)節(jié)。

一般而言 Redis 都是部署到 Linux 系統(tǒng)上,所以我們就看看使用 Redis 是怎么利用 linux 提供的 epoll 實(shí)現(xiàn)I/O 多路復(fù)用。

首先看看 epoll 提供的三個(gè)方法:

/*
 * 創(chuàng)建一個(gè)epoll的句柄,size用來告訴內(nèi)核這個(gè)監(jiān)聽的數(shù)目一共有多大
 */
int epoll_create(int size);

/*
 * 可以理解為,增刪改 fd 需要監(jiān)聽的事件
 * epfd 是 epoll_create() 創(chuàng)建的句柄。
 * op 表示 增刪改
 * epoll_event 表示需要監(jiān)聽的事件,Redis 只用到了可讀,可寫,錯(cuò)誤,掛斷 四個(gè)狀態(tài)
 */
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

/*
 * 可以理解為查詢符合條件的事件
 * epfd 是 epoll_create() 創(chuàng)建的句柄。
 * epoll_event 用來存放從內(nèi)核得到事件的集合
 * maxevents 獲取的最大事件數(shù)
 * timeout 等待超時(shí)時(shí)間
 */
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

再看 Redis 對(duì)文件事件,封裝epoll向上提供的接口:

/*
 * 事件狀態(tài)
 */
typedef struct aeApiState {

 // epoll_event 實(shí)例描述符
 int epfd;

 // 事件槽
 struct epoll_event *events;

} aeApiState;

/*
 * 創(chuàng)建一個(gè)新的 epoll 
 */
static int aeApiCreate(aeEventLoop *eventLoop)
/*
 * 調(diào)整事件槽的大小
 */
static int aeApiResize(aeEventLoop *eventLoop, int setsize)
/*
 * 釋放 epoll 實(shí)例和事件槽
 */
static void aeApiFree(aeEventLoop *eventLoop)
/*
 * 關(guān)聯(lián)給定事件到 fd
 */
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
/*
 * 從 fd 中刪除給定事件
 */
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask)
/*
 * 獲取可執(zhí)行事件
 */
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)

所以看看這個(gè)ae_peoll.c 如何對(duì) epoll 進(jìn)行封裝的:

  • aeApiCreate() 是對(duì) epoll.epoll_create() 的封裝。
  • aeApiAddEvent()和aeApiDelEvent() 是對(duì) epoll.epoll_ctl()的封裝。
  • aeApiPoll() 是對(duì) epoll_wait()的封裝。

這樣 Redis 的利用 epoll 實(shí)現(xiàn)的 I/O 復(fù)用器就比較清晰了。

再往上一層次我們需要看看 ea.c 是怎么封裝的?

首先需要關(guān)注的是事件處理器的數(shù)據(jù)結(jié)構(gòu):

typedef struct aeFileEvent {
 // 監(jiān)聽事件類型掩碼,
 // 值可以是 AE_READABLE 或 AE_WRITABLE ,
 // 或者 AE_READABLE | AE_WRITABLE
 int mask; /* one of AE_(READABLE|WRITABLE) */
 // 讀事件處理器
 aeFileProc *rfileProc;
 // 寫事件處理器
 aeFileProc *wfileProc;
 // 多路復(fù)用庫的私有數(shù)據(jù)
 void *clientData;
} aeFileEvent;

mask 就是可以理解為事件的類型。

除了使用 ae_peoll.c 提供的方法外,ae.c 還增加 “增刪查” 的幾個(gè) API。

  • 增:aeCreateFileEvent
  • 刪:aeDeleteFileEvent
  • 查: 查包括兩個(gè)維度 aeGetFileEvents 獲取某個(gè) fd 的監(jiān)聽類型和aeWait等待某個(gè)fd 直到超時(shí)或者達(dá)到某個(gè)狀態(tài)。

事件分發(fā)器(dispatcher)

Redis 的事件分發(fā)器 ae.c/aeProcessEvents 不但處理文件事件還處理時(shí)間事件,所以這里只貼與文件分發(fā)相關(guān)的出部分代碼,dispather 根據(jù) mask 調(diào)用不同的事件處理器。

//從 epoll 中獲關(guān)注的事件
numevents = aeApiPoll(eventLoop, tvp);
for (j = 0; j < numevents; j++) {
 // 從已就緒數(shù)組中獲取事件
 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 確保讀/寫事件只能執(zhí)行其中一個(gè)
  rfired = 1;
  fe->rfileProc(eventLoop,fd,fe->clientData,mask);
 }
 // 寫事件
 if (fe->mask & mask & AE_WRITABLE) {
  if (!rfired || fe->wfileProc != fe->rfileProc)
   fe->wfileProc(eventLoop,fd,fe->clientData,mask);
 }
 processed++;
}

可以看到這個(gè)分發(fā)器,根據(jù) mask 的不同將事件分別分發(fā)給了讀事件和寫事件。

文件事件處理器的類型

Redis 有大量的事件處理器類型,我們就講解處理一個(gè)簡單命令涉及到的三個(gè)處理器:

  • acceptTcpHandler 連接應(yīng)答處理器,負(fù)責(zé)處理連接相關(guān)的事件,當(dāng)有client 連接到Redis的時(shí)候們就會(huì)產(chǎn)生 AE_READABLE 事件。引發(fā)它執(zhí)行。
  • readQueryFromClinet 命令請(qǐng)求處理器,負(fù)責(zé)讀取通過 sokect 發(fā)送來的命令。
  • sendReplyToClient 命令回復(fù)處理器,當(dāng)Redis處理完命令,就會(huì)產(chǎn)生 AE_WRITEABLE 事件,將數(shù)據(jù)回復(fù)給 client。

文件事件實(shí)現(xiàn)總結(jié)

我們按照開始給出的 Reactor 模型,從上到下講解了文件事件處理器的實(shí)現(xiàn),下面將會(huì)介紹時(shí)間時(shí)間的實(shí)現(xiàn)。

時(shí)間事件

Reids 有很多操作需要在給定的時(shí)間點(diǎn)進(jìn)行處理,時(shí)間事件就是對(duì)這類定時(shí)任務(wù)的抽象。

先看時(shí)間事件的數(shù)據(jù)結(jié)構(gòu):

/* Time event structure
 *
 * 時(shí)間事件結(jié)構(gòu)
 */
typedef struct aeTimeEvent {
 // 時(shí)間事件的唯一標(biāo)識(shí)符
 long long id; /* time event identifier. */
 // 事件的到達(dá)時(shí)間
 long when_sec; /* seconds */
 long when_ms; /* milliseconds */
 // 事件處理函數(shù)
 aeTimeProc *timeProc;
 // 事件釋放函數(shù)
 aeEventFinalizerProc *finalizerProc;
 // 多路復(fù)用庫的私有數(shù)據(jù)
 void *clientData;
 // 指向下個(gè)時(shí)間事件結(jié)構(gòu),形成鏈表
 struct aeTimeEvent *next;
} aeTimeEvent;

看見 next 我們就知道這個(gè) aeTimeEvent 是一個(gè)鏈表結(jié)構(gòu)??磮D:

注意:這是一個(gè)按照id倒序排列的鏈表,并沒有按照事件順序排序。

processTimeEvent

Redis 使用這個(gè)函數(shù)處理所有的時(shí)間事件,我們整理一下執(zhí)行思路:

  • 記錄最新一次執(zhí)行這個(gè)函數(shù)的時(shí)間,用于處理系統(tǒng)時(shí)間被修改產(chǎn)生的問題。
  • 遍歷鏈表找出所有 when_sec 和 when_ms 小于現(xiàn)在時(shí)間的事件。
  • 執(zhí)行事件對(duì)應(yīng)的處理函數(shù)。
  • 檢查事件類型,如果是周期事件則刷新該事件下一次的執(zhí)行事件。
  • 否則從列表中刪除事件。

綜合調(diào)度器(aeProcessEvents)

綜合調(diào)度器是 Redis 統(tǒng)一處理所有事件的地方。我們梳理一下這個(gè)函數(shù)的簡單邏輯:

// 1. 獲取離當(dāng)前時(shí)間最近的時(shí)間事件
shortest = aeSearchNearestTimer(eventLoop);

// 2. 獲取間隔時(shí)間
timeval = shortest - nowTime;

// 如果timeval 小于 0,說明已經(jīng)有需要執(zhí)行的時(shí)間事件了。
if(timeval < 0){
 timeval = 0
}

// 3. 在 timeval 時(shí)間內(nèi),取出文件事件。
numevents = aeApiPoll(eventLoop, timeval);

// 4.根據(jù)文件事件的類型指定不同的文件處理器
if (AE_READABLE) {
 // 讀事件
 rfileProc(eventLoop,fd,fe->clientData,mask);
}
 // 寫事件
if (AE_WRITABLE) {
 wfileProc(eventLoop,fd,fe->clientData,mask);
}

以上的偽代碼就是整個(gè) Redis 事件處理器的邏輯。

我們可以再看看誰執(zhí)行了這個(gè) aeProcessEvents:

void aeMain(aeEventLoop *eventLoop) {
 eventLoop->stop = 0;
 while (!eventLoop->stop) {
  // 如果有需要在事件處理前執(zhí)行的函數(shù),那么運(yùn)行它
  if (eventLoop->beforesleep != NULL)
   eventLoop->beforesleep(eventLoop);
  // 開始處理事件
  aeProcessEvents(eventLoop, AE_ALL_EVENTS);
 }
}

然后我們?cè)倏纯词钦l調(diào)用了 eaMain:

int main(int argc, char **argv) {
 //一些配置和準(zhǔn)備
 ...
 aeMain(server.el);
 
 //結(jié)束后的回收工作
 ...
}

我們?cè)?Redis 的 main 方法中找個(gè)了它。

這個(gè)時(shí)候我們整理出的思路就是:

  • Redis 的 main() 方法執(zhí)行了一些配置和準(zhǔn)備以后就調(diào)用 eaMain() 方法。
  • eaMain() while(true) 的調(diào)用 aeProcessEvents()。

所以我們說 Redis 是一個(gè)事件驅(qū)動(dòng)的程序,期間我們發(fā)現(xiàn),Redis 沒有 fork 過任何線程。所以也可以說 Redis 是一個(gè)基于事件驅(qū)動(dòng)的單線程應(yīng)用。

總結(jié)

在后端的面試中 Redis 總是一個(gè)或多或少會(huì)問到的問題。

讀完這篇文章你也許就能回答這幾個(gè)問題:

為什么 Redis 是一個(gè)單線程應(yīng)用?
為什么 Redis 是一個(gè)單線程應(yīng)用,卻有如此高的性能?
如果你用本文提供的知識(shí)點(diǎn)回答這兩個(gè)問題,一定會(huì)在面試官心中留下一個(gè)高大的形象。

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

您可能感興趣的文章:

相關(guān)文章

  • Redis實(shí)現(xiàn)單設(shè)備登錄的場(chǎng)景分析

    Redis實(shí)現(xiàn)單設(shè)備登錄的場(chǎng)景分析

    這篇文章主要介紹了Redis實(shí)現(xiàn)單設(shè)備登錄,用戶首次登錄時(shí),將用戶信息存入Redis,key是用戶id,value是token,當(dāng)用戶在其他設(shè)備登錄時(shí),會(huì)重新生成token,這個(gè)時(shí)候原先的token已經(jīng)被覆蓋了,本文給大家提供樣例及核心代碼,感興趣的朋友參考下吧
    2022-04-04
  • Redis大key多key拆分實(shí)現(xiàn)方法解析

    Redis大key多key拆分實(shí)現(xiàn)方法解析

    這篇文章主要介紹了Redis大key多key拆分實(shí)現(xiàn)方法解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-11-11
  • Redis?SCAN命令詳解

    Redis?SCAN命令詳解

    SCAN 命令是一個(gè)基于游標(biāo)的迭代器,每次被調(diào)用之后, 都會(huì)向用戶返回一個(gè)新的游標(biāo), 用戶在下次迭代時(shí)需要使用這個(gè)新游標(biāo)作為 SCAN 命令的游標(biāo)參數(shù), 以此來延續(xù)之前的迭代過程,這篇文章給大家介紹了Redis?SCAN命令的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2022-07-07
  • 淺談一下Redis的緩存穿透、擊穿和雪崩

    淺談一下Redis的緩存穿透、擊穿和雪崩

    這篇文章主要介紹了淺談一下Redis緩存穿透、擊穿和雪崩,緩存穿透是指在使用緩存系統(tǒng)時(shí),頻繁查詢一個(gè)不存在于緩存中的數(shù)據(jù),導(dǎo)致這個(gè)查詢每次都要通過緩存層去查詢數(shù)據(jù)源,無法從緩存中獲得結(jié)果,需要的朋友可以參考下
    2023-08-08
  • Redis都做了哪些加快速度的設(shè)計(jì)

    Redis都做了哪些加快速度的設(shè)計(jì)

    這篇文章主要介紹了Redis都做了哪些加快速度的設(shè)計(jì)的相關(guān)資料,需要的朋友可以參考下
    2021-02-02
  • Redis中?HyperLogLog數(shù)據(jù)類型使用小結(jié)

    Redis中?HyperLogLog數(shù)據(jù)類型使用小結(jié)

    Redis使用HyperLogLog的主要作用是在大數(shù)據(jù)流(view,IP,城市)的情況下進(jìn)行去重計(jì)數(shù),這篇文章主要介紹了Redis中?HyperLogLog數(shù)據(jù)類型使用總結(jié),需要的朋友可以參考下
    2023-03-03
  • Redis和MySQL保證雙寫一致性的問題解析

    Redis和MySQL保證雙寫一致性的問題解析

    Redis和MySQL的雙寫一致性指的是在同時(shí)使用緩存和數(shù)據(jù)庫存儲(chǔ)數(shù)據(jù)的時(shí)候,保證Redis和MySQL中數(shù)據(jù)的一致性,那么如何才能保證他們的一致性呢,下面小編就來為大家詳細(xì)講講
    2023-11-11
  • Redis數(shù)據(jù)庫原理深入刨析

    Redis數(shù)據(jù)庫原理深入刨析

    在之前的文章我們介紹過,Redis服務(wù)器在啟動(dòng)之初,會(huì)初始化RedisServer的實(shí)例,在這個(gè)實(shí)例中存在很多重要的屬性結(jié)構(gòu),同理本篇博客中介紹的數(shù)據(jù)庫實(shí)現(xiàn)原理也會(huì)和其中的某些屬性相關(guān),我們繼續(xù)看一下吧
    2022-11-11
  • 配置Redis序列化方式不生效問題及解決

    配置Redis序列化方式不生效問題及解決

    這篇文章主要介紹了配置Redis序列化方式不生效問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • 一文弄懂Redis Stream消息隊(duì)列

    一文弄懂Redis Stream消息隊(duì)列

    本文主要介紹了一文弄懂Redis Stream消息隊(duì)列,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06

最新評(píng)論