您的位置:首页 > 数据库 > Redis

redis事件

2016-05-29 11:26 323 查看
redis服务器是一个事件驱动程序,服务器需要处理两类事件:文件事件,时间事件。


1. 文件事件

转载于:http://redisbook.com/preview/event/file_event.html

redis服务器通过套接字与客户端进行连接,而文件事件就是服务器对套接字操作的抽象。


文件事件结构:ae.h/aeFileEvent

/* File event structure
*
* 文件事件结构
*/
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;


1.1 文件事件处理器

Redis 基于 Reactor 模式开发了自己的网络事件处理器: 这个处理器被称为文件事件处理器(file event handler):

文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。

当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。



多个文件可能并发地出现,但I/O多路复用程序总是将所有产生事件的套接字放到一个队列里,以有序/同步/每次一个套接字的方式向文件事件分派器传送套接字,当上一个套接字产生的事件处理完毕后,I/O多路复用程序才会想文件事件分派器传送下一个套接字。




文件事件分派器接收 I/O 多路复用程序传来的套接字, 并根据套接字产生的事件的类型, 调用相应的事件处理器。



连接应答处理器:networking.c/acceptTcpHandler函数是redis的连接应答处理器,用于对连接服务器监听套接字的客户端进行应答,具体实现是sys/socket.h/accept函数的包装。

命令请求处理器:networking.c/readQueryFromClient函数是redis的命令请求处理器,负责从套接字中读如客户端发送的命令请求内容,具体实现是unistd.h/read函数的包装。

命令回复处理器:networking.c/sendReplyToClient函数是redis的命令回复处理器,负责将服务器执行命令后得到的命令回复通过套接字返回给客户端,具体实现 是unistd.h/write函数的包装。

1.2 I/O多路复用程序的实现

Redis 的 I/O 多路复用程序的所有功能都是通过包装常见的 select 、 epoll 、 evport 和 kqueue 这些 I/O 多路复用函数库来实现的, 每个 I/O 多路复用函数库在 Redis 源码中都对应一个单独的文件, 比如 ae_select.c 、 ae_epoll.c 、 ae_kqueue.c 等。




#include 宏定义了相应的规则, 程序会在编译时自动选择系统中性能最高的 I/O 多路复用函数库来作为 Redis 的 I/O 多路复用程序的底层实现


/* Include the best multiplexing layer supported by this system.
* The following should be ordered by performances, descending. */
#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


1.3 事件类型(两类)

I/O 多路复用程序可以监听多个套接字的 ae.h/AE_READABLE 事件和 ae.h/AE_WRITABLE 事件, 这两类事件和套接字操作之间的对应关系如下:

当套接字变得可读时(客户端对套接字执行 write 操作,或者执行 close 操作),或者有新的可应答(acceptable)套接字出现时(客户端对服务器的监听套接字执行 connect 操作), 套接字产生 AE_READABLE 事件。

当套接字变得可写时(客户端对套接字执行 read 操作), 套接字产生 AE_WRITABLE 事件。

I/O 多路复用程序允许服务器同时监听套接字的 AE_READABLE 事件和 AE_WRITABLE 事件, 如果一个套接字同时产生了这两种事件, 那么文件事件分派器会优先处理 AE_READABLE 事件, 等到 AE_READABLE 事件处理完之后, 才处理 AE_WRITABLE 事件。

这也就是说, 如果一个套接字又可读又可写的话, 那么服务器将先读套接字, 后写套接字。

2. 时间事件

redis服务器中的一些操作需要在给定的时间点执行,而时间事件就是对服务器对这类定时操作的抽象。


redis时间事件分为两类:

定时事件:让程序在指定的时间之后执行一次。

周期性事件:让程序每隔指定时间就执行一次。

时间事件结构:ae.h/aeTimeEvent

/* Time event structure
*
* 时间事件结构
*/
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 *next;

} aeTimeEvent;




服务器将所有时间事件都放在一个**无序链表**中,每当**时间事件执行器**运行时,它就**遍历整个链表**,查找所有已到达的时间事件,并调用相应的事件处理器。
无序链表:指的是链表不按when属性的大小排序。


时间事件实例:redis.c/serverCron函数

3. 事件的调度与执行

服务器中同时存在文件事件和时间事件两种事件类型,必须对这两种事件进行调度。事件的调度和执行由ae.c/aeProcessEvents函数负责。


4. 事件处理角度下的redis服务器运行流程

redis服务器的主函数:

def main():

# 初始化服务器
init_server();

# 一直处理事件,直到服务器关闭为止
while server_is_not_shutdown():
aeProcessEvents()

# 服务器关闭,执行清理工作
clean_server()




5. Reactor模式

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: