nginx介绍(二) 架构篇

时间:2023-11-10 18:38:38

2. nginx架构总览

传统的基于进程或者基于线程的模型处理并发的方式都是为每个连接单独创建一个处理进程或线程,会在网络传输或者I/O操作上阻塞。而这对应用来说,在内存和 CPU的使用上效率都是非常低的。而且生成一个单独的进程或者线程还需要为该进程或者线程准备新的运行环境包括分配堆栈内存,还必须为它创新一个新的上下文执行环境。创建这些都消耗额外的CPU时间,这最终也会因为线程上下文来回切换导致性能非常差。以上这些问题都存在于老的web服务器架构中,比如 Apache。因此,需要在提供一些丰富的通用功能和对服务器资源合理使用这两者之间进行权衡取舍。

最初,nginx打算作为一个专业工具通过获取更高的性能,更好的比重以及对服务器资源更合理的使用来满足一个网站动态增长的要求,所以它使用了不同的模型。其实这种模型的灵感来自于各种操作系统在高级的基于事件处理机制方面的不断发展。而且这种灵感最终使模块化、事件驱动、异步模式、单个线程处理以及非阻塞式架构成为nginx代码的基础。

nginx 对多路复用技术和事件通知机制使用的非常多,而且为每个进程分配特定的任务。多个连接是在一个被限制在一定数量或者单一的被称作worker(s)的进程中通过高效的回环(run-loop)机制来处理的。对于nginx的每个worker进程来说,每秒钟可以处理几千个请求和连接。

代码结构

worker 线程的代码涵盖了nginx的核心功能模块。nginx的核心则是负责维护回环(run-loop)机制以及处理请求时在合适的阶段来执行某些模块的部分代码;各个模块则主要实现了表现层和应用层的功能,主要用来对网络存储的读写、传输内容、做对外功能的过滤以及请求服务端,这包括各种请求和代理功能开启时将请求传递给上行的web服务器。

nginx 的总体模块化架构允许开发者在不改动核心代码的情况下来扩展nginx的web服务功能。nginx的模块和通用划分方式稍有不同,包括核心模块、事件处理模块、阶段处理模块、协议模块、变量处理模块、过滤器模块、upstream模块和负载均衡模块。当时,nginx并不支持动态加载模块,也就是说,模块必须在构建nginx是和核心代码一起编译。但是支持可加载模块和ABI已经纳入之后的主版本计划中了。更多关于不同模块规则的细节将在“14.4小节”中讲到。

为了处理各种各样关于接受、处理以及管理网络内容和查询网络内容的请求动作,nginx采用了事件通知机制以及一些针对Linux、Solaris和基于 BSD操作系统的性能提升措施,比如kqueue,epoll和event ports(译者注:操作系统IO模型)。这样做的目的是尽可能多的给操作系统提示,为了使出入站流量、磁盘操作、从套接字读取内容或者向套接字写入内容、超时设定等操作可以更快的获得异步响应。也正使用不同的多路复用的方法和高级I/O操作在很大程度上优化了每一个运行在基于unix操作系统上的 nignx性能。

nginx的高层次架构总览将在图14.1中呈现。

nginx介绍(二) 架构篇

图14.1:图解nginx架构

worker进程模型

就像之前提到的一样,nginx不会为每一个连接生成一个进程或者线程。相反,是由worker进程通过一个共享的监听socket来接受新的请求,并且在每个worker进程内部通过执行一个高效回环(run-loop)来使每个worker进程可以处理数千连接。对于nginx中的worker进程来说并没有特殊的机制来种菜或者分配这些连接;这由操作系统内核机制来完成。在nginx启动的时候,一定数量的socket被创建,接着work进程通过不断的接受、读写这些socket来处理HTTP请求和响应。

其中,回环(run-loop)是worker代码中最复杂的部分。这个回环包括全面的内部调用并且极度依赖异步任务处理思想,因此,模块化、事件通知机制、广泛的回调函数的使用和调整计数器都通过异步操作来实现。总的来说,就是尽可能的使用非阻塞模式。而niginx也只有在对worker进程来说磁盘存储性能不足时才会进入阻塞模式。

一方面因为nginx不会为每一个连接都分配一个处理进程或者线程,所以非常节省内存并且在绝大多数情况下效率非常高。nginx同样在CPU的使用上也非常节省,这是因为它不需要不断的来对线程或者进程执行创建-销毁模式。而nginx需要做的就是检查网络状态、保存和初始化新的连接并将它们加到回环 (run-loop)中、异步处理直到处理完成,在这个状态连接时被释放的而且被从回环中移出。另外,由于niginx对系统调用的使用非常谨慎以及对支持借口比如线程池以及内存的slab分配模式的准确实现使nginx在极限负载的情况下出色的做到了CPU的低使用率。

另一方面由于nginx为产生多个worker进程来处理客户端连接,所以在多核情况下扩展性很好。通常,多核cpu时为每个核心分配只一个的worker 进程,这样既可以充分利用多核架构又可以防止线程上下文切换和锁竞争。而且不存在资源匮乏问题,因为资源控制机制是被单独隔离在每个worker进程中的。这种模型还可支持通过增加物理存储设备来获得更好的扩展性,促进磁盘利用率以及避免磁盘I/O阻塞。这使得在多个worker进程来共同处理负载时可以对服务器资源更有效的利用。

随着使用的硬盘和cpu的加载方式的不同,nginx的worker进程数也应该跟着调整。这里讲谈一些调整的基本规则,而系统管理员则要根据自己网站的负载情况尝试好几种配置。一般建议如下:

如果负载模式是CPU密集型的-如负责处理许多TCP/IP协议,SSL,或者压缩-那么nginx的的worker线程数必须和CPU核心数相同;

如果工作负荷很大程度上依赖于磁盘I/O-例如提供提供保存下来的内容或者数量非常大的代理工作-那么worker线程数可以使CPU核心数的1.5到2倍;

然而一些工程师却选择根据单独存储单元的个数来决定worker工作线程的数量,即使这种方法只是在一定类型和结构的存储设备上才有效。

对于nginx的开发人员来说,在接下来的版本中需要解决的一个主要问题就是如果避免磁盘I/O的大面积阻塞。目前,如果没有足够的存储性能来满足某个特定的worker产生的磁盘操作,那么这个worker可能在磁盘读写方面将一直被阻塞;针对这种情况,有许多机制和配置文件指令来缓和这个问题。最有效的方法就是将文件发送和异步IO结合来大幅提升磁盘性能。而一个niginx的构建安装应建立在对nginx来说可用的内存大小、存储架构基础这些数据之上。

另一个问题是关于现行worker模型中对嵌入式脚本进行有限支持的问题。一方面,对于标准配置的nginx来说,只支持Perl脚本;对于这个问题的简单解释是:关键是嵌入式脚本会非正常阻塞任何操作和退出,而这两种关键的操作的阻塞都会迅速导致worker线程被挂起,从而一次影响数千连接。 nignix开发者们已经计划做更多工作使嵌入式脚本能更方便,更可靠以及更合适的被广大应用程序使用。

nginx处理规则

nginx 会启动多个进程在内存中,包括一个主进程(marster进程)和多个工作进程(worker进程),还有一些专用进程,特别是缓存加载进程和缓存管理进程。所有的这些进程在nginx1.x版本中都是单线程模式。所有的进程都主要通过共享内存来进行线程间通信。其中marster进程以root用户身份运行,缓存加载进程和管理进程以及工作进程(worker进程)则以普通权限用户身份运行。

主线程(master)的主要职责如下:

1.读取并校验配置文件

2.创建、绑定以及关闭socket

3.初始化、结束以及保持工作线程(worker)线程的数量与维持在所配置的数量上

4.不启动服务的情况下重新配置

5.控制不间断的二进制升级(开始新的二进制必要时做回滚)

6.重新打开日志文件

7.编译内嵌的Perl脚本

工作进程(worker)则主要用来接收、处理客户端连接,提供反向代理和过滤功能以及做任何nginx可以做的事情。关于监控一个nginx实例的行为,系统管理员还需要关注worker进程的情况,因为worker进程真实的反应了一个web服务器的日常操作。

缓存加载进程主要负责检查磁盘上的缓存以及缓存在nginx内存中的数据库元数据。实际上,缓存加载器将nginx实例所需要的磁盘文件准备成特定的目录结构来供nginx实例来使用。缓存加载器遍历目录,检查缓存内容元数据,更新相关的共享内存,然后在任何东西被清理以及准备使用时都将起作用。

缓存管理器主要负责缓存的过期和校验。它在nginx正常操作时将一直保存在内存中,在失败的情况下会被主进程(master)重启。

nginx缓存简述

nginx 缓存是以文件系统中分层存储的方式来实现的。缓存的key是可配置的,可以根据特定请求参数的不同取值来决定什么东西可以放入缓存中。缓存的键(key) 和缓存元数据存储在一个可以被缓存加载器、缓存管理器以及worker进程都能访问的共享内存片段中。目前,没有任何文件缓存在内存中,因为缓存在内存中不如依赖使用操作系统对虚拟文件系统的优化机制。每个不同的响应的缓存都被放在不同的文件中,其中分层(等级和命名细节)由nginx配置命令来完成。当一个响应被写入缓存目录结构时,它的目录和文件名都是通过对代理链接做md5哈希得到。

将内容放入缓存的流程如下:

当 nginx从上行服务器得到响应内容以后,首先将内容写入到缓存目录结构之外的一个临时目录里;当nginx对请求处理完成以后它再重命名这个临时文件并将重命名过的文件移到缓存目录中。如果临时目录被代理到其他文件系统,那么这个临时文件在放入缓存文件后会被再复制一份到当前系统中,这样,nginx就在同一个文件系统中既拥有这个请求的缓存又拥有临时文件。而且如果已经明确不再需要缓存,那么从缓存目录结构中删除文件也是非常安全的。同时,nginx 还有第三方扩展可以支持远程管理缓存内容,后续工作中也计划将该功能整合到nginx主版本中。

(未完,待续。。。)