前言
C10K problem提出了一个问题,如果1w个客户端连接到server上,间歇性的发送消息,有哪些好的方案?
其中的一种方案是,每个线程处理多个客户端,使用异步I/O和就绪通知机制,redis无疑是一个很好的榜样
redis的特点和C10K proble的契合点
内存数据库;
单线程支持上w个客户端连接;
高并发,单机支持10w并发数;
低时延,局域网内大多数时延低于3ms。
redis的文件事件处理器
四个关键组成
套接字、I/O多路复用、文件事件分发器、事件处理器。
这是一个典型的reactor设计模式,redis没有采用现有的事件驱动库,比如libev等,而是自己定义了一个ae驱动器。
使用epoll同时监听多个套接字,并给不同套接字关联不同的处理程序。
当被监听的套接字准备好连接应答、请求、应答、关闭等操作时,对应的文件事件就会产生,文件事件处理器会调用对应的处理程序。
I/O多路复用和文件事件分派器的实现
epollI/O多路复用的封装
ae_epoll.c
创建epoll实例和事件槽
aeApiCreate
释放epoll实例和事件槽
aeApiFree
给fd新增或者修改关注事件
aeApiAddEvent
删除fd关注的事件
aeApiDelEvent
获取可执行事件
aeApiPoll
ae.h
文件事件处理器的实例
typedef struct aeEventLoop {
// 目前已注册的最大描述符
int maxfd; /* highest file descriptor currently registered */
// 目前已追踪的最大描述符
int setsize; /* max number of file descriptors tracked */
// 用于生成时间事件 id
long long timeEventNextId;
// 最后一次执行时间事件的时间
time_t lastTime; /* Used to detect system clock skew */
// 已注册的文件事件
aeFileEvent *events; /* Registered events */
// 已就绪的文件事件
aeFiredEvent *fired; /* Fired events */
// 时间事件
aeTimeEvent *timeEventHead;
// 事件处理器的开关
int stop;
// 多路复用库的私有数据
void *apidata; /* This is used for polling API specific data */
// 在处理事件前要执行的函数
aeBeforeSleepProc *beforesleep;
} aeEventLoop;
文件事件处理器以及文件事件分派器的实现
ae.c
初始化文件事件处理器
aeCreateEventLoop
删除事件处理器
aeDeleteEventLoop
停止事件处理器
aeStop
创建文件事件处理器
aeCreateFileEvent
删除文件事件处理器
aeDeleteFileEvent
获取监听的事件类型
aeGetFileEvents
文件事件分派器,调用aePoll获取激活的事件,并调用事件对应的文件处理器来处理这些事件
aeProcessEvents
redis的I/O事件处理器
创建连接处理程序
aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler,NULL)
创建请求处理程序
aeCreateFileEvent(server.el,fd,AE_READABLE, readQueryFromClient, c)
创建应答处理程序,当命令回复完毕,解除套接字和事件的关联
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE, sendReplyToClient, c)
ae驱动的事件类型
// 可读
#define AE_READABLE 1
// 可写
#define AE_WRITABLE 2
当客户端发起连接、断开连接、发送请求时,套接字产生AE_READABLE事件
当套接字变得可写(客户端调用read操作)时,套接字产生AE_WRITABLE事件。
ae驱动的事件处理顺序
ae驱动允许同时监听可读和可写事件,同时发生时先处理可读事件,再处理可写事件。