nginx基础篇:基本架构和基础概念

nginx是一个开源的高性能web服务器或反向代理服务器。

基本架构

多进程模型

nginx启动后会产生一个master进程和多个worker进程。master进程主要来管理worker进程,包括:

  • 接受来自外接的信号,向各个worker进程发送信号
  • 监控worker进程的运行状态,当worker异常退出后会自动重启新的worker进程。

worker进程则负责处理网络事件,多个worker进程之间是对等并且互相独立的,一个网络请求只可能在一个worker中处理。worker进程数可以设置,例如worker_processes 8;设置8个worker进程,通常设置和机器cpu的核数一致。

多个进程如何处理请求?
  • 当一个连接过来,每个worker进程都可能处理这个连接,如何保证只有一个worker进程处理?
    master进程会先建立好需要listen的socket(listenfd)之后,再fork(fork的原理和实现)出多个worker进程,那么当新的连接到来时所有worker进程的listenfd都变得可读,为保证只有一个进程处理该连接,所有worker进程注册listenfd读事件前会抢accept_mutex互斥锁,抢到的worker进程才注册listenfd读事件,调用accept建立连接。之后就开始读取请求,解析请求,处理请求,产生数据返回客户端,最后断开链接,一个完整的请求就完成了。

  • 如何保证多个worker可以比较平均的抢到连接呢?
    nginx使用ngx_accept_disabled变量来控制是否竞争accept_mutex锁,ngx_accept_disabled的等于当前进程所有连接总数的1/8减剩余的空闲连接数量,当剩余的空闲连接数小于总连接数的1/8时,该值大于0,且剩余连接数越小,该值越大。当ngx_accept_disabled的值>0时不会尝试抢占accept_mutex锁,只是将ngx_accept_disabled减1,相当于让出了抢占机会,而且剩余空闲连接数越小的worker进程让出的机会越多,控制多个worker进程连接的平衡。

ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;

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;
        }
    }
}
多进程模型的优点
  • 对于每个worker进程来说,独立进程资源独立,不需要加锁
  • 各个worker进程之间互不影响,一个挂了其他的还能工作,master进程还会自动重启新的worker进程。

事件模型

每个worker进程只有一个主线程,如何实现高并发?

使用epoll多路复用非阻塞I/O模型,解决多线程模型带来的上下文切换和系统资源开销,同时在事件准备好时使用回调的方式通知处理,减少非阻塞模型轮询的开销。

定时器

nginx借助epoll_wait的超时时间实现定时器,nginx的定时器事件放在一棵维护定时器的红黑树里,每次进入epoll_wait前,先从红黑树里拿到所有定时器事件的最小事件,在计算出epoll_wait的超时时间后进入epoll_wait。

当没有事件产生也没有信号中断时,epoll_wait会超时,即定时器事件到达了,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;
        }
    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);
}

基础概念

connection

nginx的connection是对TCP连接的封装,包括连接的socket、读事件、写事件。利用nginx的connection可以很方便的处理连接相关的事情,比如建立连接、发送数据、接收数据等。nginx的http请求就是建立在connection只上的,nginx不仅可以做web服务器,也可以做邮件服务器等任何后端服务。

nginx如何创建一个连接的?

nginx在启动时会解析配置文件,得到需要监听的ip和端口,然后再nginx的master进程中初始化监听socket(创建socket、设置addrreuse等选项,bind到指定的ip和端口,然后再listen),然后同前面所述fork出多个worker进程,此时客户端就可以向nginx发起连接了。当客户端与服务器三次握手建立好一个连接后,抢到accept_mutex的worker进程会accept成功,得到建立好连接的socket,然后创建nginx对连接的封装:ngx_connection_t结构体。

当nginx作为客户端请求其他sever的数据时,与其他sever建立的连接也使用ngx_connection_t结构。作为客户端,nginx先获取一个ngx_connection_t对象,然后创建socket,设置socket属性。然后通过添加读写事件,调用connect/read/write来调用连接,当断开连接后释放ngx_connection_t。

连接数的最大上限

因为每个socket会占用一个文件描述符,操作系统的最大fd是有上限的(可通过ulimit -n查看),所以nginx的最大连接数受限于系统的最大fd数,当fd用完创建socket就会失败。nginx通过worker_connections 65535;命令设置单个worker的最大连接数。nginx通过连接池来管理,连接池保存的不是真正的连接,只是worker_connections大小的ngx_connection_t类型的数组,同时维护了一个空闲链表free_connections保存所有空闲的ngx_connection_t,每次创建连接时就从空闲链表中取一个,用完再放回链表。

最大连接数不等于最大并发数,对于HTTP请求来说能支持的最大并发数为worker_connections*worker_processes,如果是作为反向代理服务器,最大支持的并发数要减半,因为每个并发会占用两个连接(客户端到反向代理、反向代理到后端服务)。

参考资料

【1】nginx平台初探

你可能感兴趣的:(nginx基础篇:基本架构和基础概念)