nginx 架构
nginx在启动的时候,在unix系统中以daemon的方式后台运行,包含master进程,以及多个work进程(也可配置单进程),cache manager,cache loader进程
我们也可以手动开启前台调试。本文以多进程方式展开描述
nginx在启动后,会有一个master来管理work进程,包括接收操作信号,向各work进程发送信号,
监控work进程运行状态,在work进程退出后(如向work进程发送kill信号,内部崩溃),会自动重新启动新的
work进程。
对于网络处理,也是放在work进程处理,多个work进程之间是同等且独立来竞争处理客户端请求,work的个数在配置文件worker_processes参数。一般设置cpu核数
nginx网络处理
nginx 采用了异步非阻塞的方式来处理请求,在用户态完成连接切换,是可以同时处理成千上万个请求的。我们对比下apache,每个请求独占一个线程,
当并发上万时,意味着同时几千个线程,上下文切换导致cpu开销很大。所以,我们所有的目的就是减少系统调用,以及减少cpu上下文切换。上文我们说到建议cpu个数的work数,
就是减少不必要的上下文切换,减少切换带来的cpu cache失效。
首先,nginx 在启动时,会解析配置文件,得到需要监听的端口与ip地址,然后在nginx的master进程里面,先初始化好这个监控的socket(创建 socket,设置 addrreuse 等选项,
绑定到指定的ip地址端口,再listen),然后再 fork 出多个子进程出来,然后子进程会竞争 accept 新的连接(注意这里linux 2.6之前会有惊群问题,nginx通过加锁解决惊群,具体会在下文详细介绍)。
此时,客户端就可以向nginx发起连接了。当客户端与服务端通过三次握 手建立好一个连接后, nginx 的某一个子进程会 accept 成功,得到这个建立好的连接的 socket,然后创建 nginx对
连接的封装,即 ngx_connection_t 结构体。接着,设置读写事件处理函数并添加读写事件来 与客户端进行数据的交换。最后,nginx或客户端来主动关掉连接。
当配置upstream模块时,代理请求到其它下游服务,也封装在ngx_connection_t内,作为客户端会创建socket再设置如非阻塞,通过添加读写事件,调用connect/read/write调用连接
处理完后关闭连接并释放ngx_connection_t。
我们再来看下nginx对连接数限制,都知道每个连接会有一个句柄,受限于操作系统ulimit -n。nginx通过设置worker_connectons来限制每个work进程最大连接数,对于反向代理需要多建立下游服务连接,会占用两个连接。
nginx是通过连接池来实现来管理, 但连接池里面保存的其实不是真实的连接,它只是一个worker_connections大小的一个 ngx_connection_t结构的数组,并且,nginx 会通过一个链表 free_connections 来保存
所有的空闲ngx_connection_t,每次获取一个连接时,就从空闲连接链表中获取一个,用完后,再放回空闲连接链表里面
前面我们提到惊群问题,尽管操作系统解决了,还会有其它问题,即连接进来,会有多个空闲进程争抢连接,会导致分配不公平,当某个work进程空闲连接用完后,也无法交给其它空闲进程处理, 我们来看如何解决的
nginx的处理得先打开,accept_mutex选项,只有获得了accept_mutex的进程才会去添加accept事件,也就是说,nginx会控制进程是否添加accept事件,使用的是ngx_accept_disabled变量去控制是否拿锁,
下面我们看下拿锁的代码
//总连接/8-空闲连接
ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;
//每次执行到此处时,都会减 1,直到小于 0。不去获取accept_mutex 锁
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {return; }
if (ngx_accept_mutex_held) {
flags |= NGX_POST_EVENTS;
} else {
if (timer == NGX_TIMER_INFINITE || timer > ngx_accept_mutex_delay){
timer = ngx_accept_mutex_delay;
}
}
下面我们来看下nginx的事件处理模块
//伪代码如下
while (true) {
for t in run_tasks:
t.handler();
//更新时间
update_time(&now);
timeout = ETERNITY;
//判断超时
for t in wait_tasks: /* sorted already */
if (t.time <= now) { t.timeout_handler();
} else {
timeout = t.time - now; break;
}
//epoll事件
nevents = poll_function(events, timeout); for i in nevents:
task t;
if (events[i].type == READ) {
t.handler = read_handler;
} else { /* events[i].type == WRITE */
t.handler = write_handler; }
run_tasks_add(t);
nginx connection
在nginx里,connection就是对tcp/udp连接的封装。其中包括连接的socket,读事件,写事件。 以及对下游客户端的封装
//src/core/ngx_connection.h
struct ngx_connection_s {
void *data;
ngx_event_t *read;
ngx_event_t *write;
ngx_socket_t fd;
ngx_recv_pt recv;
ngx_send_pt send;
ngx_recv_chain_pt recv_chain;
ngx_send_chain_pt send_chain;
ngx_listening_t *listening;
off_t sent;
ngx_log_t *log;
ngx_pool_t *pool;
int type;
struct sockaddr *sockaddr;
socklen_t socklen;
ngx_str_t addr_text;
ngx_proxy_protocol_t *proxy_protocol;
#if (NGX_SSL || NGX_COMPAT)
ngx_ssl_connection_t *ssl;
#endif
ngx_udp_connection_t *udp;
struct sockaddr *local_sockaddr;
socklen_t local_socklen;
ngx_buf_t *buffer;
ngx_queue_t queue;
ngx_atomic_uint_t number;
ngx_msec_t start_time;
ngx_uint_t requests;
//...
}