nginx机制-从源码分析(1)

一.工作原理
    nginx在启动后,在unix系统中会以daemon的方式在后台运行,后台进程包含一个master进程和多个worker进程。
    nginx在启动后,会有一个master进程和多个worker进程。master进程主要用来管理worker进程,包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程。而基本的网络事件,则是放在worker进程中来处理了。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,这里面的原因与nginx的进程模型以及事件处理模型是分不开的。
    1.nginx的进程模型
    1.1 nginx机制
    worker进程之间是平等的,每个进程,处理请求的机会也是一样的。当我们提供80端口的http服务时,一个连接请求过来,每个进程都有可能处理这个连接,怎么做到的呢?首先,每个worker进程都是从master进程fork过来,在master进程里面,先建立好需要listen的socket(listenfd)之后,然后再fork出多个worker进程。所有worker进程的listenfd会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该连接。当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。我们可以看到,一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。
    1.2 优点:
        对于每个worker进程来说,独立的进程,不需要加锁,所以省掉了锁带来的开销,同时在编程以及问题查找时,也会方便很多。其次,采用独立的进程,可以让互相之间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master进程则很快启动新的worker进程。当然,worker进程的异常退出,肯定是程序有bug了,异常退出,会导致当前worker上的所有请求失败,不过不会影响到所有请求,所以降低了风险。
    小结:
        1)一个完整的请求读取请求、解析请求、处理请求,产生数据后,再返回给客户端,最后断开连接。
        2)一个完整的请求完全由一个worker进程处理。
    2.nginx的事件模型
    2.1基本知识:
        对于一个基本的web服务器来说,事件通常有三种类型,网络事件、信号、定时器。
    2.2网络事件的事件处理机制
        首先看一个请求的完整过程:
        大致过程:
            建立连接---接收数据---发送数据。
        再次看系统底层的操作:
                上述过程(建立连接---接收数据---发送数据)在系统底层就是读写事件。
               1)如果采用阻塞调用的方式,当读写事件没有准备好时,必然不能够进行读写事件,那么久只好等待,等事件准备好了,才能进行读写事件。那么请求就会被耽搁。
                操作系统的三态转换,都很清楚,阻塞之后任务在队列中等候,那么CPU在处理其它事件,在web服务器中,预备的CPU大量用来处理网络事件,现在事件堵塞,那么CPU就会空闲下来,CPU的利用率无法上去,如何高并发?当然,我们也可以增加线程,那么只会生成大量的线程,导致不必要的资源消耗,严重时会造成进程死锁。
                 2)既然阻塞调用不行,那么采用非阻塞方式。非阻塞就是,事件没有准备好,马上返回EAGAIN,提醒事件正在准备。每隔一段时间,检查事件是否已经准备好了。这就需要时刻检查事件的准备情况,也是会消耗资源(同步非阻塞)非阻塞通过不断检查事件的状态来判断是否进行读写操作,这样带来的开销很大。
                 3)因此才有了异步非阻塞的事件处理机制。具体到系统调用就是像select/poll/epoll/kqueue这样的系统调用。他们提供了一种机制,让你可以同时监控多个事件,调用他们是阻塞的,但可以设置超时时间,在超时时间之内,如果有事件准备好了,就返回。这种机制解决了我们上面两个问题。
                 以epoll为例:当事件没有准备好时,就放入epoll里面。如果有事件准备好了,那么就去处理;如果事件返回的是EAGAIN,那么继续将其放入epoll里面。从而,只要有事件准备好了,我们就去处理她,只有当所有时间都没有准备好时,才在epoll里面等着。这样,我们就可以并发处理大量的并发了,当然,这里的并发请求,是指未处理完的请求,线程只有一个,所以同时能处理的请求当然只有一个了,只是在请求间进行不断地切换而已,切换也是因为异步事件未准备好,而主动让出的。这里的切换是没有任何代价,你可以理解为循环处理多个准备好的事件,事实上就是这样的
                 4)与多线程的比较:
                    与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换)。更多的并发数,只是会占用更多的内存而已。现在的网络服务器基本都采用这种方式,这也是nginx性能高效的主要原因。
                 小结:通过异步非阻塞的事件处理机制,Nginx实现由进程循环处理多个准备好的事件,从而实现高并发和轻量级。


    补充:
    同步、异步、阻塞、非阻塞的理解
    同步阻塞I/O
         官方解释:用户空间的应用程序执行一个系统调用时,会导致应用程序阻塞。只有等系统调用结束(数据传输完成或发生错误),用户控件的应用程序才会继续执行。
    同步非阻塞I/O
         官方解释:在自己准备的同时,检查是否有事件完成。
    异步阻塞I/O
         官方解释:另外一个阻塞解决方案是带有阻塞通知的非阻塞 I/O。在这种模型中,配置的是非阻塞 I/O,然后使用阻塞 select 系统调用来确定一个 I/O 描述符何时有操作。使 select 调用非常有趣的是它可以用来为多个描述符提供通知,而不仅仅为一个描述符提供通知。对于每个提示符来说,我们可以请求这个描述符可以写数据、有读数据可用以及是否发生错误的通知。(理解有难度)
    异步非阻塞I/O(AIO)
         官方解释:异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。读请求会立即返回,说明 read 请求已经成功发起了。在后台完成读操作时,应用程序然后会执行其他处理操作。当 read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。

    2.3信号
        信号的处理。对nginx来说,有一些特定的信号,代表着特定的意义。信号会中断掉程序当前的运行,在改变状态后,继续执行。如果是系统调用,则可能会导致系统调用的失败,需要重入。对于nginx来说,如果nginx正在等待事件(epoll_wait时),如果程序收到信号,在信号处理函数处理完后,epoll_wait会返回错误,然后程序可再次进入epoll_wait调用。

    2.4 定时器
        由于epoll_wait等函数在调用的时候是可以设置一个超时时间的,所以nginx借助这个超时时间来实现定时器。nginx里面的定时器事件是放在一颗维护定时器的红黑树里面,每次在进入epoll_wait前,先从该红黑树里面拿到所有定时器事件的最小时间,在计算出epoll_wait的超时时间后进入epoll_wait。所以,当没有事件产生,也没有中断信号时,epoll_wait会超时,也就是说,定时器事件到了。这时,nginx会检查所有的超时事件,将他们的状态设置为超时,然后再去处理网络事件。由此可以看出,当我们写nginx代码时,在处理网络事件的回调函数时,通常做的第一个事情就是判断超时,然后再去处理网络事件。
    2.5 小结
         我们可以用一段伪代码来描述这一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);
                }
            }    


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值