内容简介:大概做高性能服务器的,都绕不开事件处理模块来,一般一个事件模块,会分为以下几部分:下面就这三部分展开Nginx事件处理模块的分析。描述事件的数据结构,一般至少需要以下几部分数据:
大概做高性能服务器的,都绕不开事件处理模块来,一般一个事件模块,会分为以下几部分:
- 如何定义一个描述事件的数据结构。
- 如何在事件模块中支持定时器。
- 如果需要支持多平台,事件模块需要考虑如何统一以及区分各平台的具体实现。
下面就这三部分展开Nginx事件处理模块的分析。
ngx_event_t
描述事件的数据结构,一般至少需要以下几部分数据:
- 用于保存用户相关的数据。
- 用于保存事件触发之后的回调函数。
- 用于表示事件状态、类型的数据。
nginx中,描述事件采用的数据结构是ngx_event_t中,其内部成员就是按照前面的三部分来划分了。
- void *data:事件相关的数据。
- ngx_event_handler_pt handler:事件被触发时的回调函数。
-
第三类数据,ngx_event_t中划分的比较仔细:
- unsigned write:1:可写标志位
- unsigned active:1:活跃标志位
- unsigned disabled:1:禁用标志位
- unsigned eof:1:为1表示字节流已经结束
- unsigned error:1:处理事件出错
- unsigned timedout:1:事件超时
- unsigned timer_set:1:为1表示这是一个超时事件
- unsigned deferred_accept:1:为1表示需要延迟接收TCP连接
-
除了以上三部分,还有其他一些重要的数据:
- ngx_rbtree_node_t timer:红黑树节点,用于实现定时器的,下面讨论定时器再展开。
- ngx_queue_t queue:延迟队列,如果事件不在轮询循环中直接处理,而是之后被处理,就放在这个队列中。
总体来看,event这个结构体为了涵盖所有可能的事件,做的大而全,不只是用来描述一般的IO事件,还包括了定时器事件,还包括了接收连接相关的数据。
定时器的实现
Nginx内部使用红黑树来实现定时器,目的在于能够快速的查询到哪些定时器超时了。不同的事件结构中,这部分实现采用的数据结构不一样,libevent、libuv采用的是最小堆,redis比较挫,这部分采用的是链表。
在一个事件循环中,因为既要考虑到一般的IO事件,又要考虑到定时器事件,所以都会以一个最近被触发的定时器来做为查询IO事件被触发的时间,即以下的伪代码:
查询最近将被触发的定时器超时时间返回t 将t做为epoll_wait之类的查询IO事件的超时时间,即最长等待t时间看有没有IO事件被触发 遍历定时器,查询已经超时的定时器进行回调处理
从这里可以看出,“迅速查询到距离当前最近被触发的定时器时间”以及“迅速查询到当前哪些定时器超时”,是这个定时器模块速度的关键。
由于红黑树、最小堆这种平衡数据结构,每次查询都排除掉当前一半的元素,可以做到时间复杂度O(logn),所以就常用来实现定时器了。
事件模块的实现
由于nginx需要跑在多个平台下面,而不同平台使用的事件机制又不一样,比如 linux 是epoll,bsd是kqueue等,需要实现事件模块的时候,既需要统一事件模块的共性部分,又需要区分不同平台的差异部分。
这看上去又是一个面向对象的设计问题了:基类负责实现共性的部分,子类具体再来实现各平台相关的部分。
前面 分析libuv 的时候提到过,libuv多使用宏来模拟C++中的继承,不是很认可这个代码风格,来看看nginx类似场景的实现。
nginx中,将事件相关的操作函数统一放在结构体ngx_event_actions_t中,可以把这部分类比于子类需要实现的函数接口:
typedef struct { ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); ngx_int_t (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); ngx_int_t (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); ngx_int_t (*add_conn)(ngx_connection_t *c); ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags); ngx_int_t (*notify)(ngx_event_handler_pt handler); ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags); ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer); void (*done)(ngx_cycle_t *cycle); } ngx_event_actions_t;
前面在分析到nginx如何解析配置的时候提到过,nginx中的配置是分层次的,event模块做为一个顶层的core模块,内部又有子模块,而这里的事件模块就是event模块中的子模块:
typedef struct { ngx_str_t *name; void *(*create_conf)(ngx_cycle_t *cycle); char *(*init_conf)(ngx_cycle_t *cycle, void *conf); ngx_event_actions_t actions; } ngx_event_module_t;
在具体实现中,每个平台的事件模块创建自己的ngx_event_module_t结构,在create_conf、init_conf中完成对事件模块的初始化,然后填充模块的actions结构体。
最后,具体调用actions结构体中的函数,封装到宏里面,毕竟虽然有多平台的实现,最后也只能用上一个而已:
#define ngx_process_events ngx_event_actions.process_events #define ngx_done_events ngx_event_actions.done #define ngx_add_event ngx_event_actions.add #define ngx_del_event ngx_event_actions.del #define ngx_add_conn ngx_event_actions.add_conn #define ngx_del_conn ngx_event_actions.del_conn #define ngx_notify ngx_event_actions.notify
而前面提到的事件处理部分共性的地方,全都放在函数ngx_process_events_and_timers里,那个函数里面再通过宏ngx_process_events调用具体事件模块的处理函数。
这里有个细节,其实前面的分析也提到过,nginx的事件模块里,不一定在检查到事件触发之后就会被马上调用回调函数来处理,而是可能放在一个post队列中,在轮询完所有事件之后再进行回调:
if (flags & NGX_POST_EVENTS) { // 有NGX_POST_EVENTS标志位的情况,将accept事件放到ngx_posted_accept_events队列中 // 等待后续被回调 queue = rev->accept ? &ngx_posted_accept_events : &ngx_posted_events; ngx_post_event(rev, queue); } else { // 否则直接处理 rev->handler(rev); }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Zepto源码学习Event模块
- NodeJS Cluster模块源码学习
- NodeJS Events模块源码学习
- 试读angular源码第四章:angular模块及JIT编译模块
- 对公司内部某个模块某个源码审计
- 比特币源码分析:txdb 模块(三)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。