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;
    //...
 }