Irhine

home

Nginx 学习笔记

26 Nov 2013

nginx平台初探

架构

nginx运行方式:

基本的网络事件都是放在worker进程中来处理的。多个worker进程之间是对等独立的,同等竞争来自客户端的请求,一个请求只可能实在一个worker进程中处理。 worker的数量一般会设置成cpu的核心数,这与nginx的进程模型以及事件处理模型是分不开的 Image Title

可以通过kill向master进程发送信号来控制nginx,例如

kill -HUP pid

则告诉nginx从容地重启nginx。一般用这个信号来重启nignx或者重新加载配置,这样重启服务是不中断的。master接收到HUP信号后是这样的:

nginx 0.8之后可以使用命令来管理,例如,

./nginx -s reload #重启nginx
./nginx -s stop #停止nginx运行

其中reload执行时,会启动一个新的nginx进程并解析到reload参数,则nginx会重新加载配置文件,向master进程发送信号,接下来的动作就和直接向nginx发送信号是一样的了。

nginx采用进程模型的好处:

nginx采用异步非阻塞的方式来处理事件。一个请求事件的完整过程是:

具体到系统底层,就是读写事件,而当读写事件没有准备好的时候,必然不可操作,如果是阻塞调用,事件没有准备好就只能等事件准备好之后才能继续处理。阻塞调用会进入内核等待,cpu就会让出去给别的进程用。当网络事件特别多时,大多数进程都在等待,cpu空闲下来没人用,利用率很低,更别谈高并发了。那就要用非阻塞来实现,非阻塞就是事件没有准备好,马上返回EAGAIN,告诉你,事件还没准备好呢,等会再过来吧。过一会儿再来检查一下事件,知道事件准备好了为止,在这期间,你就可以先去做其它事情,然后再来看看事件好了没。虽然不阻塞了,但需要不时的来检查事件的状态,可以做更多的事情了,但带来的开销也很大。所以才会有了异步非阻塞的事件处理机制,具体到系统调用就是select/poll/epoll/kqueue。他们提供了一种机制,让你可以同时监控多个事件,调用他们是阻塞的,但可以设置超时时间,在超时时间之内,如果有事件准备好了,就返回。这种机制正好解决了我们的两个问题,以epoll为例,当事件没准备好时,放到epoll里面,事件准备好了,就去读写,当读写返回EAGAIN时,将请求再次加入epoll里。只要有事件准备好了,就去处理。若所有事件都没有准备好,就在epoll里面等待。这样并发量就上来了,但是都是未完成的请求,线程只有一个,所以当前处理的请求只有一个,只是可以在大量的请求之间不断地切换。切换是因为异步事件没有准备好,而主动让给别的请求,这里的切换是没有任何代价的,可以理解为循环处理多个准备好的事件。与多线程相比,异步非阻塞不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常轻量级。 所以推荐设置worker数和cpu的核心数一致也就不难理解了,更多的worker数,只会导致进程来竞争cpu资源,从而带来不必要的上下文切换。而且,nginx为了根号的利用多核的特性,提供了cpu亲缘性的绑定选项,可以将某一个进程绑定在某一个核上,这样就不会因为进程的切换带来cache的失效。

伪代码来总结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;
        } elif (events[i].type == WRITE) {
            t.handler = write_handler;
        }
        run_tasks_add(t);
}