Nginx架构珠玑

Nginx由俄罗斯软件工程师Igor Sysoev编写并开源的web服务器。

自2004年公开发布以来,nginx一直专注于高性能、高并发性和低内存消耗。

Web服务器功能之上的其他功能,在nginx上都有体现,如:

负载均衡、缓存、访问、带宽控制、与应用程序集成能力。

从而使nginx成为现代网站架构优选方案。目前,nginx是互联网上第二大受欢迎的开源web服务器。

架构概述

传统连接弊端

传统并发连接处理基于进程/线程的模型,使用单独进程/线程处理每个连接,以阻塞方式执行网络或I/O操作。内存和CPU利率性非常低。

生成单独的进程/线程,需准备新的运行时环境,包括分配堆和堆栈内存,及创建新的执行上下文。

创建这些项目还需要额外的cpu时间,最终,有可能因为过多的上下文切换,导致线程抖动造成性能不佳。

所有这些复杂性,在apache这类老式web服务器架构中随处可见。

这是在提供丰富的通用性功能和优化服务器资源利用率间的权衡。

解决方案

Nginx从一开始就以更高性能、更高效的资源利用率为目标,同时为网站动态增长而生的专业工具,它采用与传统方式不同的模式。

实际上,它受到各种操作系统,事件处理机制的影响。

以模块化、事件驱动、异步、单线程、非阻塞架构等方案构成nginx基础。

Nginx大量使用多路复用和事件通知,并用单独进程处理特定任务。

Nginx有一组被称为workers的,数量有限的进程,以单线程方式高效循环的处理连接请求。

Nginx中每个worker线程每秒可处理数千个并发连接请求。

代码结构

Nginx worker代码由核心和功能模块构成。

核心负责维护一个密集的消息循环(a tight  run-loop),请求处理的各个阶段执行对应的逻辑代码块。

功能模块大部分由表示层和应用层功能构成,模块从网络和存储设备中读取内容、写入内容,转换内容、执行出站过滤,将请求传递给后端服务器。

Nginx模块化架构允许开发人员无需修改核心即可扩展web功能集。

Nginx模块各版本略有不同,即:核心模块、事件模块、阶段处理程序、协议、变量处理程序、过滤器、上游(接入后端服务)和负载均衡器。

目前,nginx不支持动态加载模块,即:模块必须在构建阶段与核心一起编译。

未来的主版本中会支持可加载模块和ABI。

有关不同模块角色的更多详细信息后面会提到。

在处理、接受请求、管理网络连接和内容检索时,nginx使用事件通知机制,和linux、solaris、BSD-based操作系统中磁盘I/O优化技术,像kqueue、epoll、 event ports。

目标是为操作系统提供尽可能多的提示(Hints),以便为入站和出站流量、磁盘操作、读取或写入socket,超时等操作获得及时的异步反馈。

Nginx针对基于unix操作系统的多路复用、高级I/O操作运用大量优化方法。

架构抽象概述

Nginx Architecture

work模型

如前所述,nginx不会为每个连接生成单独的进程/线程。

相反,worker进程接受来自共享“侦听”的socket请求,并执行高效的消息循环(run-loop),每个worker进程处理数千个连接。

在nginx中连接分配不由nginx决定,这项工作由操作系统内核负责完成的。

Nginx启动时会创建一组初始的socket侦听,然后由worker处理http请求和响应,对socket进行接入、读取、写入。

消息循环(run-loop)是nginx worker代码中最复杂的部分。它包括全面的内部调用,且在很大程度上依赖于异步任务处理。

异步操作通过对模块化、事件通知、回调函数、微调定时器( fine-tuned timers)实现。

总体来说,一条关键原则就是尽可能的不阻塞。

Nginx唯一会阻塞场景:worker进程没有足够的磁盘空间可供使用时。

因为nginx不会为每个连接单开进程/线程,所以,通常对内存的消耗非常小且非常高效。

同时nginx也节省了cpu周期,因为,进程/线程没有在反复的创建 - 销毁模式下运行。

Nginx所要做的是检查网络和存储的状态、初始化新连接,将它们添加到运行的循环中,并异步处理直到完成。

最后连接被解除分配,并从消息循环(run-loop)中删除。

结合syscalls和精确实现,如:内存池和内核内存分配器(pool and slab memory allocators),即使在极端工作负载下,nginx也可保持较低的CPU使用率。

Nginx创建多个workers处理连接,它可以在多核上很好地扩展。

通常,worker单独运行在每个核心上,以充分利用多核架构,防止线程抖动和锁定。

此模型允许跨物理存储设备实现更高的可伸缩性,有助于提高磁盘利用率,避免阻塞磁盘I/O。

因此,在多个worker共享的工作负载下,可更有效地利用服务器资源。

对于某些磁盘的使用和cpu负载模式,应调整nginx的worker进程数量 。

这里列出了一些基础配置规则,管理员应为nginx的工作负载多尝试几种配置。

Nginx worker配置建议

若负载模式是cpu密集型,如:处理大量tcp/ip、SSL/压缩 ,此时,nginx worker数量应与cpu核心数匹配。

若主要负载是磁盘I/O,如:从存储器提供不同的内容集、或者重度代理,此时,nginx workers数量可能是核心数的1.5~2倍。

一些工程师选择worker的依据是:存储单元的数量,这种方法的效率取决于磁盘存储类型和配置。

Nginx开发人员在即将推出的版本中,解决了一个主要问题是如何避免磁盘I/O上的大部分阻塞。

如果,目前没有可满足存储性能的磁盘用于worker,那么,worker从磁盘读取/写入仍是阻塞的。

有许多机制和配置文件指令可减轻此类磁盘I/O阻塞。

最值得注意的是sendfile和AIO等选项的组合,通常会为磁盘性能带来很大的提升。

应根据数据集、nginx可用内存,及底层存储架构规划nginx安装项。

现有worker模型与嵌入式脚本结合是很大的问题(现今已支持lua脚本)。

首先,使用标准的nginx发行版,只支持嵌入perl脚本。

对此有一个简单的说法:

问题的关键是嵌入式脚本可能会阻止操作或引发意外退出。

这两种行为都会导致worker被立即挂起,从而对一个worker上的数千连接产生影响。

Nginx正准备在这方面投入更多,以便使嵌入式脚本更简单、更可靠、适用范围更广。

高并发性为何重要

如今,无处不在的互联网,是十年前难以想象的。

最早从简单的html生成可点击文本,基于NCSA,然后是Apache Web服务器,到现在全球超过20亿用户,都在使用的永久在线通信设备,互联网已发生翻天覆地的变化。

随着永久在线PC、移动设备,平板电脑的激增,互联网格局正在迅速变化,整个经济已经成为在线数字。

在线服务变得更加精细,偏向即时可用的实时信息和娱乐。

运行在线业务的安全方面也发生了重大变化。

因此,网站变得比以前复杂太多。

网站架构师面临的最大挑战之一是并发。自web服务开始以来,并发性一直在不断增长。

一个流行的网站同时为数十万,甚至数百万用户提供服务的情况并不罕见。

十年前,并发主要原因是缓慢的客户端 - ADSL或拨号连接的用户。

如今,并发性是由移动客户端,和较新应用程序组合引起,这些体系结构通常基于一直在线的连接。该连接允许客户端、使用新闻、推文、朋友订阅源等进行信息更新。

另一个助长并发的重要因素是现代浏览器的行为,可同时打开4~6个连接,以提高页面加载速度。

为说明慢客户端问题,假如一个基于apache的简单web服务器,产生一个相对较短的100 KB响应,带有文本或图像的网页。

生成或检索此页面只需几分之一秒,但将其传输到带宽为80 kbps(10 KB / s)的客户端需要10秒钟。

从本质上讲,web服务器会相对快速地提取100 KB的内容,然后在释放连接前,它会忙着将这些内容缓慢地发送到客户端(10秒钟)。

假设现在有1000个客户同时连接,请求类似的内容。

若每个客户端仅需1 MB额外内存,则会消耗1000 MB(约1 GB)内存,用于为1000个客户端提供100 KB的内容。

事实上,基于apache的典型web服务器,通常会为每个连接分配超过1 MB的内存,令人遗憾的是,几十kbps仍是移动通信的有效速度。

虽然通过增加操作系统内核socket缓冲区的大小,某种程度上可改善向慢速客户端发送内容的问题,但并不是解决该问题的通用方法,且可能产生副作用。

对于持久连接,处理并发性问题更加明显,为避免建立新http连接产生的延迟,客户端将一直保持连接,且对于每个客户端的连接,web服务器都需分配一定的内存。

因此,为应对持续增长的用户量带来的高并发问题, 网站应基于非常有效的构建块。

虽然硬件(cpu、内存、磁盘)、网络带宽、应用程序和数据存储架构等方面都很重要,而web服务器软件用于接受和处理客户端连接,应能够随着每秒连接数的增加进行非线性扩展。

Apache的问题

Apache,这种网络服务器软件,很大程度上仍然主宰着互联网,它于20世纪90年代初被开发。

最初,它的架构与当时存在的操作系统和硬件相匹配,同时也与互联网状态相匹配,这些网站通常是运行单个apache实例的独立物理服务器。

2000年初期,已无法通过复制独立的web服务器模型,满足不断增长的web服务。

尽管apache为未来的开发提供了坚实的基础,但它的架构是为每个新连接生成自己的副本,这不适合网站的非线性可伸缩性。

最终,apache成为一个通用的web服务器,专注于拥有许多不同的功能,第三方扩展,且几乎适用于任何类型的web应用程序。

然而,单个软件中拥有如此丰富的通用功能集,也致使它的可扩展性变得更低,因为,每个连接的cpu和内存使用量在增加。

因此,当服务器硬件,操作系统和网络资源不再是网站增长的主要限制时,全球的web开发人员开始寻找更有效的运行web服务器的方法。

大约十年前,著名软件工程师丹尼尔凯格尔宣称: “现在是网络服务器同时处理一万个用户的时候了”。并预测了现在称之为互联网云服务的东西。

Kegel的C10K问题清单引发了许多尝试,解决web服务器优化问题,同时处理大量客户端,nginx被证明是最成功的实践者之一。

旨在解决同时10000个连接的C10K问题,nginx在编写时,考虑到不同体系结构 - 非常适合对连接数和每秒请求数的非线性扩展。

Nginx是基于事件的,它不遵循apache为每个网页请求生成新进程/线程。

最终效果是,即使负载增加,内存和cpu使用率仍然可控。

Nginx具备在典型硬件服务器上提供数万个并发连接。

当nginx的第一个版本发布时,意味着与apache一起部署,nginx处理静态内容,如:HTML、CSS、JavaScript和图片,以减轻基于apache服务器的并发和延迟。

Nginx在开发过程中,通过使用FastCGI、uswgi/SCGI协议及分布式对象缓存(如:memcached),来提高与应用程序的集成度 。

还添加了其他功能,如:具有负载均衡和缓存的反向代理。这些附加功能使nginx成为有效的工具组合,是构建可扩展性web应用的基础架构。

2012年2月,apache 2.4.x版本向公众发布。

尽管apache最新版本增加了新的多核处理模块,增强了可扩展性和提高性能,但现在就断言它的性能、并发性、资源利用率,是否与纯事件驱动的web服务器旗鼓相当或更好还为时尚早。

不过,这要看apache服务器在新版本中的扩展性怎么样了,因为,这可大大缓解后端的压力,在典型的nginx-plus-Apache Web配置中仍然没有得到解决。

Nginx优势

以高性能和高效处理高并发始终是使用nginx的核心优势。但,现在有更多有趣的功能。

在过去几年中,web架构师已接受将其做为应用程序基础结构,然而,以前以LAMP(Linux,Apache,MySQL,PHP,Python或Perl)为基础的网站,现在已不仅仅基于LEMP(E'代表'nginx/engine x')。

越来越多的做法是将web服务器推向基础设施的边缘,以不同的方式围绕它,集成一组应用程序和数据库工具。

Nginx非常适合这一点,它提供了许多的关键功能,如:offload并发(将原本在协议栈中进行的IP分片、TCP分段、重组、checksum校验等操作,转移到网卡硬件中,降低系统cpu消耗,提高处理性能)、延迟处理、SSL(安全socket层)、静态内容、压缩和缓存、连接和请求限制。

甚至从应用程序层到边缘web服务器层的http媒体流,nginx能直接与memcached/redis、NoSQL解决方案集成,以提高并发性能。

随着最近开发套件和编程语言的广泛使用,越来越多的公司正在改变应用程序开发和部署习惯。

Nginx已成为不断变化的典范,最重要的组成部分之一,已经帮助许多公司在预算范围内,快速启动和开发web服务。

Nginx的第一行代码是在2002年编写的。2004年,根据双条款BSD许可证向公众发布。从那以后,nginx用户数量一直在增长,提出想法、错误报告和建议并提交给社区。

Nginx代码库是原创的,用C语言从头开始编写的。

Nginx已被移植到许多架构和操作系统,包括Linux,FreeBSD,Solaris,Mac OS X,AIX和Microsoft Windows。

Nginx使用自己的功能库,除zlib、PCRE、OpenSSL之外,它的标准模块不会超出系统的C核心库,若不需要或因潜在许可证冲突,可选择从构建中排除它们。

Windows版几句话

虽然nginx适用于Windows环境,但Windows版本的nginx更像是概念版,功能并不齐全,nginx与Windows内核架构存在某些限制,目前这些架构不能很好地互动。

Windows版本的nginx已知问题包括:

并发连接数量少、性能低、没有缓存,且没有带宽监管。Nginx的Windows未来版本将更接近主流功能。