这两天着重看nginx事件模块的处理,这一部分跟网络框架比较大。跟现在工作内容相关性也比较大,总结一下它在这方面的处理方式,下次写代码的时候可以用一下。得到了2点启发:
1. 防止epoll惊群,可以采用锁的方式处理,但是不能等epoll响应的事件处理完再释放锁,这样锁的占用时间会比较长,从而使得其他的进程无法epoll wait,拖慢效率。可以先把事件加入连接池,释放锁,然后再处理。
2. 负载均衡的粗略实现。下面有描述。
同时也有些许疑问。
1. 这些listen的处理可以理解,但当普通连接,他转发给server之后,如何做处理还不清楚,这点需要更层次的阅读代码来理解。
2. ngx_process_events_and_timers 代码当中参杂了一些定时器的实现,这个定时器与cache manage process有关,需要理清楚一些。
首先nginx整个流程处理依赖的是一个cycle的数据结构,这个数据结构当中保存了一个ngx_listening_t链表,纪录的是nginx监听的信息。在init_cycle的时候会根据配置文件来初始化这个链表,并且调用ngx_open_listening_sockets函数来bind和listen。
注意这个init_cycle是在主进程内进行初始化的,所以在spawn worker process的时候会复制一份给worker process。
现在来看
ngx_event.c :ngx_event_process_init
的部分代码,这部分代码包含了许多东西,下面来看下这代码,他预先分配了连接池和读事件,写事件。
cycle->connections =ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
//预先分配connection_n个连接
cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,cycle->log);
//预先分配connection_n个读事件和写事件
i = cycle->connection_n;
next = NULL;
do {
i--;
c[i].data = next;
c[i].read = &cycle->read_events[i];
c[i].write = &cycle->write_events[i];
c[i].fd = (ngx_socket_t) -1;
next = &c[i];
} while (i);
//这个循环将预先分配的读事件和写事件分配到连接。
cycle->free_connections = next;
cycle->free_connection_n = cycle->connection_n;
//构造空闲连接池。
- 继续往下阅读
ngx_event.c :ngx_event_process_init
的部分代码。下面这部分代码就跟监听事件注册到epoll 有关了。值得注意的一点,这边会有TCP和UDP两种协议,以下讨论的是TCP协议,UDP不予考虑。
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
c = ngx_get_connection(ls[i].fd, cycle->log);
// 根据监听端口的fd,从以上的空闲connection池获取连接
if (c == NULL) {
return NGX_ERROR;
}
c->type = ls[i].type;
c->log = &ls[i].log;
c->listening = &ls[i];
ls[i].connection = c;
rev = c->read;
rev->log = c->log;
rev->accept = 1;
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
: ngx_event_recvmsg;
// 根据type 是TCP还是UDP来分配回调函数是accept还是recvmsg。
}
- 上面讲的是epoll add的部分,所以现在自然而然要讨论epoll wait的部分了。但是呢这边先讨论一个”惊群” 现象的处理了。其实思想比较简单,就是加锁,但是呢,因为加锁的会堵塞,影响性能, 所以先判断然后锁。
代码位置为ngx_event:ngx_process_events_and_timers
这边撸代码会比较多, 所以只列一些关键函数, 并说明其做的事情。
ngx_trylock_accept_mutex(); //获取锁,获取不到就直接返回。
ngx_process_events();// 这是一个宏,根据配置文件,定义处理的方法,如果是epoll module 则做3件事情,
// epoll wait()
// 根据epoll的返回,添加事件到队列
// 根据read事件的标志位accept来区分什么accept事件还是recv事件,从而添加到不同的队列
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
//处理accept事件队列的数组,值得注意,这时候不unlock锁,至于为什么?现在不明
ngx_event_process_posted(cycle, &ngx_posted_events);
- nginx如何做负载均衡的呢?
用下面的ngx_accept_disabled 变量来控制负载的均衡,ngx_accept_disabled 表示空闲连接数的7/8,
如果小于0.则直接处理事件池的事件了。
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
}