相关阅读
简介
Redis的原子性是通过Redis的单进程单线程的网络模型保证的;
那么单线程的Redis是如何实现高性能呢?
- 基于内存操作;
- 事件驱动模型;
server.c中的main函数是服务的开始,其中和事件驱动相关的核心代码如下:
int main(int argc, char **argv) {
...
aeMain(server.el);
...
}
aeMain
在ae.c中实现,代码如下:
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
// 死循环执行aeProcessEvents处理事件
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
}
}
aeProcessEvents
在ae.c中实现,核心代码如下:
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
int processed = 0, numevents;
// 处理AE_TIME_EVENTS/AE_FILE_EVENTS
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
...
// I/O多路复用,当有事件发生或者超时才返回
// 调用操作系统函数实现,在Linux上为epoll
numevents = aeApiPoll(eventLoop, tvp);
/* After sleep callback. */
if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
eventLoop->aftersleep(eventLoop);
// 遍历发生的事件
for (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 fired = 0;
int invert = fe->mask & AE_BARRIER;
// 处理读事件
if (!invert && fe->mask & mask & AE_READABLE) {
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
// 处理写事件
if (fe->mask & mask & AE_WRITABLE) {
if (!fired || fe->wfileProc != fe->rfileProc) {
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
}
// 如果颠倒,那么此时处理读事件
if (invert && fe->mask & mask & AE_READABLE) {
if (!fired || fe->wfileProc != fe->rfileProc) {
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
}
processed++;
}
}
// 处理时间事件
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop);
// 返回处理的文件、时间事件数量
return processed;
}
aeProcessEvents
是Redis统一处理所有事件的地方(事件分发器),从其处理逻辑看出,Redis没有fork
任何线程处理事件,所以说Redis是一个基于事件驱动高的单线程应用;
Redis的事件驱动模型主要涉及:事件类型、I/O多路复用模块、事件分发器、事件处理器;而aeProcessEvents
就是实现了事件分发器;
事件类型
Redis是一个事件驱动的内存数据库,服务器需要处理两种类型的事件,定义如下:
// 文件事件
#define AE_FILE_EVENTS 1
// 时间事件
#define AE_TIME_EVENTS 2
文件事件
Redis服务器通过socket实现和客户端(或其它Redis服务器)的交互,文件事件就是服务器对socket操作的抽象,Redis服务器通过监听并处理这些socket产生的文件事件,实现对客户端调用的响应;
其数据结构如下:
typedef struct aeFileEvent {
// 监听事件类型掩码,可表示为事件类型
// 值可以是 AE_READABLE 或 AE_WRITABLE ,
// 或者 AE_READABLE | AE_WRITABLE
int mask; /* one of AE_(READABLE|WRITABLE) */
// 读事件处理器
aeFileProc *rfileProc;
// 写事件处理器
aeFileProc *wfileProc;
// 多路复用库的私有数据
void *clientData;
} aeFileEvent;
// 文件事件处理器
typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
时间事件
Redis有很多操作需要在给定的时间点进行处理,时间事件就是对这类定时任务的抽象;
其数据结构定义如下:
typedef struct aeTimeEvent {
// 时间事件的唯一标识符
long long id; /* time event identifier. */
// 事件的到达时间
long when_sec; /* seconds */
long when_ms; /* milliseconds */
// 事件处理函数
aeTimeProc *timeProc;
// 事件终结函数
aeEventFinalizerProc *finalizerProc;
// 多路复用库的私有数据
void *clientData;
// 指向上个时间事件结构,形成链表
struct aeTimeEvent *prev;
// 指向下个时间事件结构,形成链表
struct aeTimeEvent *next;
} aeTimeEvent;
// 时间事件处理器
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
// 时间事件终结处理器
typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);
I/O多路复用模块
Redis基于Reactor模式开发了自己的事件处理器;
I/O多路复用模块会监听多个FD,当这些FD产生:accpet
、read
、write
、close
的文件事件时,就会向事件分发器传送事件;
事件分发器收到事件后,会根据事件的类型(读/写)将事件分发给对应的事件处理器;
Redis的I/O多路复用模块,其实就是封装了底层不同操作系统提供的select
(ae_select.c)、epoll
(ae_epoll.c)、avport
(ae_avport.c)、kqueue
(ae.kqueue.c)等基础函数,向上层提供了一个统一的接口(ae.c),屏蔽了底层操作系统实现的差异性;
Redis封装的接口定义如下:
typedef struct aeApiState {
// epoll实例描述符
int epfd;
// 绑定的事件
struct epoll_event *events;
} aeApiState;
/**
* 创建epoll
*/
static int aeApiCreate(aeEventLoop *eventLoop)
/**
* 调整绑定事件的大小
*/
static int aeApiResize(aeEventLoop *eventLoop, int setsize)
/**
* 释放epoll
*/
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)
事件分发器
ae.c中aeProcessEvents
实现了事件分发器的功能,文件事件分发的核心代码如下:
// I/O多路复用,当有事件发生或者超时才返回
// 调用操作系统函数实现,在Linux上为epoll
numevents = aeApiPoll(eventLoop, tvp);
/* After sleep callback. */
if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
eventLoop->aftersleep(eventLoop);
// 遍历发生的事件
for (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 fired = 0;
int invert = fe->mask & AE_BARRIER;
// 处理读事件
if (!invert && fe->mask & mask & AE_READABLE) {
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
// 处理写事件
if (fe->mask & mask & AE_WRITABLE) {
if (!fired || fe->wfileProc != fe->rfileProc) {
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
}
// 如果颠倒,那么此时处理读事件
if (invert && fe->mask & mask & AE_READABLE) {
if (!fired || fe->wfileProc != fe->rfileProc) {
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
}
processed++;
}
时间事件处理的核心代码如下:
static int processTimeEvents(aeEventLoop *eventLoop) {
int processed = 0;
aeTimeEvent *te;
long long maxId;
time_t now = time(NULL);
// 处理系统时间被修改的情况
if (now < eventLoop->lastTime) {
te = eventLoop->timeEventHead;
while(te) {
te->when_sec = 0;
te = te->next;
}
}
// 更新上一次执行的时间
eventLoop->lastTime = now;
te = eventLoop->timeEventHead;
maxId = eventLoop->timeEventNextId-1;
// 遍历时间事件链表
while(te) {
long now_sec, now_ms;
long long id;
// 删除标记为删除状态的时间事件
if (te->id == AE_DELETED_EVENT_ID) {
aeTimeEvent *next = te->next;
if (te->prev)
te->prev->next = te->next;
else
eventLoop->timeEventHead = te->next;
if (te->next)
te->next->prev = te->prev;
if (te->finalizerProc)
te->finalizerProc(eventLoop, te->clientData);
zfree(te);
te = next;
continue;
}
// 如果时间事件ID大于maxId,说明该时间事件是本次遍历过程中产生的,本次遍历不处理
if (te->id > maxId) {
te = te->next;
continue;
}
// 获取当前时间
aeGetTime(&now_sec, &now_ms);
if (now_sec > te->when_sec ||
(now_sec == te->when_sec && now_ms >= te->when_ms))
{
// 找到需要处理的时间事件
int retval;
id = te->id;
// 执行对应的时间事件处理函数
retval = te->timeProc(eventLoop, id, te->clientData);
processed++;
if (retval != AE_NOMORE) {
// 刷新该时间事件下一次执行时间
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
} else {
// 不再需要,则标记为删除状态
te->id = AE_DELETED_EVENT_ID;
}
}
// 继续下一个时间事件,直到链表结束
te = te->next;
}
return processed;
}
事件处理器
Redis有大量的事件处理器,通过aeCreateFileEvent
绑定事件处理器到文件事件上,代码如下:
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData)
{
// 校验fd
if (fd >= eventLoop->setsize) {
errno = ERANGE;
return AE_ERR;
}
// 准备填充该FD对应的处理
aeFileEvent *fe = &eventLoop->events[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;
if (fd > eventLoop->maxfd)
eventLoop->maxfd = fd;
return AE_OK;
}
一个简单命令的处理流程涉及如下处理器:
acceptTcpHandler
:连接应答处理器,负责处理连接相关的事件,当有client连接到Redis时就会产生AE_READABLE
事件,由其处理;绑定操作为:aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler, NULL)
;readQueryFromClient
:命令请求处理器,负责读取通过socket发送过来的命令;绑定操作为:aeCreateFileEvent(server.el,fd,AE_READABLE, readQueryFromClient, c)
;sendReplyToClient
:命令回复处理器,当Redis处理完命令,就会产生AE_WRITEABLE
事件,将数据回复给client;绑定操作为:aeCreateFileEvent(server.el, c->fd, ae_flags, sendReplyToClient, c)
;