nginx架构初探
nginx是多进程的架构,由master进程和多个worker进程组成。master进程和worker进程之间
用信号进行通信。nginx默认是以后台进程的形式运行的。系统管理员通过信号与master进程通信
进而与worker进程通信。同一个请求只会在一个worker进程之间进行处理。worker进程的数目是
可以通过配置文件改变的。一般worker进程的数目设置为cpu的核数。进程越多并不代表性能越高,
因为操作系统在管理多进程的时候还需要进程的上下文切换,这个也是有时间消耗的。
1、nginx的信号处理
kill -HUP pid用于告诉nginx,从容地重启nginx,我们一般用这个信号来重启nginx,或重新加载配置,因为是从容地重启,因此服务是不中断的。master进程在接收到HUP信号后是怎么做的呢?首先master进程在接到信号后,会先重新加载配置文件,然后再启动新的进程,并向所有老的进程发送信号,告诉他们可以光荣退休了。新的进程在启动后,就开始接收新的请求,而老的进程在收到来自master的信号后,就不再接收新的请求,并且在当前进程中的所有未处理完的请求处理完成后,再退出。当然,直接给master进程发送信号,这是比较老的操作方式,nginx在0.8版本之后,引入了一系列命令行参数,来方便我们管理。比如,./nginx -s reload,就是来重启nginx,./nginx -s stop,就是来停止nginx的运行。如何做到的呢?我们还是拿reload来说,我们看到,执行命令时,我们是启动一个新的nginx进程,而新的nginx进程在解析到reload参数后,就知道我们的目的是控制nginx来重新加载配置文件了,它会向master进程发送信号,然后接下来的动作,就和我们直接向master进程发送信号一样了。
2.nginx如何处理请求
每一个nginx的worker进程都是平等的,都有同样的机会获得处理一个client请求的机会,
nginx启动之后,master进程会去监听80端口,worker进程从master进程fork出来之后,
子进程继承了父进程打开的文件句柄,则子进程同样监听了80端口,每个请求来了之后
所有在accept在这个socket上面的进程,都会收到通知,而只有一个进程可以accept这个
连接,其它的则accept失败,这是所谓的惊群现象。当然,nginx也不会视而不见,所以
nginx提供了一个accept_mutex这个东西,从名字上,我们可以看这是一个加在accept上
的一把共享锁。有了这把锁之后,同一时刻,就只会有一个进程在accpet连接,这样就
不会有惊群问题了。accept_mutex是一个可控选项,我们可以显示地关掉,默认是打开
的。当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求
,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。
我们可以看到,一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。
nginx采用了异步非阻塞的方式来处理请求。nginx为了更好的利用多核特性,提供了cpu
亲缘性的绑定选项,我们可以将某一个进程绑定在某一个核上,这样就不会因为进程的
切换带来cache的失效。
nginx在做4个字节的字符串比较时,会将4个字符转换成一个int型,再作比较,以减少
cpu的指令数等等。
3. nginx的事件处理模型
对于一个基本的web服务器来说,事件通常有三种类型,网络事件、信号、定时器。
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);
}