Nginx工作机制剖析
Nginx
以下简介摘自百度百科:
Nginx是一款轻量级的HTTP服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等
初探Nginx架构
Nginx的启动
Nginx在启动后,在Unix系统中会以daemon
(守护进程)的方式在后台运行,后台进程包含一个master
进程和多个worker
进程。
当然,我们也可以手动关掉daemon模式,让Nginx在前台运行,这个时候Nginx就是单进程模式。这个方法一般用于调试,其他时候不用。
master进程主要用来管理worker进程。包含:接受外界信号、向各worker进程发送性、监控worker进程的运行状态、当worker异常退出时,重启新的worker。
多个worker进程之间是对等的,它们共同竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。
Nginx的操作
刚刚我们说到,master来管理worker进程。所以我们可以忽略worker进程,专注与master进程的通信。master进程会接收来自外界发来的信号,再根据信号做不同的处理。所以我们要控制Nginx,只需要通过kill
向master进程发送信号就行了。
比如
kill-HUP pid
,则是告诉Nginx,从容的重启Nginx。因为是从容的重启,因此服务是不中断的。master进程在接收到这个信号的时候,会先重新加载配置文件,然后再启动新的进程。同时向所有的老进程发送信号。老进程在接收到信号后,先执行完当前的任务,然后就可以退休了。
当然,现在一般不给master进程发送信号,而是直接使用命令行参数:比如./nginx -s stop
停止运行、./nginx -s reload
重新加载。
worker进程处理请求
我们提到了,worker进程之间是平等的,每个进程处理请求的机会也是一样的。
当一个请求过来的时候,每个进程都有同等的机会去处理。这是因为,每个worker进程都是从master进程fork过来的,在master进程里面,先建立好需要listen
的socket之后,再fork
处多个worker进程,这样每个worker进程都可以通过去accept
这个socket。
当然,并不是同一个socket,而是监听同一个ip和port的一组socket。
一般来说,当一个连接进来后,所有在accept
这个socket上的进程都会收到通知。但是只有一个进程会处理这个请求,其他则accept
失败,这就是惊群现象。
因此,Nginx采用了acept_mutex
这个东西。有了这把共享锁之后,同一时刻,就只会由于一个进程在accept
连接,这样就可以解决惊群问题。
Nginx处理高并发的策略
Nginx采用异步非阻塞
的方式来处理请求。与apache的常用工作方式相比Nginx采用的方法能够大大解决线程的上下文切换带来的CPU开销。
Apache常用工作方式:
一个请求独占一个工作线程。但是当并发非常大的时候,会有很多线程存在。这对操作系统来说是一个很大的开销。
我们来看一下Nginx为什么要采用异步非阻塞呢?
我们来看一个完整的请求过程:首先,建立连接,然后再接受数据,接受数据后再发送数据。
再具体一点,就是读写事件。如果读写事件没有准备好,必然是不可能操作的。如果不以非阻塞的方式,那么就会一直进行等待,阻塞调用会进入内核等待。一旦网络中的事件变多,大家都在等待。CPU没人用,利用率自然上不去。所以,在Nginx的设计里面,最忌讳的就是阻塞的系统调用。
那么我们以非阻塞调用,当事件没好,你可以去干别的事件,然后时不时回来看看事件好没好。但是这个时不时回来看一下就会增加开销。所以我们需要异步。
异步就是当事件准备好了,内核会通知你。不用你去一遍遍检查好没好。
Q:为什么Nginx推荐设置worker数为CPU核心数
异步非阻塞也正是Nginx推荐设置worker为CPU核数的原因。更多的worker数只会导致进程来竞争CPU资源。
而且,Nginx还提供了CPU亲缘绑定选项,可以把某个进程绑定在某个核上。
同时Nginx在做4字节的字符串比较的时候,回答四个字符转换成一个整型,来减少CPU指令数。
Nginx异步处理模型
我们知道:对于一个基本的web服务器来说,事件通常有:网络事件、信号、定时器三种。
刚刚我们通过异步非阻塞可以很好的解决。现在还差信号和定时器。
Q1:Nginx如何处理信号
对于Nginx来首,特定的信号代表着特定的意义。信号会中断掉程序当前运行,在改变状态后继续执行。
对于Nginx来说,如果Nginx正在等待事件(epoll_wait
)时,如果程序收到信号,在信号处理函数处理完后,epoll_wait
会返回错误,然后程序再次进入epoll_wait
。
Q2:Nginx如何处理定时器
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);
time_out = ETERNITY;
for t in wait_tasks:
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 if(events[i].type == WRITE)
{
t.handler = write_handler;
}
}
run_tasks_add(t);
}
参考文献
[1] Nginx开发从入门到精通