zoukankan      html  css  js  c++  java
  • Redis 文件事件

    事件驱动

    Redis 服务器是事件驱动程序,分为文件事件时间事件

    • 文件事件:socket 的可读可写事件
    • 定时任务

    它们都被封装到aeEventLoop结构体中

    typedef struct aeEventLoop {
    	int stop; // 标识事件是否结束
    	aeFileEvent *events; // 文件事件数组,存储已注册的文件事件
    	aeFireEvent *fired; // 存储被触发的文件事件
    	aeTimeEvent *timteEventHead; // 多个时间事件形成的链表
    	void *apidata; // I/O模型的封装
    	aeBeforeSleepProc *beforesleep; // 进程阻塞前执行
    	aeBeforeSleepProc *aftersleep; // 进程被唤醒后执行
    } aeEventLoop;
    

    事件驱动程序实际上也是通过while/for循环,循环等待事件的发生

    while (! eventLoop->stop) {
    	if (eventLoop->beforesleep != NULL)
    		eventLoop->beforesleep(eventLoop)
    	aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
    }
    

    aeProcessEvents为事件处理主函数

    epoll

    Redis 客户端通过 TCP socket 与服务端交互,文件事件指的就是 socket 的可读可写事件。一般使用非阻塞模式,相关的 I/O 多路复用有select/epoll/kqueue等,不同的操作系统不同的实现。

    epoll为例,它是 Linux 内核为处理大量并发网络连接而提出解决方案。epoll提供3个 API

    1. epoll_create 创建一个 epoll 专用的文件描述符,用于后续 epoll 相关 API 调用
    int epoll_create(int size)
    // size 告知内核程序期望注册的网络连接数目,Linux 2.6.8后改为内核动态分配
    // 返回参数是 epoll 专用的文件描述符
    
    1. epoll_ctl 函数向 epoll 注册、修改或删除需要监控的事件
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
    // epfd 函数 epoll_create 返回的 epoll 文件描述符
    // op 操作类型 EPOLL_CTL_ADD:注册事件; EPOLL_CTL_MOD:修改网络连接事件; EPOLL_CTL_DEL:删除事件
    // fd 网络连接的 socket 文件描述符
    // event 需要监控的事件
    
    1. epoll_wait 函数会会阻塞进程,直到监控的若干网络连接有事件发生
    int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout)
    // epfd 函数epoll_create返回的epoll文件描述符
    // epoll_event 作为输出参数使用,用于回传已触发的事件数组
    // maxevents 每次能处理的最大事件数目
    // timeout epoll_wait 函数阻塞超时时间,如果超过 timeout 时间还没有事件发生,函数就不再阻塞直接返回;当 timeout 等于0是函数立即返回,timeout 等于-1时函数一直阻塞到有事件发生
    

    文件事件

    Reids 没有直接使用 epoll 的 API,而是同时支持4种I/O多路复用模型,对这些模型的 API 进行了封装。然后在编译阶段检查操作系统支持的I/O多路复用模型,并按照策略来决定复用那张模型。

    还是以 epoll 为例,Redis 进行了如下封装

    // 对应 epoll_create
    static int aeApiCreate(aeEventLoop *eventLoop)
    
    // 对应 epoll_ctl 添加事件
    static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
    // 对应 epoll_ctl 删除事件
    static int aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask)
    
    // 对应 epoll_wait
    static int aeApiPool(aeEventLoop *eventLoop, struct timeval *tvp)
    

    回忆一下上面提到的eventLoop结构体,其成员 apidata 指向4种I/O多路复用模型对象;events 存储需要监控的事件数组,以 socket 文件描述符作为数组索引存取元素;fired 存储已触发的事件数组。

    文件事件的结构体定义如下:

    typedef struct aeFileEvent {
    	int mask; // 文件事件类型 AE_READABLE 可读事件;AE_WRITEABLE 可写事件
    	aeFileProc *rfileProc; // 读事件处理函数指针
    	aeFileProc *wfileProc; // 写事件处理函数指针
    	void *clientData; // 指向对应的客户端对象
    } aeFileEvent;
    

    看一下创建文件事件 aeCreateFileEvent 的实现

    int aeCreateFileEvent (aeEventLoop *eventLoop, int fd, int mask, aeFileProc *proc, void *clientData) {
    	aeFileEvent *fe = &eventLoop->evnts[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;
    }
    

    Redis 服务器会通过创建各类文件事件来处理事务,比如:

    • 启动时创建 socket 并监听,等待客户端连接
    aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler, NULL);
    
    • 客户端与服务器建立 socket 连接之后,服务器会等待客户端的命令请求
    aeCreateFileEvent(server.el, fd, AE_READABLLE, readQueryFromClient, c);
    
    • 服务器处理完客户端的命令请求之后,命令回复会暂时缓存在client结构体的buf缓冲区,待客户端文件描述符的可写事件发生时,才会真正往客户端发送命令回复
    aeCreateFileEvent(server.el, c->fd, AE_READABLLE, sendReplyToClient, c);
    

    Redis 所有事件的执行都是通过aeProcessEvents函数来控制。在其中,执行文件事件会出现阻塞情况(epoll_wait),如果阻塞事件太长了,会妨碍到时间事件(定时)的执行,为避免出现这种情况,在实现文件事件时传入的等待时间,是计算最早发生的时间事件得到的

    int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
    	shortest = aeSearchNearestTimer(eventLoop);
    	long long ms = (shortest->when_sec - now_sec) * 1000 + 
    		shortest->when_ms - now_ms;
    
    	// 阻塞事件发生
    	numevents = aeApiPoll(eventLoop, ms);
    
    	for (j=0; j < numevents; j++) {
    		aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j]].fd];
    		// 处理文件事件,即根据类型执行rfileProc或wfileProc
    	}
    
    	// 处理时间事件
    	processed += processTimeEvents(eventLoop);
    }
    

    总结

    现在我们来整体看一下 Redis 服务器相应命令的流程
    image
    aeMain 函数通过调用 aeProcessEvents 函数来进行文件事件和时间事件的调度和执行。aeEventLoop 中记录了事件相关的信息。首先通过 aeSearchNearestTimer 函数获取最短的时间事件的执行时间间隔n,然后调用 aeApiPoll 函数获取监听到的套接字,最后执行与套接字向对应的事件处理函数 rfileProc 和 wfileProc,最后再执行时间事件函数 processTimeEvents。

    一次完整的客户端与服务端连接事件:

    • 器监听套件字的 AE_READABLE 事件,当客户端发送连接请求产生 AE_READABLE 事件,服务端会对客户端的连接请求进行应答,将客户端套接字的 AE_READABLE 事件与命令请求处理函数(aeFileProc),客户端可以向服务端发送命令请求了

    • 端向服务端发送一个命令请求,客户端套接字将产生 AE_READABLE 事件,引发命令处理器去执行,执行命令将产生相应的命令回复,服务端将客户端套接字的 AE_WRITABLE 事件与命令回复处理函数(aeFileProc)关联

    • 端尝试读取命令回复时,客户端套接字将产生 AE_WRITABLE 事件,触发命令回复处理器执行,当命令回复处理器将命令回复全部写入套接字之后,服务器就会接触客户端套接字的 AE_WRITABLE 事件与命令回复处理函数(aeFileProc)之间的关联

  • 相关阅读:
    MyBatis高级查询 存储过程
    MyBatis高级查询 一对多映射
    第20章-使用JMX管理Spring Bean
    第19章-使用Spring发送Email
    第18章-使用WebSocket和STOMP实现消息功能
    第17章-Spring消息
    第16章-使用Spring MVC创建REST API
    第15章-使用远程服务
    基于IKAnalyzer搭建分词服务
    第08章-使用Spring Web Flow
  • 原文地址:https://www.cnblogs.com/Zioyi/p/15469156.html
Copyright © 2011-2022 走看看