美文网首页
Nginx结构原理

Nginx结构原理

作者: 全栈小运维 | 来源:发表于2020-03-27 16:09 被阅读0次

一.Nginx的结构解析

进程操作

nginx 在启动后,在 unix 系统中会以 daemon(服务)的方式在后台运行,后台进程包含一个 master 进程和多个 worker 进程。

我们只需要与master 进程通信就行了。 master 进程会接收来自外界发来的信号,再根据信号做不同的事情。所以我们要控制 nginx,只需要通过 kill 向 master 进程发送信号就行了。

比如 kill -HUP pid,则是告诉 nginx,从容地重启 nginx,我们一般用这个信号来重启 nginx,或重新加载配置,因为是从容地重启,因此服务是不中断的。

master 进程在接收到 HUP 信号后是怎么做的呢?首先 master 进程在接到信号后,会先重新加载配置文件,然后再启动新的 worker 进程,并向所有老的 worker 进程发送信号,告诉他们可以光荣退休了。新的 worker 在启动后,就开始接收新的请求,而老的 worker 在收到来自 master 的信号后,就不再接收新的请求,并且在当前进程中的所有未处理完的请求处理完成后,再退出。

事件模型

现在,我们知道了当我们在操作 nginx 的时候,nginx 内部做了些什么事情,那么,worker 进程又是如何处理请求的呢?我们前面有提到,worker 进程之间是平等的,每个进程,处理请求的机会也是一样的。当我们提供 80 端口的 http 服务时,个连接请求过来,每个进程都有可能处理这个连接,怎么做到的呢?

首先,每个 worker 进程都是从 master 进程 fork 过来,在 master 进程里面,先建立好需要 listen 的 socket(listenfd)之后,然后再 fork 出多个worker 进程。所有 worker 进程的 listenfd 会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有 worker 进程在注册 listenfd 读事件前抢accept_mutex,抢到互斥锁的那个进程注册 listenfd 读事件,在读事件里调用 accept 接受该连接。

当一个 worker 进程在 accept 这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。我们可以看到,一个请求,完全由worker 进程来处理,而且只在一个 worker 进程中处理。

事件处理

nginx是用的异步非阻塞

首先,请求过来,要建立连接,然后再接收数据,接收数据后,再发送数据。具体到系统底层,就是读写事件,而当读写事件没有准备好时,必然不可操作,如果不用非阻塞的方式来调用,那就得阻塞调用了,事件没有准备好,那就只能等了,等事件准备好了,你再继续吧。

非阻塞就是,事件没有准备好,马上返回EAGAIN,告诉你,事件还没准备好呢,慌什么,过会再来吧。好吧,你过一会,再来检查一下事件,直到事件准备好了为止,在这期间,你就可以先去做其它事情,然后再来看看事件好了没。虽然不阻塞了,但你得不时地过来检查一下事件的状态,你可以做更多的事情了,但带来的开销也是不小的。

所以,才会有了异步非阻塞的事件处理机制,具体到系统调用就是 select/poll/epoll/kqueue 这样的系统调用。它们提供了一种机制,让你可以同时监控多个事件,调用他们是阻塞的,但可以设置超时时间,在超时时间之内,如果有事件准备好了,就返回。

这种机制正好解决了我们上面的两个问题,拿 epoll 为例(在后面的例子中,我们多以 epoll 为例子,以代表这一类函数),当事件没准备好时,放到epoll 里面,事件准备好了,我们就去读写,当读写返回 EAGAIN 时,我们将它再次加入到 epoll 里面。这样,只要有事件准备好了,我们就去处理它,只有当所有事件都没准备好时,才在 epoll 里面等着。

这样,我们就可以并发处理大量的并发了,当然,这里的并发请求,是指未处理完的请求,线程只有一个,所以同时能处理的请求当然只有一个了,只是在请求间进行不断地切换而已,切换也是因为异步事件未准备好,而主动让出的。这里的切换是没有任何代价,你可以理解为循环处理多个准备好的事件,事实上就是这样的。

与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换)。更多的并发数,只是会占用更多的内存而已。

一个是基于轮询的,一个是基于事件的。apache 想知道5个孩子饿不饿,需要一个个去问,问到谁饿了就知道了。nginx 想知道5个孩子饿不饿,注册一个回调,谁饿了就自己报道一声,调用一下事件。可以这么理解。

二.Nginx的启动顺序

启动顺序

post-read 1.读取请求内容阶段,nginx读取并解析完请求头之后就立即开始运行;

server-rewrite 2.server请求地址重写阶段;

find-config 3.配置查找阶段,用来完成当前请求与location配重块之间的配对工作;

rewrite 4.location请求地址重写阶段,当ngx_rewrite指令用于location中,就是再这个阶段运行的;

post-rewrite 5.请求地址重写提交阶段,当nginx完成rewrite阶段所要求的内部跳转动作,如果rewrite阶段有这个要求的话;

preaccess 6.访问权限检查准备阶段,ngx_limit_req和ngx_limit_zone在这个阶段运行,ngx_limit_req可以控制请求的访问频率,ngx_limit_zone可以控制访问的并发度;

access 7.权限检查阶段,ngx_access在这个阶段运行,配置指令多是执行访问控制相关的任务,如检查用户的访问权限,检查用户的来源IP是否合法;

post-access 8.访问权限检查提交阶段;

try-files 9.配置项try_files处理阶段;

content 10.内容产生阶段,是所有请求处理阶段中最为重要的阶段,因为这个阶段的指令通常是用来生成HTTP响应内容的;

log 11.日志模块处理阶段;

每一个阶段上能够注冊handler。处理请求就是执行每一个阶段上注冊的handler。Nginx模块提供的配置指令仅仅会一般仅仅会注冊并执行在当中的某一个处理阶段。

比如,set指令属于rewrite模块的,执行在rewrite阶段,deny和allow执行在access阶段。

子请求

Nginx中从客户端访问的叫主请求,他被nginx这个程序来逐步处理。还有一种内部的请求,叫子请求。

比如下面这个,将请求交给foo和bar的区块去处理。

location /main {
echo_location /foo;  #echo_location发送子请求到指定的location
echo_location /bar;
}
location /foo {
echo foo;
}
location /bar {
echo bar;
}

#curl http://127.0.0.1/main
#返回 foo
#      bar

相关文章

网友评论

      本文标题:Nginx结构原理

      本文链接:https://www.haomeiwen.com/subject/ktgluhtx.html