写给后端的Nginx初级入门教程:Nginx原理初探

在上一篇文章写给后端的Nginx初级入门教程:配置高可用集群 中,咱们使用keepalived实现了咱们Nginx服务器的高可用配置,防止由于Nginx服务器挂掉而致使整个应用挂掉的这种状况的发生。而Nginx做为当下最受欢迎的web服务器软件之一,能作到现在的地位和成就也并非没有缘由的,优秀的性能表现,可伸缩性,可修改性的设计,同时跨平台的特性以及很是低的故障率都是Nginx现今如此受欢迎的重要因素,那Nginx总体架构又是如何设计的呢?本篇文章呢(因为要了解Nginx核心技术须要很是深的技术内力,我没有(大哭)),咱们将轻轻的稍微揭开Nginx神秘面纱的一角,去探索一下Nginx内部是如何设计与运做的。java

Nginx的特性

Nginx能如此受欢迎,而且企业所普遍采用,必定是有不少技能点满了的,通过查阅相关资料,前辈们一共总结出来六点Nginx服务器相对于其余类型的web服务器软件作的更加优秀的地方,也是Nginx设计之初主要关注的地方。它们分别是:linux

  • 性能
  • 可伸缩性
  • 简单性
  • 可修改性
  • 可见性
  • 可移植性

性能:

性能我想没必要多说,这是Nginx能混到如今的核心资本,即便Nginx在其余方面作的很优秀,若是性能比不上其余的web服务器,在如今这个你们广泛比较看重性能的时代,Nginx有较大几率会受到冷遇。而Nginx和传统的程序不同的是,其余程序好比游戏可能会须要计算性能,图形渲染,网络渲染性能,而Nginx做为一款web服务器,就只能在网络领域和别人一决雌雄了。git

Nginx在网络性能这块作了大量的工做,包括使用事件驱动架构配合请求的多阶段异步处理,以及Master-workers机制的使用,都保证了在高并发场景下Nginx所展示出来的出色性能表现。github

可伸缩性:

可伸缩性也能够理解为可扩展性,好比谷歌浏览器的插件,火狐浏览器的插件等等(没想到什么合适的例子),Nginx支持添加相关的模块来加强咱们的服务,同时优秀的模块化设计容许咱们定制或者采用第三方开发的模块来知足咱们额外的业务需求。web

简单性:

简单性一般指的是组件的简单程度,每一个组件越简单,就会越容易理解和实现,也更容易被验证。固然开发Nginx组件不能为所欲为,同时要遵循Nginx模块开发统一的规范,而Nginx模块接口很是简单,具备很高的灵活性。后端

可修改性:

Nginx基于BSD开源,这意味当Nginx某些功能不能知足咱们其余的额外需求时,咱们能够修改它的代码来达到咱们的业务要求。同时Nginx也支持在咱们不重启,中止服务的前提下,修改咱们web服务器的某些配置并使之生效。(平滑重启)浏览器

可见性:

可见性呢,就是咱们整个应用对使用者的透明程度,开放程度。Nginx 有 http_stub_status_module 来实现基础的可见性,可让咱们了解到Nginx 当前一共创建了多少个连接,处理了多少个请求等等,这些监控参数可让运维人员更好的了解Nginx服务总体运行的情况,并及时的作出调整。好比当 Reading + Writing 数值比较高的时候,就意味着咱们当前的应用并发量仍是比较大的。tomcat

可移植性:

因为Nginx是基于C语言开发的,这意味着Nginx能够在多个操做系统平台上运行,同时Nginx从新封装了日志,各类数据结构等工具软件,并且核心代码皆采用与操做系统无关代码的实现方式,而涉及到与操做系统的交互,Nginx则为不一样操做系统提供了各自独立的实现,这点其实和java虚拟机有着殊途同归之妙。安全

说完了这些,Nginx又是如何实现这些骚操做的呢?接下来咱们浅入Nginx内部,从模块设计,事件驱动,请求处理,进程管理四个方面来简单地了解Nginx内部是如何设计得如此高效的。服务器

优秀的模块化设计:

Nginx和java同样,java呢是除了少数基本类型以外,其余一切皆为对象,Nginx也是如此,除了少部分核心代码以外,其余的皆为模块,Nginx模块遵循着一样的设计规范(ngx_module-t),设计规范中只要求了最核心的几个实现,好比初始化,退出,以及配置等等,这样作的好处和java接口类同样,在给了模块设计者充分自由的同时,又有效地避免了模块设计者乱来致使Nginx自己出现问题

一样的,规范(ngx_module-t)中容许咱们自定义服务类型,好比在以前的实战篇 配置详解那部分,咱们就主要说了Nginx的 全局块,events块,http块。而这些就属于咱们Nginx的模块类型。好比http模块就只负责相关的http请求的处理,而关于事件的处理则所有交给events模块处理。

同时Nginx也引入了核心模块的概念,目前Nginx一共有六个核心模块,用来处理咱们常见的

  • 日志(ngx_errlog_module)
  • 事件(ngx_events_module)
  • 安全(ngx_openssl_module)
  • 网络(ngx_http_module)
  • 邮件(ngx_mail_module)
  • 核心代码(ngx_mail_module)

这样作有什么好处呢,这意味着Nginx非模块的代码,好比Nginx的核心代码,只须要关注怎么调用这六个模块进行相应的处理就能够了,彻底不须要管它们是具体怎么实现的。一样的,Nginx框架不会约束核心模块的接口和功能。这种简洁,灵活的设计为Nginx实现动态可扩展性,动态可配置性。动态可定制性带来了极大的便利。这段话怎么理解呢,这样理解:

无论黑猫白猫,能抓住耗子的就是好猫。Nginx核心模块无论你是怎么实现的,只要实现就行。因此核心模块的实现才能够充足的发挥。固然,这一切也是须要遵照相关的规范的,可是规范只是极少的一部分,总体留给核心模块的空间是十分大的。

事件驱动架构:

在了解Nginx的事件驱动架构前,咱们先看一下传统的web服务器是如何工做的,接下来进入小剧场:

报,报tomcat大王,一个请求过来了!

这样啊,你派一个线程跟着,防止它有什么小动做,记住,等请求结束离开以后,再让那个线程回来。

在传统的web服务器中,一个请求每每会分配一个独立的线程或进程去处理,直到该线程结束,这固然没有什么问题,但是若是该请求请求到一半又想去读一下文件,这个时候就会形成IO阻塞,咱们线程就只能在那干等着等它处理完,而请求开始到请求结束的这个过程,线程都始终占用着系统资源,直到请求结束线程被销毁才会释放资源,固然,若是请求刷的一下就处理完了这没有什么问题,可是若是请求一会儿处理了几分钟,几十分钟,新的请求到来时只能额外再开新的线程,这谁顶得住,并发量稍微高一点线程数就达到最大值了。

固然,以上只是举例,tomcat在7以后就支持NIO异步IO处理了,tomcat8在linux环境中已经默认开启NIO模式。

而Nginx不同在哪呢,传统的web服务器每每是事件消费者独自占用一个进程资源,而Nginx的事件消费者只是被事件分发者短时间调用而已。好比在传统的web服务器中,当TCP创建连接的时候发生一个事件,而后连接以后交给一个进程去处理消费,这其中好比读写操做什么的都是这一个进程始终如一地去完成的。

而Nginx的独特之处就在于:

好比当tcp链接事件来的时候,会首先被咱们事件收集者,分发者收到,而后事件分发者将这个事件交给,记住,交给仅仅处理tcp连接的消费者去处理,而tcp读事件和tcp链接消费者一点关系都没有,当读事件来的时候,就分发给只负责读事件的事件消费者,而每一个事件消费者的处理都是刷的一下很是快的就处理完了,全部的事件消费者只是事件分发者进程的短时间调用而已,这种设计使得网络性能,用户感知和请求时延都获得了提高,每一个用户的请求都会获得及时的响应,整个服务器的网络吞吐量都会因为事件的及时响应而增大。

若是200个请求到达传统的web服务器,将会分配两百个线程去处理,若是传统的web服务器最大只能申请两百个线程的话,后面的用户就只有等待前面的请求完成,而Nginx则是两百个请求发起连接,链接事件消费者只把链接事件处理了,而后剩下的操做交给其余的事件消费者去处理,这样第201个请求来的时候,因为tcp链接事件消费者已经处理完了或者已经处理了大多数请求的链接,因此第201个请求也能够瞬间获得链接成功的响应。

太牛X了。

固然,这样也有弊端,就是咱们的事件消费者进程不能阻塞和休眠,好比请求来了,你负责链接的事件消费者阻塞了,那个人事件分发者就得一直等你处理完,要不链接不上我也无法执行读事件。或者负责tcp链接的事件消费者由于太闲进程睡着了,事件分发者每次调用链接事件消费者的时候还得先把它唤醒,这都是不能忍的。因此Nginx的总体实现难度要比传统的web服务器高不少。

Nginx事件处理大体图以下(画的有点丑):

请求的多阶段异步处理:

既然说到了多阶段,在Nginx可以把单个请求分割成多个阶段的也只有事件驱动机制了,因此请求的多阶段异步处理实际上就是基于Nginx自己的事件驱动架构实现的。

好比获取静态文件的HTTP请求就能够划分为如下七个阶段:

阶段 触发事件
创建tcp链接 接收到tcp中的SYN包
开始接收用户请求 接收到TCP中的ACK包表示链接创建成功
接收到用户请求并分析已经接收到的请求是否完整 接收到用户的数据包
接收到完整的用户请求后开始处理用户请求 接受到用户的数据包
由目标静态文件中读取部份内容,并直接发送给用户 接收到用户的数据包,或者接收到TCP中的ACK包表示用户已经接收到上次发送的数据包,TCP滑动窗口向前滑动。
对于非keep-alive请求,再发送完静态文件以后主动关闭链接。 接收到TCP中的ACK包表示用户已经收到以前发送的全部数据包。
因为用户关闭链接而结束请求 接收到TCP中的FIN包。

固然,对于不少计算机网络基础较差的同窗不是特别明白也没有关系,咱们这篇文章并非去分析Nginx这些操做是如何具体去实现的,而是去宏观的了解Nginx具体用了一种什么样的思路去设计和实现的。

你们这样去理解,每一个响应的事件都会有对应的专门的事件消费者去处理,因为是单一的任务(好比只处理链接或者关闭),这对于每个事件消费者来讲都是相对容易且处理迅速的,负责tcp链接的事件消费者处理过以后能够立刻投入到下一个tcp链接事件的处理中,这样可使得咱们每一个事件消费者进程都一直在快马加鞭的全速工做,在高并发的状况下就不多有进程休眠这种状况的发生,由于在高并发的场景下,每一个进程要处理的事件是很是多的,哪有功夫去睡觉。而传统的web服务器,一旦出现进程休眠,对于用户的感知就是请求的响应变慢了,而在高并发的场景下,因为一个请求对应一个进程(或线程),这个时候,若是进程不够了,系统就会去建立更多的进程,进程间的切换都会占用至关多的操做系统的资源,从而致使咱们网络性能的降低。

但是如何把一个请求划分红多个阶段的呢?通常是找到请求处理流程中的阻塞方法。

好比在使用send调用发送数据给用户时,若是使用阻塞socket句柄,当send在向操做系统内核发出数据包以后就必须把当前进程休眠,直到数据成功发送以后才能醒来。而Nginx根据不一样的触发事件把send这个过程分红两个阶段:

  1. 向操做系统内核发出数据包,不等待结果
  2. send结果返回。

所以就可使用非阻塞的socket句柄,而后把socket句柄加入到事件中,也就是你发吧,我先干别的事儿,发完了经过事件告诉我,我再来处理数据包的事儿。

而在大文件中,也能够把阻塞的方法按照时间分解成多个阶段的方法调用,好比在没有开启异步IO的状况下,把1000M 的文件处理成1000份,每份1M,处理完这1M,立刻处理其余的事情,而后再回来接着依次处理剩下的999M,这样的好处是,每次处理1m,咱们能先腾出手来去处理一下其余的事情,而不是一会儿处理1000M,干等着发送完。

若是实在没有办法把阻塞的操做拆分红多个阶段处理,Nginx便会派一个新的进程去单独处理这个阻塞方法,完成以后再发送完成事件通知。这样虽然方法是阻塞的,可是因为是额外的进程在处理,对其余的请求处理的影响是相对来讲较小的。

管理进程+多工做进程的设计:

Nginx采用master - worker 机制,这样对于每一个worker进程来讲,因为是独立的进程,因此也避免了锁带来的额外开销,若是有多个CPU的状况下,多个worker进程占用不一样的CPU核心来工做,提升了网络性能,下降了请求的平均时延,毕竟再怎么说,十个进程也要比一个进程处理起来快一点。

而咱们master进程并不针对请求作处理,主要是用来管理和监控咱们其余的worker进程,因此master并不会占用特别多的系统资源,同时还能经过进程通讯作到worker之间的负载均衡,好比请求来的时候,优先分配给压力较小的worker进程去处理。一样的,好比咱们单个worker进程挂了,因为进程之间是独立的,因此并不会影响到其余worker进程的处理。提升了整个系统的可靠性,下降了因为单个进程挂掉致使整个应用挂掉的风险。

如图所示:

下面开始技术总结:

今天呢,做为写给后端的Nginx初级入门教程最后一篇,原理篇,咱们经过对Nginx架构的设计的简单探索很是浅显地了解了一下Nginx内部是如何设计和工做的,总的来讲,本篇文章内容较为基础,对代码层面上的分析几乎没有提到,主要缘由第一呢,考虑到这是一篇初级入门教程,因此并无在代码设计上作很深的分析,更多的是架构设计,实现思路上面的宏观解释,至少让咱们在不了解代码实现以前能够粗略地知道Nginx是如何运做的,第二个则是Nginx源码太过复杂,不是我这样的菜鸟能够分析透彻的(这个是主要缘由)。

最后,很是感谢阅读本篇文章的小伙伴们,可以帮助到大家对于我来讲是一件很是开心的事儿,若是有什么疑问或者批评欢迎留言到本篇文章下方,有时间的话我会一一回复。

韩数的学习笔记目前已经悉数开源至github,必定要点个star啊啊啊啊啊啊啊

万水千山老是情,给个star行不行

韩数的开发笔记

欢迎点赞,关注我,有你好果子吃(滑稽)

附:写给后端的Nginx初级入门教程全部文章连接:

写给后端的Nginx初级入门教程:基础篇

写给后端的Nginx初级入门教程:实战篇

写给后端的Nginx初级入门教程:配置高可用集群

相关文章
相关标签/搜索