Nginx特点:
(1)更快
一方面,在正常情况下,单次请求会得到更快的响应;
另一方面,在高峰期(如有数以万计的并发请求),Nginx可以比其他Web服务器更快地响应请求
(2)高扩展性
Nginx的设计极具扩展性,它完全是由多个不同功能、不同层次、不同类型且耦合度极低的模块组成
(3)高可靠性
Nginx的高可靠性来自于其核心框架代码的优秀设计、模块设计的简单性;另外,官方提供的常用模块都非常稳定,每个worker进程相对独立,master进程在1个worker进程出错时可以快速“拉起”新的worker子进程提供服务
(4)低内存消耗
一般情况下,10000个非活跃的HTTP Keep-Alive连接在Nginx中仅消耗2.5MB的内存,这是Nginx支持高并发连接的基础
(5)单机支持10万以上的并发连接
这是一个非常重要的特性!随着互联网的迅猛发展和互联网用户数量的成倍增长,各大公司、网站都需要应付海量并发请求,一个能够在峰值期顶住10万以上并发请求的Server,无疑会得到大家的青睐。理论上,Nginx支持的并发连接上限取决于内存,10万远未封顶
(6)热部署
master管理进程与worker工作进程的分离设计,使得Nginx能够提供热部署功能,即可以在7×24小时不间断服务的前提下,升级Nginx的可执行文件。当然,它也支持不停止服务就更新配置项、更换日志文件等功能
(7)最自由的BSD许可协议
这是Nginx可以快速发展的强大动力。BSD许可协议不只是允许用户免费使用Nginx,它还允许用户在自己的项目中直接使用或修改Nginx源码,然后发布
Nginx拥有如此强大的性能和它的优秀架构设计是分不开的。
Nginx的架构设计
1、优秀的模块化设计
(1)高度抽象的模块接口
所有的模块都遵循着同样的ngx_module_t接口设计规范
(2)模块接口非常简单,具有很高的灵活性
模块的基本接口ngx_module_t足够简单,只涉及模块的初始化、退出以及对配置项的处
理,这同时也带来了足够的灵活性,使得Nginx比较简单地实现了动态可修改
(3)配置模块的设计
ngx_module_t接口有一个type成员,它指明了Nginx允许在设计模块时定义模块类型这个概念,允许专注于不同领域的模块按照类型来区别。而配置类型模块是唯一一种只有1个模块的模块类型。配置模块的类型叫做NGX_CONF_MODULE,它仅有的模块叫做ngx_conf_module,这是Nginx最底层的模块,它指导着所有模块以配置项为核心来提供功能。因此,它是其他所有模块的基础。配置模块使Nginx提供了高可配置性、高可扩展性、高可定制性、高可伸缩性
(4)核心模块接口的简单化
Nginx还定义了一种基础类型的模块:核心模块,它的模块类型叫做NGX_CORE_MODULE。目前官方的核心类型模块中共有6个具体模块,分别是ngx_core_module、ngx_errlog_module、ngx_events_module、ngx_openssl_module、ngx_http_module、ngx_mail_module模块
(5)多层次、多类别的模块设计
所有的模块间是分层次、分类别的,官方Nginx共有五大类型的模块:核心模块、配置模块、事件模块、HTTP模块、mail模块。虽然它们都具备相同的ngx_module_t接口,但在请求处理流程中的层次并不相同。就如同上面介绍过的核心模块一样,事件模块、HTTP模块、mail模块都会再次具体化ngx_module_t接口
2、事件驱动架构
事件驱动架构,简单来说,就是由一些事件发生源来产生事件,由一个或者多个事件收集器来收集、分发事件,然后许多事件处理器会注册自己感兴趣的事件,同时会“消费”这些事件
对于Nginx这个Web服务器而言,一般会由网卡、磁盘产生事件,事件模块将负责事件的收集、分发操作,而所有的模块都可能是事件消费者,它们首先需要向事件模块注册感兴趣的事件类型,这样,在有事件产生时,事件模块会把事件分发到相应的模
块中进行处理
传统Web服务器:
对于传统Web服务器而言,采用的所谓事件驱动往往局限在TCP连接建立、关闭事件上,一个连接建立以后,在其关闭之前的所有操作都不再是事件驱动,这时会退化成按序执行每个操作的批处理模式,这样每个请求在连接建立后都将始终占用着系统资源,直到连接关闭才会释放资源。要知道,这段时间可能会非常长,从1毫秒到1分钟都有可能,而且这段时间内占用着内存、CPU等资源也许并没有意义,整个事件消费进程只是在等待某个条件而已,造成了服务器资源的极大浪费,影响了系统可以处理的并发连接数。这种传统Web服务器往往把一个进程或线程作为事件消费者,当一个请求产生的事件被该进程处理时,直到这个请求处理结束时进程资源都将被这一个请求所占用
各种事件 —> 事件收集、分发者 —> 队列 ----> 事件消费者
Nginx服务器:
Nginx采用完全的事件驱动架构来处理业务,它不会使用进程或线程来作为事件消费者,所谓的事件消费者只能是某个模块(在这里没有进程的概念)。只有事件收集、分发器才有资格占用进程资源,它们会在分发某个事件时调用事件消费模块使用当前占用的进程资源,在事件收集、分发者进程的一次处理过程中,这5个事件按照顺序被收集后,将开始使用当前进程分发事件,从而调用相应的事件消费者模块来处理事件。当然,这种分发、调用也是有序的
各种事件 —> 事件收集、分发者 —> 多个事件消费者
差别:
前者是每个事件消费者独占一个进程资源,后者的事件消费者只是被事件分发者进程短期调用而已。这种设计使得网络性能、用户感知的请求时延(延时性)都得到了提升,每个用户的请求所产生的事件会及时响应,整个服务器的网络吞吐量都会由于事件的及时响应而增大。但这也会带来一个重要的弊端,即每个事件消费者都不能有阻塞行为,否则将会由于长时间占用事件分发者进程而导致其他事件得不到及时响应。尤其是每个事件消费者不可以让进程转变为休眠状态或等待状态,如在等待一个信号量条件的满足时会使进程进入休眠状态。这加大了事件消费程序的开发者的编程难度,因此,这也导致了Nginx的模块开发相对于Apache来说复杂不少
3、请求的多阶段异步处理
请求的多阶段异步处理只能基于事件驱动架构实现,就是把一个请求的处理过程按照事件的触发方式划分为多个阶段,每个阶段都可以由事件收集、分发器来触发,这些阶段也是可以重复发生的,例如,一个下载静态资源请求可能会由于请求数据过大、网速不稳定等因素而被分解为成百上千个TCP连接、数据传输、TCP连接关闭的阶段。
异步处理和多阶段是相辅相成的,只有把请求分为多个阶段,才有所谓的异步处理。也就是说,当一个事件被分发到事件消费者中进行处理时,事件消费者处理完这个事件只相当于处理完1个请求的某个阶段。什么时候可以处理下一个阶段呢?这只能等待内核的通知,即当下一次事件出现时,epoll等事件分发器将会获取到通知,再继续调用事件消费者处理请求。这样,每个阶段中的事件消费者都不清楚本次完整的操作究竟什么时候会完成,只能异步被动地等待下一次事件的通知
请求的多阶段异步处理优势在哪里?这种设计配合事件驱动架构,将会极大地提高网络性能,同时使得每个进程都能全力运转,不会或者尽量少地出现进程休眠状况。因为一旦出现进程休眠,必然减少并发处理事件的数目,一定会降低网络性能,同时会增加请求处理时间的平均时延!这时,如果网络性能无法满足业务需求将只能增加进程数目,进程数目过多就会增加操作系统内核的额外操作:进程间切换,可是频繁地进行进程间切换仍会消耗CPU等资源,从而降低网络性能。同时,休眠的进程会使进程占用的内存得不到有效释放,这最终必然导致系统可用内存的下降,从而影响系统能够处理的最大并发连接数。
根据什么原则来划分请求的阶段呢?一般是找到请求处理流程中的阻塞方法(或者造成阻塞的代码段),在阻塞代码段上按照下面4种方式来划分阶段:
(1)将阻塞进程的方法按照相关的触发事件分解为两个阶段
一个本身可能导致进程休眠的方法或系统调用,一般都能够分解为多个更小的方法或者系统调用,这些调用间可以通过事件触发关联起来
(2)将阻塞方法调用按照时间分解为多个阶段的方法调用
注意,系统中的事件收集、分发者并非可以处理任何事件。如果按照前一种方式试图划分某个方法时,那么可能会发现找出的触发事件不能够被事件收集、分发器所处理,这时只能按照执行时间来拆分这个方法了。
(3)在“无所事事”且必须等待系统的响应,从而导致进程空转时,使用定时器划分阶段
(4)如果阻塞方法完全无法继续划分,则必须使用独立的进程执行这个阻塞方法
请求的多阶段异步处理将会提高网络性能、降低请求的时延,在与事件驱动架构配合工作后,可以使得Web服务器同时处理十万甚至百万级别的并发连接,我们在开发Nginx模块时必须遵循这一原则
4、管理进程、多工作进程设计
Nginx采用一个master管理进程、多个worker工作进程的设计方式
master进程—>多个worker进程(包括cache manager进程、cache loader 进程)
这种设计带来以下优点:
(1)利用多核系统的并发处理能力
(2)负载均衡
多个worker工作进程间通过进程间通信来实现负载均衡,也就是说,一个请求到来时更容易被分配到负载较轻的worker工作进程中处理。这将降低请求的时延,并在一定程度上提高网络性能
(3)管理进程会负责监控工作进程的状态,并负责管理其行为
管理进程不会占用多少系统资源,它只是用来启动、停止、监控或使用其他行为来控制工作进程。首先,这提高了系统的可靠性,当工作进程出现问题时,管理进程可以启动新的工作进程来避免系统性能的下降。其次,管理进程支持Nginx服务运行中的程序升级、配置项的修改等操作,这种设计使得动态可扩展性、动态定制性、动态可进化性较容易实现
5、平台无关的代码实现
在使用C语言实现Nginx时,尽量减少使用与操作系统平台相关的代码,如某个操作系统上的第三方库。Nginx重新封装了日志、各种基本数据结构(如第7章中介绍的容器)、常用算法等工具软件,在核心代码都使用了与操作系统无关的代码实现,在与操作系统相关的系统调用上则分别针对各个操作系统都有独立的实现,这最终造就了Nginx的可移植性,实现了对主流操作系统的支持
6、内存池的设计
为了避免出现内存碎片、减少向操作系统申请内存的次数、降低各个模块的开发复杂度,Nginx设计了简单的内存池。这个内存池没有很复杂的功能:通常它不负责回收内存池中已经分配出的内存。这种内存池最大的优点在于:把多次向系统申请内存的操作整合成一次,这大大减少了CPU资源的消耗,同时减少了内存碎片
通常每一个请求都有一个这种简易的独立内存池,而在请求结束时则会销毁整个内存池,把曾经分配的内存一次性归还给
操作系统。这种设计大大提高了模块开发的简单性(如在前几章中开发HTTP模块时,申请内存后都不用关心它释放的问题),而且因为分配内存次数的减少使得请求执行的时延得到了降低,同时,通过减少内存碎片,提高了内存的有效利用率和系统可处理的并发连接数,从而增强了网络性能。
7、使用统一管道过滤器模式的HTTP过滤模块
有一类HTTP模块被命名为HTTP过滤模块,其中每一个过滤模块都有输入端和输出端,这些输入端和输出端都具有统一的接口。这些过滤模块将按照configure执行时决定的顺序组成一个流水线式的加工HTTP响应的中心,每一个过滤模块都是完全独立的,它处理着输入端接收到的数据,并由输出端传递给下一个过滤模块。每一个过滤模块都必须可以增量地处理数据,也就是说能够正确处理完整数据流的一部分。
这种统一管理过滤器的设计方式的好处非常明显:首先它允许把整个HTTP过滤系统的输入/输出简化为一个个过滤模块的简单组合,这大大提高了简单性;其次,它提供了很好的可重用性,任意两个HTTP过滤模块都可以连接在一起(在可允许的范围内);再次,整个过滤系统非常容易维护、增强。例如,开发了一个新的过滤模块后,可以非常方便地添加到过滤系统中,这是一种高可扩展性。又如,旧的过滤模块可以很容易地被升级版的过滤模块所替代,这是一种高可进化性;接着,它在可验证性和可测试性上非常友好,我们可以灵活地变动这个过滤模块流水线来验证功能;最后,这样的系统完全支持并发执行
8、其他一些用户模块
Nginx还有许多特定的用户模块都会改进约束属性。例如,ngx_http_stub_status_module模块提供对所有HTTP连接状态的监控,这就提高了系统可见性。而ngx_http_gzip_filter_module过滤模块和ngx_http_gzip_static_module模块使得相同的吞吐量传
送了更多的信息,自然也就提高了网络效率。当然,我们也可以开发这样的模块
参考书籍:
《深入理解Nginx:模块开发与架构解析》
————————————————
版权声明:本文为优快云博主「藏呆羊」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/zangdaiyang1991/article/details/84424260