目录
One loop pre process:类多 Reactor 多进程
ngx_trylock_accept_mutex 获取accept锁
nginx服务器的网络模块设计,基于进程设计,采用多个Reactors充当I/O进程和工作进程,通过一把 accept锁,完美解决多个Reactors的“惊群现象”,并通过双向链表管理业务,并通过计算其偏移量来得到数据结构的指针地址,完成数据结构与业务层的解耦。同时使用零拷贝技术,利用sendfile来传输小文件,利用直接IO+异步IO来传输大文件。
One loop pre process:类多 Reactor 多进程
master进程
<1> 管理worker进程
<2> 接收外界信号,向各worker发送信号(./nginx -s reload 进行热部署
)
<3> 监控worker的工作状态,worker退出后自动启动新的worker进程
<4> 创建了 socket、然后 bind 并 listen
问:Master进程与标准的多 Reactor 多进程有些差异。
具体差异表现在主进程中仅仅用来初始化 socket,并没有创建 mainReactor 来 accept 连接,而是由子进程的 Reactor 来 accept 连接。
worker进程
<1> 由master进程fork而来。由于每个进程都一样,所以每个 worker 都有 Master 创建出来的 listenfd。所以每个worker进程都能监听到新连接加入的事件。
<5> worker进程个数是可配置的,一般与机器cpu核心数量一致。
work_connection(worker的连接数)
2个(访问静态资源,一来一回)或者4个(访问动态资源,一来一回+和 tomcat 的一来一回)。
问:nginx有1个master,有4个worker,每个worker的最大连接数为1024,那么Nginx支持的最大并发为多少?
访问静态资源: work_connections * worker_processes /2
访问动态资源::work_connections * worker_processes /4
Nginx多进程模型的好处
<1> 每个worker进程都是独立处理连接数据,处理过程中不需要锁,节省了开销。
<2> worker进程互相独立,一个出现异常其他的worker可以继续服务
<3> 多进程模型为reload热部署提供了良好的支撑
Nginx进程事件分发器:解决Worker间协同
此方法为进程实现的核心函数。主要作用:事件分发;惊群处理;简单的负载均衡。
负载均衡:
当事件配置初始化的时候,会设置一个全局变量:
ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;
当ngx_accept_disabled为正数的时候,connection达到连接总数的7/8的时候,就不再处理新的连接accept事件,只处理当前连接的read事件
惊群处理:
<1> 通过ngx_trylock_accept_mutex争抢文件锁,拿到文件锁的,才可以处理accept事件。
<2> ngx_accept_mutex_held是拿到锁的一个标志,当拿到锁了,flags会被设置成NGX_POST_EVENTS,这个标志会在事件处理函数ngx_process_events中将所有事件(accept和read)放入对应的ngx_posted_accept_events和ngx_posted_events队列中进行延后处理。
<3> 当没有拿到锁,调用事件处理函数ngx_process_events的时候,可以明确都是read的事件,所以可以直接调用事件ev->handler方法回调处理。
<4> 拿到锁的进程,接下来会优先处理ngx_posted_accept_events队列上的accept事件,处理函数:ngx_event_process_posted
<5> 处理完accept事件后,就将文件锁释放
<6> 接下来处理ngx_posted_events队列上的read事件,处理函数:ngx_event_process_posted
Nginx解决惊群和进程和负载均衡处理的要点
<1> Nginx的N个进程会争抢文件锁,当只有拿到文件锁的进程,才能处理accept的事件。
<2> 没有拿到文件锁的进程,只能处理当前连接对象的read事件
<3> 当单个进程总的connection连接数达到总数的7/8的时候,就不会再接收新的accpet事件。
<4> 如果拿到锁的进程能很快处理完accpet,而没拿到锁的一直在等待(等待时延:ngx_accept_mutex_delay),容易造成进程忙的很忙,空的很空,类似于自旋锁
Accept锁结构
typedef struct {
#if (NGX_HAVE_ATOMIC_OPS)
ngx_atomic_t *lock;
#if (NGX_HAVE_POSIX_SEM)
ngx_atomic_t *wait;
ngx_uint_t semaphore;
sem_t sem;
#endif
#else
ngx_fd_t fd;
u_char *name;
#endif
ngx_uint_t spin; //衰变值
} ngx_shmtx_t;
ngx_trylock_accept_mutex 获取accept锁
/**
* 获取accept锁
*/
ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle) {
/**
* 拿到锁
*/
if (ngx_shmtx_trylock(&ngx_accept_mutex)) {
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"accept mutex locked");
/* 多次进来,判断是否已经拿到锁 */
if (ngx_accept_mutex_held && ngx_accept_events == 0) {
return NGX_OK;
}
/* 调用ngx_enable_accept_events,开启监听accpet事件*/
if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
ngx_shmtx_unlock(&ngx_accept_mutex);
return NGX_ERROR;
}
ngx_accept_events = 0;
ngx_accept_mutex_held = 1;
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"accept mutex lock failed: %ui", ngx_accept_mutex_held);
/**
* 没有拿到锁,但是ngx_accept_mutex_held=1
*/
if (ngx_accept_mutex_held) {
/* 没有拿到锁,调用ngx_disable_accept_events,将accpet事件删除 */
if (ngx_disable_accept_events(cycle, 0) == NGX_ERROR) {
return NGX_ERROR;
}
ngx_accept_mutex_held = 0;
}
return NGX_OK;
}