热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

nginx工作原理,进程模型,事件处理,配置系统和模块化体系

nginx在启动后,在unix系统中会以daemon的方式在后台运行,后台进程包含一个master进程和多个worker进程。当然nginx也是支持多线

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


当然nginx也是支持多线程的方式的,只是我们主流的方式还是多进程的方式,也是nginx的默认方式。nginx采用多进程的方式有诸多好处,所以我就主要讲解nginx的多进程模式吧。


nginx在启动后,会有一个master进程和多个worker进程。master进程主要用来管理worker进程,包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程。而基本的网络事件,则是放在worker进程中来处理了。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,这里面的原因与nginx的进程模型以及事件处理模型是分不开的。nginx的进程模型,可以由下图来表示:


nginx进程模型的好处:


  1. 对于每个worker进程来说,独立的进程,不需要加锁,所以省掉了锁带来的开销,同时在编程以及问题查找时,也会方便很多。
  2. 采用独立的进程,可以让互相之间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master进程则很快启动新的worker进程。
  3. 如果worker进程的异常退出,肯定是程序有bug了,异常退出,会导致当前worker上的所有请求失败,不过不会影响到所有请求,所以降低了风险。



Nginx的事件处理过程

有人可能要问了,nginx采用多worker的方式来处理请求,每个worker里面只有一个主线程,那能够处理的并发数很有限啊,多少个worker就能处理多少个并发,何来高并发呢?

首先,请求过来,要建立连接,然后再接收数据,接收数据后,再发送数据。具体到系统底层,就是读写事件,而当读写事件没有准备好时,必然不可操作,如果不用非阻塞的方式来调用,那就得阻塞调用了,事件没有准备好,那就只能等了,等事件准备好了,你再继续吧。阻塞调用会进入内核等待,cpu就会让出去给别人用了,对单线程的worker来说,显然不合适,当网络事件越多时,大家都在等待呢,cpu空闲下来没人用,cpu利用率自然上不去了,更别谈高并发了。好吧,你说加进程数,这跟apache的线程模型有什么区别,注意,别增加无谓的上下文切换。所以,在nginx里面,最忌讳阻塞的系统调用了。不要阻塞,那就非阻塞喽。非阻塞就是,事件没有准备好,马上返回EAGAIN,告诉你,事件还没准备好呢,你慌什么,过会再来吧。好吧,你过一会,再来检查一下事件,直到事件准备好了为止,在这期间,你就可以先去做其它事情,然后再来看看事件好了没。虽然不阻塞了,但你得不时地过来检查一下事件的状态,你可以做更多的事情了,但带来的开销也是不小的。所以,才会有了异步非阻塞的事件处理机制,具体到系统调用就是像select/poll/epoll/kqueue这样的系统调用。它们提供了一种机制,让你可以同时监控多个事件,调用他们是阻塞的,但可以设置超时时间,在超时时间之内,如果有事件准备好了,就返回。这种机制正好解决了我们上面的两个问题,拿epoll为例(在后面的例子中,我们多以epoll为例子,以代表这一类函数),当事件没准备好时,放到epoll里面,事件准备好了,我们就去读写,当读写返回EAGAIN时,我们将它再次加入到epoll里面。这样,只要有事件准备好了,我们就去处理它,只有当所有事件都没准备好时,才在epoll里面等着。这样,我们就可以并发处理大量的并发了,当然,这里的并发请求,是指未处理完的请求,线程只有一个,所以同时能处理的请求当然只有一个了,只是在请求间进行不断地切换而已,切换也是因为异步事件未准备好,而主动让出的。这里的切换是没有任何代价,你可以理解为循环处理多个准备好的事件,事实上就是这样的。与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换)。更多的并发数,只是会占用更多的内存而已。 我之前有对连接数进行过测试,在24G内存的机器上,处理的并发请求数达到过200万。现在的网络服务器基本都采用这种方式,这也是nginx性能高效的主要原因。

我们之前说过,推荐设置worker的个数为cpu的核数,在这里就很容易理解了,更多的worker数,只会导致进程来竞争cpu资源了,从而带来不必要的上下文切换。而且,nginx为了更好的利用多核特性,提供了cpu亲缘性的绑定选项,我们可以将某一个进程绑定在某一个核上,这样就不会因为进程的切换带来cache的失效。像这种小的优化在nginx中非常常见,同时也说明了nginx作者的苦心孤诣。比如,nginx在做4个字节的字符串比较时,会将4个字符转换成一个int型,再作比较,以减少cpu的指令数等等。

现在,知道了nginx为什么会选择这样的进程模型与事件模型了。对于一个基本的web服务器来说,事件通常有三种类型,网络事件、信号、定时器。从上面的讲解中知道,网络事件通过异步非阻塞可以很好的解决掉。如何处理信号与定时器?

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

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

我们可以用一段伪代码来总结一下nginx的事件处理模型:

while (true) {for t in run_tasks:t.handler();update_time(&now);timeout &#61; ETERNITY;for t in wait_tasks: /* sorted already */if (t.time <&#61; now) {t.timeout_handler();} else {timeout &#61; t.time - now;break;}nevents &#61; poll_function(events, timeout);for i in nevents:task t;if (events[i].type &#61;&#61; READ) {t.handler &#61; read_handler;} else { /* events[i].type &#61;&#61; WRITE */t.handler &#61; write_handler;}run_tasks_add(t);
}

Nginx配置系统

Nginx的配置系统由一个主配置文件和一些辅助配置文件构成&#xff0c;这些配置文件默认在/usr/local/nginx/目录下。

nginx的配置系统由一个主配置文件和其他一些辅助的配置文件构成。这些配置文件均是纯文本文件&#xff0c;全部位于nginx安装目录下的conf目录下。

配置文件中以#开始的行&#xff0c;或者是前面有若干空格或者TAB&#xff0c;然后再跟#的行&#xff0c;都被认为是注释&#xff0c;也就是只对编辑查看文件的用户有意义&#xff0c;程序在读取这些注释行的时候&#xff0c;其实际的内容是被忽略的。

由于除主配置文件nginx.conf以外的文件都是在某些情况下才使用的&#xff0c;而只有主配置文件是在任何情况下都被使用的。所以在这里我们就以主配置文件为例&#xff0c;来解释nginx的配置系统。

在nginx.conf中&#xff0c;包含若干配置项。每个配置项由配置指令和指令参数2个部分构成。指令参数也就是配置指令对应的配置值。


指令概述

配置指令是一个字符串&#xff0c;可以用单引号或者双引号括起来&#xff0c;也可以不括。但是如果配置指令包含空格&#xff0c;一定要引起来。


指令参数

指令的参数使用一个或者多个空格或者TAB字符与指令分开。指令的参数有一个或者多个TOKEN串组成。TOKEN串之间由空格或者TAB键分隔。

TOKEN串分为简单字符串或者是复合配置块。复合配置块即是由大括号括起来的一堆内容。一个复合配置块中可能包含若干其他的配置指令。

如果一个配置指令的参数全部由简单字符串构成&#xff0c;也就是不包含复合配置块&#xff0c;那么我们就说这个配置指令是一个简单配置项&#xff0c;否则称之为复杂配置项。例如下面这个是一个简单配置项&#xff1a;

error_page 500 502 503 504 /50x.html;

对于简单配置&#xff0c;配置项的结尾使用分号结束。对于复杂配置项&#xff0c;包含多个TOKEN串的&#xff0c;一般都是简单TOKEN串放在前面&#xff0c;复合配置块一般位于最后&#xff0c;而且其结尾&#xff0c;并不需要再添加分号。例如下面这个复杂配置项

location / {root /home/jizhao/nginx-book/build/html;index index.html index.htm;
}

指令上下文¶

nginx.conf中的配置信息&#xff0c;根据其逻辑上的意义对其进行分类&#xff0c;可以分成多个作用域或指令上下文&#xff0c;指令上下文层次关系如下&#xff1a;


  • main&#xff1a;Nginx在运行时与具体业务功能无关的参数&#xff0c;比如工作进程数、运行身份等。

  • http&#xff1a;与提供http服务相关的参数&#xff0c;比如keepalive、gzip等。

  • server&#xff1a;http服务上支持若干虚拟机&#xff0c;每个虚拟机一个对应的server配置项&#xff0c;配置项里包含该虚拟机相关的配置。

  • location&#xff1a;http服务中&#xff0c;某些特定的URL对应的一系列配置项。

  • mail&#xff1a;实现email相关的SMTP/IMAP/POP3代理时&#xff0c;共享的一些配置项。


更多配置信息可以参看Nginx安装后的缺省配置文件/usr/local/nginx/nginx.conf.default。


指令上下文&#xff0c;可能有包含的情况出现。例如&#xff1a;通常http上下文和mail上下文一定是出现在main上下文里的。在一个上下文里&#xff0c;可能包含另外一种类型的上下文多次。例如&#xff1a;如果http服务&#xff0c;支持了多个虚拟主机&#xff0c;那么在http上下文里&#xff0c;就会出现多个server上下文。

user nobody;
worker_processes 1;
error_log logs/error.log info;events {worker_connections 1024;
}http {server {listen 80;server_name www.linuxidc.com;access_log logs/linuxidc.access.log main;location / {index index.html;root /var/www/linuxidc.com/htdocs;}}server {listen 80;server_name www.Androidj.com;access_log logs/androidj.access.log main;location / {index index.html;root /var/www/androidj.com/htdocs;}}
}mail {auth_http 127.0.0.1:80/auth.php;pop3_capabilities "TOP" "USER";imap_capabilities "IMAP4rev1" "UIDPLUS";server {listen 110;protocol pop3;proxy on;}server {listen 25;protocol smtp;proxy on;smtp_auth login plain;xclient off;}
}

在这个配置中&#xff0c;上面提到个五种配置指令上下文都存在.

存在于main上下文中的配置指令如下:


  • user
  • worker_processes
  • error_log
  • events
  • http
  • mail

存在于http上下文中的指令如下:


  • server

存在于mail上下文中的指令如下&#xff1a;


  • server
  • auth_http
  • imap_capabilities

存在于server上下文中的配置指令如下&#xff1a;


  • listen
  • server_name
  • access_log
  • location
  • protocol
  • proxy
  • smtp_auth
  • xclient

存在于location上下文中的指令如下&#xff1a;


  • index
  • root

当然&#xff0c;这里只是一些示例。具体有哪些配置指令&#xff0c;以及这些配置指令可以出现在什么样的上下文中&#xff0c;需要参考nginx的使用文档.


Nginx的模块化体系


一、模块体系

Nginx的内部结构是由核心部分和一系列功能模块组成的&#xff0c;这样可以使得每个模块的功能相对简单&#xff0c;便于对系统进行功能扩展&#xff0c;各模块之间的关系如下图&#xff1a;

nginx core实现了底层的通讯协议&#xff0c;为其它模块和Nginx进程构建了基本的运行时环境&#xff0c;并且构建了其它各模块的协作基础。

http模块和mail模块位于nginx core和各功能模块的中间层&#xff0c;这2个模块在nginx core之上实现了另外一层抽象&#xff0c;分别处理与http协议和email相关协议&#xff08;SMTP/IMAP/POP3&#xff09;有关的事件&#xff0c;并且确保这些事件能被以正确的顺序调用其它的一些功能模块。

nginx功能模块基本上分为如下几种类型&#xff1a;

&#xff08;1&#xff09;event module&#xff1a;搭建了独立于操作系统的事件处理机制的框架&#xff0c;以及提供了各具体事件的处理&#xff0c;包括ngx_event_module、ngx_event_core_module和ngx_epoll_module等&#xff0c;Nginx具体使用何种事件处理模块&#xff0c;这依赖于具体的操作系统和编译选项。

&#xff08;2&#xff09;phase handler&#xff1a;此类型的模块也被直接称为handler模块&#xff0c;主要负责处理客户端请求并产生待响应内容&#xff0c;比如ngx_http_module模块&#xff0c;负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。

&#xff08;3&#xff09;output filter&#xff1a;也称为filter模块&#xff0c;主要是负责对输出的内容进行处理&#xff0c;可以对输出进行修改&#xff0c;比如可以实现对输出的所有html页面增加预定义的footbar一类的工作&#xff0c;或者对输出的图片的URL进行替换之类的工作。

&#xff08;4&#xff09;upstream&#xff1a;实现反向代理功能&#xff0c;将真正的请求转发到后端服务器上&#xff0c;并从后端服务器上读取响应&#xff0c;发回客户端&#xff0c;upstream模块是一种特殊的handler&#xff0c;只不过响应内容不是真正由自己产生的&#xff0c;而是从后端服务器上读取的。

&#xff08;5&#xff09;load-balancer&#xff1a;负载均衡模块&#xff0c;实现特定的算法&#xff0c;在众多的后端服务器中&#xff0c;选择一个服务器出来作为某个请求的转发服务器。

&#xff08;6&#xff09;extend module&#xff1a;根据特定业务需要编写的第三方模块。


二、请求处理

下面将会以http请求处理为例来说明请求、配置和模块是如何串起来的。

当Nginx读取到一个HTTP Request的header时&#xff0c;首先查找与这个请求关联的虚拟主机的配置&#xff0c;如果找到了则这个请求将会经历以下几个阶段的处理&#xff08;phase handlers&#xff09;&#xff1a;

NGX_HTTP_POST_READ_PHASE&#xff1a;读取请求内容阶段

NGX_HTTP_SERVER_REWRITE_PHASE&#xff1a;Server请求地址重写阶段

NGX_HTTP_FIND_CONFIG_PHASE&#xff1a;配置查找阶段

NGX_HTTP_REWRITE_PHASE&#xff1a;Location请求地址重写阶段

NGX_HTTP_POST_REWRITE_PHASE&#xff1a;请求地址重写提交阶段

NGX_HTTP_PREACCESS_PHASE&#xff1a;访问权限检查准备阶段

NGX_HTTP_ACCESS_PHASE&#xff1a;访问权限检查阶段

NGX_HTTP_POST_ACCESS_PHASE&#xff1a;访问权限检查提交阶段

NGX_HTTP_TRY_FILES_PHASE&#xff1a;配置项try_files处理阶段

NGX_HTTP_CONTENT_PHASE&#xff1a;内容产生阶段

NGX_HTTP_LOG_PHASE&#xff1a;日志模块处理阶段

在内容产生阶段&#xff0c;为了给一个request产生正确的response&#xff0c;Nginx必须把这个请求交给一个合适的content handler去处理。如果这个request对应的location在配置文件中被明确指定了一个content handler&#xff0c;那么Nginx就可以通过对location的匹配&#xff0c;直接找到这个对应的handler&#xff0c;并把这个request交给这个content handler去处理。这样的配置指令包括perl、flv、proxy_pass、mp4等。

如果一个request对应的location并没有直接配置的content handler&#xff0c;那么Nginx依次作如下尝试&#xff1a;

&#xff08;1&#xff09;如果一个location里面有配置random_index on&#xff0c;那么随机选择一个文件发送给客户端。

&#xff08;2&#xff09;如果一个location里面有配置index指令&#xff0c;那么发送index指令指定的文件给客户端。

&#xff08;3&#xff09;如果一个location里面有配置autoindex on&#xff0c;那么就发送请求地址对应的服务端路径下的文件列表给客户端。

&#xff08;4&#xff09;如果这个request对应的location上有设置gzip_static on&#xff0c;那么就查找是否有对应的.gz文件存在&#xff0c;如果有的话&#xff0c;就发送这个给客户端&#xff08;客户端支持gzip的情况下&#xff09;。

&#xff08;5&#xff09;请求的URI如果对应一个静态文件&#xff0c;static module就发送静态文件的内容到客户端。

内容产生阶段完成以后&#xff0c;生成的输出会被传递到filter模块去进行处理。filter模块也是与location相关的。所有的fiter模块都被组织成一条链。输出会依次穿越所有的filter&#xff0c;直到有一个filter模块的返回值表明已经处理完成。接下来就可以发送response给客户端了。




推荐阅读
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 如何提高PHP编程技能及推荐高级教程
    本文介绍了如何提高PHP编程技能的方法,推荐了一些高级教程。学习任何一种编程语言都需要长期的坚持和不懈的努力,本文提醒读者要有足够的耐心和时间投入。通过实践操作学习,可以更好地理解和掌握PHP语言的特异性,特别是单引号和双引号的用法。同时,本文也指出了只走马观花看整体而不深入学习的学习方式无法真正掌握这门语言,建议读者要从整体来考虑局部,培养大局观。最后,本文提醒读者完成一个像模像样的网站需要付出更多的努力和实践。 ... [详细]
  • 一、什么是闭包?有什么作用什么是闭包闭包是定义在一个函数内部的函数,它可以访问父级函数的内部变量。当一个闭包被创建时,会关联一个作用域—— ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • LVS实现负载均衡的原理LVS负载均衡负载均衡集群是LoadBalance集群。是一种将网络上的访问流量分布于各个节点,以降低服务器压力,更好的向客户端 ... [详细]
author-avatar
奇异果产出国_706
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有