redis事件機制
redis事件機制
redis並沒有採用libevent庫作為事件機制的底層實現,而是自己對io多路複用進行了封裝,即可以採用select、epoll、evport、kqueue作為底層的實現。redis客戶端與服務端進行通訊時,redis提供了命令事件(也就是檔案事件)。另外redis還提供了定時事件,用於對系統實時性要求進行處理,以及處理使用者的業務需求。
先來看下redis事件機制的整體結構, 接下來的每個小結都圍繞這個結構進行分析。
一、建立/監聽socket
redis服務端在初始化函式initServer中會進行建立socket、繫結socket的操作。將服務端的指定ip或者所有ip都設定為可以監聽來自客戶端的連線請求。
//建立套接字、繫結套接字
int listenToPort(int port, int *fds, int *count);
fds用於儲存所有繫結的socket, 例如:"假設服務端有兩個ip:192.168.100.10 , 192.168.100.11,如果對這兩個ip都進行繫結,則fds中將儲存繫結後的兩個套接字fd"
count 用於儲存具體有多少個套接字進行了繫結
建立並繫結socket後、服務端可以在這些socket上監聽來自客戶端的連線請求。
二、建立事件管理器
redis服務端在初始化函式initServer中,會建立一個事件管理器物件,用於管理命令事件、時間事件。函式aeCreateEventLoop將建立一個事件管理器,setsize引數指的是socket描述符的個數,服務端初始化時將該引數設為最大socket個數。函式將返回一個事件管理器物件。
- aeEventLoop *aeCreateEventLoop(int setsize)
- {
- aeEventLoop *eventLoop;
- int i;
- // 建立事件狀態結構
- if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL)
- goto err;
- <span style="color:#ff0000;">// 建立未就緒事件表、就緒事件表</span>
- eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
- eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
- if (eventLoop->events == NULL || eventLoop->fired == NULL)
- goto err;
- // 設定陣列大小
- eventLoop->setsize = setsize;
- // 初始化執行最近一次執行時間
- eventLoop->lastTime = time(NULL);
- // 初始化時間事件結構
- eventLoop->timeEventHead = NULL;
- eventLoop->timeEventNextId = 0;
- <span style="color:#ff0000;"> //將多路複用io與事件管理器關聯起來</span>
- if (aeApiCreate(eventLoop) == -1)
- goto err;
- // 初始化監聽事件
- for (i = 0; i < setsize; i++)
- eventLoop->events[i].mask = AE_NONE;
- // 返回事件迴圈
- return eventLoop;
- err:
- ...
- }
1、首先建立一個aeEventLoop物件
2、建立一個未就緒事件表、就緒事件表。events指標指向未就緒事件表、fired指標指向就緒事件表。表的內容在後面新增具體事件時進行初始化。
3、呼叫aeApiCreate建立一個epoll例項
- static int aeApiCreate(aeEventLoop *eventLoop)
- {
- aeApiState *state = zmalloc(sizeof(aeApiState));
- // 初始化epoll就緒事件表
- state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize);
- // 建立 epoll 例項
- state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
- // 事件管理器與epoll關聯
- eventLoop->apidata = state;
- return 0;
- }
- typedef struct aeApiState
- {
- // epoll_event 例項描述符
- int epfd;
- <span style="color:#ff0000;">// 儲存epoll就緒事件表</span>
- struct epoll_event *events;
- } aeApiState;
物件中epfd儲存epoll的標識,events是一個就緒事件陣列,當有事件發生時,所有發生的事件都將儲存在這個陣列中。這個就緒事件陣列由應用層開闢空間、核心負責把所有發生的事件填充到該陣列。
三、建立命令事件(也就是檔案事件)
建立來事件管理器後、接下來就可以把具體的事件插入到事件管理器中。
- typedef struct aeFileEvent
- {
- // 監聽事件型別掩碼,// 值可以是 AE_READABLE 或 AE_WRITABLE ,
- // 或者 AE_READABLE | AE_WRITABLE
- int mask;
- // 讀事件處理器
- aeFileProc *rfileProc;
- // 寫事件處理器
- aeFileProc *wfileProc;
- // 多路複用庫的私有資料
- void *clientData;
- } aeFileEvent;
- int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,aeFileProc *proc, void *clientData)
- {
- // 取出檔案事件結構
- aeFileEvent *fe = &eventLoop->events[fd];
- // 監聽指定 fd 的指定事件
- if (aeApiAddEvent(eventLoop, fd, mask) == -1)
- return AE_ERR;
- // 設定檔案事件型別,以及事件的處理器
- fe->mask |= mask;
- if (mask & AE_READABLE)
- fe->rfileProc = proc;
- if (mask & AE_WRITABLE)
- fe->wfileProc = proc;
- // 私有資料
- fe->clientData = clientData;
- return AE_OK;
- }
根據fd為索引在未就緒表中找到相應的元素,該陣列元素就被fd佔用。這個過程就相當於把fd插入到未就緒事件表中。接下來填充事件的回撥、引數、事件型別等引數。
除了將事件插入到未就緒表中外,還需要把fd對應的事件插入到具體的io複用中,本例為epoll。
- static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
- {
- aeApiState *state = eventLoop->apidata;
- struct epoll_event ee;
- //如果 fd 沒有關聯任何事件,那麼這是一個 ADD 操作。如果已經關聯了某個/某些事件,那麼這是一個 MOD 操作。
- int op = eventLoop->events[fd].mask == AE_NONE ?
- EPOLL_CTL_ADD : EPOLL_CTL_MOD;
- // 註冊事件到 epoll
- 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.u64 = 0; /* avoid valgrind warning */
- ee.data.fd = fd;
- //將事件加入epoll中
- if (epoll_ctl(state->epfd,op,fd,&ee) == -1)
- return -1;
- return 0;
- }
因此建立一個事件時、將會發生下面3個操作
1、建立事件並給事件賦值
2、將事件插入到未就緒事件表
3、將事件插入到epoll維護的紅黑樹中。
四、事件迴圈
程式將使用一個while死迴圈,一直維持著服務端的運轉。
- //事件處理器的主迴圈
- void aeMain(aeEventLoop *eventLoop)
- {
- eventLoop->stop = 0;
- while (!eventLoop->stop)
- {
- // 如果有需要在事件處理前執行的函式,那麼執行它
- if (eventLoop->beforesleep != NULL)
- eventLoop->beforesleep(eventLoop);
- // 開始處理事件
- aeProcessEvents(eventLoop, AE_ALL_EVENTS);
- }
- }
- // 處理檔案事件,阻塞時間由 tvp 決定
- numevents = aeApiPoll(eventLoop, tvp);
- for (j = 0; j < numevents; j++)
- {
- // 從已就緒陣列中獲取事件
- <span style="white-space:pre"> </span>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 確保讀/寫事件只能執行其中一個
- 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);
- }
- }
遍歷所有已經發生的事件,根據fd在未就緒表中找到相應的事件,然後呼叫事件處理函式。
當一輪事件執行完後,程式又進入最外層的事件迴圈中,接著處理剩於的事件。
aeApiPoll內部呼叫epoll系統呼叫,等待指定事件發生或者超時。如果有事件發生則eploo_wait返回非0,如果為超時,則返回0。
當有事件發生時,核心會把所有發生的事件由核心層拷貝到應用層。eploo_wait返回時state->events就是儲存是由核心返回的就緒表。
- 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;
- // 為已就緒事件設定相應的模式
- // 並加入到 eventLoop 的 fired 陣列中
- numevents = retval;
- for (j = 0; j < numevents; j++)
- {
- int mask = 0;
- struct epoll_event *e = <span style="color:#ff0000;">state->events</span>+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;
- }
五、刪除事件
當不在需要某個事件時,需要把事件刪除掉。例如: 如果fd同時監聽讀事件、寫事件。當不在需要監聽寫事件時,可以把該fd的寫事件刪除。
- void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
- {
- // 取出檔案事件結構
- aeFileEvent *fe = &eventLoop->events[fd];
- // 未設定監聽的事件型別,直接返回
- if (fe->mask == AE_NONE)
- return;
- // 計算新掩碼
- fe->mask = fe->mask & (~mask);
- // 取消對給定 fd 的給定事件的監視
- aeApiDelEvent(eventLoop, fd, mask);
- }
- static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask)
- {
- aeApiState *state = eventLoop->apidata;
- struct epoll_event ee;
- 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.u64 = 0; /* avoid valgrind warning */
- ee.data.fd = fd;
- if (mask != AE_NONE)
- {
- epoll_ctl(state->epfd,EPOLL_CTL_MOD,fd,&ee);
- }
- else
- {
- epoll_ctl(state->epfd,EPOLL_CTL_DEL,fd,&ee);
- }
- }
1、根據fd在未就緒表中查詢到事件
2、取消該fd對應的相應事件識別符號
3、通知核心,核心會將紅黑樹上的相應事件也給取消。
相關文章
- Redis的事件機制Redis事件
- Redis 事件機制詳解Redis事件
- redis的事件處理機制Redis事件
- 淺談JS事件機制與React事件機制JS事件React
- DOM事件機制事件
- react事件機制React事件
- Redis使用IO多路複用進行事件處理機制Redis事件
- Redis分片機制Redis
- redis哨兵機制Redis
- Redis 哨兵機制Redis
- View事件機制分析View事件
- JS的事件物件與事件機制JS事件物件
- JavaScript執行緒機制與事件機制JavaScript執行緒事件
- Android事件分發機制Android事件
- JS 事件機制 Event LoopJS事件OOP
- Qt 事件機制 學習QT事件
- JavaScript 事件迴圈機制JavaScript事件
- Spring事件機制詳解Spring事件
- 【React深入】React事件機制React事件
- JavaScript事件迴圈機制JavaScript事件
- redis持久化機制Redis持久化
- 聊聊Redis sentinel 機制Redis
- redis 內部機制Redis
- jQuery的事件機制,事件物件介紹,外掛機制,多庫共存,each()jQuery事件物件
- Android10_原理機制系列_事件傳遞機制Android事件
- View事件分發機制分析View事件
- javascript事件迴圈機制EventLoopJavaScript事件OOP
- javascript之事件迴圈機制JavaScript事件
- 深入理解DOM事件機制事件
- Java——事件處理機制概要Java事件
- cocos EventDispatcher事件分發機制事件
- 18.spring系列- 事件機制Spring事件
- [譯]談談SpringBoot 事件機制Spring Boot事件
- React原始碼分析 – 事件機制React原始碼事件
- js--事件迴圈機制JS事件
- Zookeeper watcher 事件機制原理剖析事件
- Redis系列(九):Redis的事務機制Redis
- Redis系列12:Redis 的事務機制Redis