Nginx介绍
原⽂:
作者:
nginx(发⾳"engine x")是俄罗斯软件⼯程师Igor Sysoev开发的免费开源web服务器软件。nginx于2004年发布,聚焦于⾼性能,⾼并发和低内存消耗问题。并且具有多种web服务器功能特性:负载均衡,缓存,访问控制,带宽控制,以及⾼效整合各种应⽤的能⼒,这些特性使nginx很适合于现代⽹站架构。⽬前,nginx已经是互联⽹上第⼆流⾏的开源web服务器软件。
14.1 为什么⾼并发重要
和⼗年前相⽐,⽬前的互联⽹已经难以想象的⼴泛应⽤和普及。从NCSA⽤Apache搭的web服务器提供的可点击的⽂本HTML,已然进化成超过20亿⼈在线的通信媒介。随着永久在线的个⼈电脑,移动终端以及平板电脑的增多,互联⽹在快速变化,经济系统也完全数字有线化。提供实时可⽤信息和娱乐的在线服务变得更加复杂精巧。在线业务的安全需求也急剧变化。⽹站⽐从前更加复杂,需要在⼯程上做的更具有健壮性和可伸缩性。
并发总是⽹站架构最⼤的挑战之⼀。由于web服务的兴起,并发的数量级在不断增长。热门⽹站为⼏⼗
万甚⾄⼏百万的同时在线⽤户提供服务并不寻常。⼗年前,并发的主要原因是由于客户端接⼊速度慢--⽤户使⽤ADSL或者拨号商务。现在,并发是由移动终端和新应⽤架构所带来,这些应⽤通常基于持久连接来为客户端提供新闻,微博,通知等服务。另⼀个重要的因素就是现代浏览器⾏为变了,他们浏览⽹站的时候会同时打开4到6个连接来加快页⾯加载速度。
举例说明⼀下慢客户端的问题,假设⼀个Apache⽹站产⽣⼩于100KB的响应--包含⽂本或图⽚的⽹页。⽣成这个页⾯可能需要1秒钟,但是如果⽹速只有80kbps(10KB/s),需要花10秒才能把这个页⾯发送到客户端。基本上,web服务器相对快速的推送100KB数据,然后需要等待10秒发送数据之后才能关闭连接。那么现在如果有1000个同时连接的客户端请求相同的页⾯,那么如果为每个客户端分配
1MB内存,就需要1000MB内存来为这1000个客户端提供这个页⾯。实际上,⼀个典型的基于Apache的web服务器通常为每个连接分配1MB内存,⽽移动通信的有效速度也通常是⼏⼗kbps。虽然借助于增加操作系统内核socket缓冲区⼤⼩,可以优化发送数据给慢客户端的场景,但是这并不是⼀个常规的解决⽅案,并且会带来⽆法预料的副作⽤。
随着持久连接的使⽤,并发处理的问题更加明显。为了避免新建HTTP连接所带来的延时,客户端需要保持连接,这样web服务器就需要为每个连接上的客户端分配⼀定数量的内存。
因此,为了处理持续增长的⽤户带来的负载和更⾼量级的并发,⽹站需要⼤量⾼效的组件。⽽另⼀⽅⾯,web服务器软件运⾏在诸如硬件(CPU,内存,磁盘),⽹络带宽,应⽤和数据存储架构等之上,这些基础设施显然也很重要。因⽽,随着同时在线数和每秒请求数的增长,web服务器性能也应该能够⾮线性扩展。
Apache不再适⽤?
Apache web服务器软件发源于1990年代,⽬前在互联⽹⽹站上占有率第⼀。Apache的架构适合当时的操作系统和硬件,并且也符合当时的互联⽹状况:⼀个⽹站通常使⽤⼀台物理服务器运⾏⼀个Apache实例。2000年之后,显然这种单服务器模型已经⽆法简单扩展来满⾜⽇益增长的web服务需求。虽然Apache为新功能开发提供了坚实的基础,但他为每个新连接派⽣⼀个进程的做法(译注:Apache从2.4版本起已经⽀持事件模型),不适合⽹站的⾮线性扩展。最终,Apache成为⼀个通⽤的web服务器软件,聚焦于功能多样化,第三⽅扩展开发,以及web应⽤开发的通⽤性。然⽽,当硬件成本越来越低,每个连接消耗的CPU和内存越来越多,使⽤这样功能繁多的单⼀软件不再具有可伸缩性。
因⽽,当服务器硬件、操作系统和⽹络设施不再成为⽹站增长的主要限制因素时,⽹站开发者开始寻求更⾼效的⼿段来架设web服务器。⼤约⼗年前,著名软件⼯程师Daniel Kegel提出:“是时候让web服
务器⽀持同时处理10000客户端了”,并且预⾔了现在称为云服务的技术。Kegel的C10K设想明显推动了许多⼈尝试解决这个问题--通过优化web服务器软件来⽀持⼤规模客户端连接的并发处理,nginx是其中做的最成功者之⼀。
为了解决10000个并发连接的C10K问题,nginx基于⼀个完全不同的架构—更适合每秒同时连接数和请求数⾮线性增长。Nginx基于事件模型,⽽没有模仿Apache为每个请求派⽣新进程或线程的做法。最终结果就是即使负载增加了,内存和CPU使⽤事件始终保持可预期。Nginx使⽤普通的硬件就能在⼀个服务器上处理数万的并发连接。
Nginx的第⼀个版本发布之后,⼀般被⽤来同Apache⼀同部署,HTML、CSS、JavaScript脚本和图⽚等静态内容由nginx处理,来降低Apache应⽤服务器的并发和延时。随着开发演进的过程,nginx增加了FastCGI、uswge和SCGI等协议的⽀持,以及对分布式内存对象缓存系统如memcached的⽀持。也增加了其他有⽤的功能,例如⽀持负载均衡和缓存的反向代理。这些附加功能使nginx成为⼀个⾼效的⼯具集,⽤于构建可伸缩的web基础设施。
2012年2⽉,Apache 2.4.x版本发布。虽然增加了新的并发处理核⼼模块和代理模块,⽤于加强可伸缩性和性能,但要说性能、并发能⼒和资源利⽤率是否能赶上或超过纯事件驱动模型的web服务器还为时尚早。Apache新版本具有了更好的性能值得⾼兴,对于
nginx+Apache的web⽹站架构,虽然这能够缓解后端潜在的瓶颈,但并不能解决全部问题。
nginx有更多的优点吗?
部署nginx最关键的好处就是能够⾼性能⾼效的处理⾼并发。同时,还有更多有意思的好处。
最近⼏年,web架构拥抱解耦的理念并且将应⽤层设施从web服务器中分离。虽然现在仅仅是将原先基于LAMP(Linux, Apache, MySQL, PHP, Python or Perl)所构建的⽹站,变为基于LEMP(E表⽰Engine x)的。但是,越来越多的实践是将web服务器推⼊基础设施的边缘,并且⽤不同的⽅法整合这些相同或更新的应⽤和数据库⼯具集。
Nginx很适合做这些⼯作。他提供了必要的关键功能⽤于⽅便将下列功能从应⽤层剥离到更⾼效的边缘web服务器层:并发、长连接处理、SSL,静态内容、压缩和缓存、连接和请求限速,以及HTTP媒体流等。Nginx同时也允许直接整合memcached、Redis或者其他的NoSQL解决⽅案,增强为处理⼤规模并发⽤户的性能。
在线代码运行器随着现代编程语⾔和开发包⼴泛使⽤,越来越多的公司改变了应⽤开发和部署的⽅式。Nginx已经成为这些改变范例之中的最重要的部件之⼀,并且已经帮助许多公司在预算内快速启动和开发他们的web服务。
Nginx开发始于2002年,2004年基于2-clause BSD授权正式对外发布。⾃发布起,Nginx⽤户就在不断增长,并且贡献提议,提交bug 报告、建议和评测报告,这极⼤的帮助和促进了整个社区的发展。
Nginx代码完全⽤C语⾔从头写成,已经移植到许多体系结构和操作系统,包括:Linux、FreeBSD、Solaris、Mac OS X、AIX以及Microsoft Windows。Nginx有⾃⼰的函数库,并且除了zlib、PCRE和OpenSSL之外,标准模块只使⽤系统C库函数。⽽且,如果不需要或者考虑到潜在的授权冲突,可以不使⽤这些第三⽅库。
谈谈关于Windows版本nginx。当nignx在Windows环境下⼯作时,Windows版本的nginx更像是概念验证版本,⽽不是全功能移植。这是由于⽬前nginx和Windows内核架构之间交互的某些限制导致。Windows版本ngnix已知的问题包括:低并发连接数、性能降低、不⽀持缓存和带宽策略。未来Windows版本的nginx的功能会更接近主流版本。
14.2 Nginx架构综览
传统基于进程或线程的模型使⽤单独的进程或线程处理并发连接,因⽽会阻塞于⽹络或I/O操作。根据不同的应⽤,就内存和CPU⽽⾔,这是⾮常低效的。派⽣进程或线程需要准备新的运⾏环境,包括在内存上分配堆和栈、⽣成⼀个新的运⾏上下⽂。创建这些东西还需要额外的CPU时间,⽽且过度的上下⽂切换引起的线程抖动最终会导致性能低下。所有这些复杂性在如Apache web服务器的⽼架构上⼀
览⽆遗。在提供丰富的通⽤应⽤功能和优化服务器资源使⽤之间需要做⼀个权衡。
最早的时候,nginx希望为动态增长的⽹站获得更好的性能,并且密集⾼效的使⽤服务器资源,所以其使⽤了另外⼀个模型。受不断发展的在不同操作系统上开发基于事件模型的技术驱动,最终⼀个模块化,事件驱动,异步,单线程,⾮阻塞架构成为nginx代码的基础。
Nginx⼤量使⽤多路复⽤和事件通知,并且给不同的进程分配不同的任务。数量有限的⼯作进程(Worker)使⽤⾼效的单线程循环处理连接。每个worker进程每秒可以处理数千个并发连接、请求。
代码结构
Nginx worker的代码包含核⼼和功能模块。核⼼负责维护⼀个紧凑的事件处理循环,并且在请求处理的每个阶段执⾏对应的模块代码段。模块完成了⼤部分展现和应⽤层功能。包括从⽹络和存储设备读取、写⼊,转换内容,进⾏输出过滤,SSI(server-side include)处理,或者如果启⽤代理则转发请求给后端服务器。
nginx模块化的架构允许开发者扩展web服务器的功能,⽽不需要修改nginx核⼼。Nginx模块可分为:核⼼、事件模块,阶段处理器,协议、变量处理器,过滤器,上游和负载均衡器等。⽬前,nginx不⽀持动态加载模块,即模块代码是和nginx核⼼代码⼀起编译的。模块动态加载和ABI已经计划在将来的某个版本开发。更多关于不同模块⾓⾊的详细信息可在14.4章到。
Nginx在Linux、Solaris和BSD系统上使⽤kqueue、epoll和event ports等技术,通过事件通知机制来处理⽹络连接和内容获取,包括接受、处理和管理连接,并且⼤⼤增强了磁盘IO性能。⽬的在于尽可能的提供操作系统建议的⼿段,⽤于从⽹络进出流量,磁盘操作,套接字读取和写⼊,超时等事件中及时异步地获取反馈。Nginx为每个基于Unix的操作系统⼤量优化了这些多路复⽤和⾼级I/O操作的⽅法。
图14.1展⽰了nginx架构的⾼层设计。
图14.1 nginx架构图
⼯作进程模型
前⾯提到过,nginx不为每个连接派⽣进程或线程,⽽是由worker进程通过监听共享套接字接受新请求,并且使⽤⾼效的循环来处理数千个连接。Nginx不使⽤仲裁器或分发器来分发连接,这个⼯作由操作系统内核机制完成。监听套接字在启动时就完成初始化,worker进程通过这些套接字接受、读取请求和输出响应。
事件处理循环是nginx worker代码中最复杂的部分,它包含复杂的内部调⽤,并且严重依赖异步任务处理的思想。异步操作通过模块化、事件通知、⼤量回调函数以及微调定时器等实现。总的来说,基本原则就是尽可能做到⾮阻塞。Nginx worker进程唯⼀会被阻塞的情形是磁盘性能不⾜。
由于nginx不为每个连接派⽣进程或线程,所以内存使⽤在⼤多数情况下是很节约并且⾼效的。同时由于不⽤频繁的⽣成和销毁进程或线程,所以nginx也很节省CPU时间。Nginx所做的就是检查⽹络和存储的状态,初始化新连接并添加到主循环,异步处理直到请求结束才从主循环中释放并删除。兼具精⼼设计的系统调⽤和诸如内存池等⽀持接⼝的精确实现,nginx在极端负载的情况下通常能做到中低CPU使⽤率。
nginx派⽣多个worker进程处理连接,所以能够很好的利⽤多核CPU。通常⼀个单独的worker进程使⽤⼀个处理器核,这样能完全利⽤多核体系结构,并且避免线程抖动和锁。在⼀个单线程的worker进程内部不存在资源匮乏,并且资源控制机制是隔离的。这个模型也允许在物理存储设备之间进⾏扩展,提⾼磁盘利⽤率以避免磁盘I/O导致的阻塞。将⼯作负载分布到多个worker进程上最终能使服务器资源被更⾼效的利⽤。
针对某些磁盘使⽤和CPU负载的模式,nginx worker进程数应该进⾏调整。这⾥的规则⽐较基本,系统管理员应根据负载多尝试⼏种配置。通常推荐:如果负载模式是CPU密集型,例如处理⼤量TCP/IP协议,使⽤SSL,或者压缩数据等,nginx worker进程应该和CPU核⼼数相匹配;如果是磁盘密集型,例如从存储中提供多种内容服务,或者是⼤量的代理服务,worker的进程数应该是1.5到2倍的CPU核⼼数。⼀些⼯程师基于独⽴存储单元的数⽬来决定worker进程数,虽然这个⽅法的有效性取决于磁盘存储配置的类型,。
Nginx开发者在下个版本中要解决的⼀个主要问题是怎么避免磁盘I/O引起的阻塞。⽬前,如果没有⾜够的存储性能为⼀个worker进程的磁盘操作提供服务,这个进程就会阻塞在磁盘读写操作上。⼀些机制和配置指令⽤于缓解这个磁盘I/O阻塞的场景,最显著的是sendfile和AIO 指令,这通常可以降低许多磁盘利⽤率。应该根据数据集(data set),可⽤内存数,以及底层存储架构等来规划安装nginx。
当前的worker模型的另⼀个问题是对嵌⼊脚本的⽀持有限。举例来说,标准的nginx发布版只⽀持Perl作为嵌⼊脚本语⾔。这个原因很简单:嵌⼊脚本很可能会在任何操作上阻塞或者异常退出,这两个⾏为都会导致worker进程挂住⽽同时影响数千个连接。将脚本更简单,更可靠地嵌⼊nginx,并且更适合⼴泛应⽤的⼯作已经列⼊计划。
nginx 进程⾓⾊
Nginx在内存中运⾏多个进程,⼀个master进程和多个worker进程。同时还有⼀些特殊⽤途的进程,例如缓存加载和缓存管理进程。在nginx 1.x版本,所有进程都是单线程的,使⽤共享内存作为进程间通信机制。Master进程使⽤root⽤户权限运⾏,其他进程使⽤⾮特权⽤户权限运⾏。
master进程负责下列⼯作:
读取和校验配置⽂件
创建、绑定、关闭套接字
启动、终⽌、维护所配置数⽬的worker进程
不中断服务刷新配置⽂件
不中断服务升级程序(启动新程序或在需要时回滚)
重新打开⽇志⽂件
编译嵌⼊Perl脚本
Worker进程接受、处理来⾃客户端的连接,提供反向代理和过滤功能以及其他nginx所具有的所有功能。由于worker进程是web服务器每⽇操作的实际执⾏者,所以对于监控nginx实例⾏为,系统管理员应该保持关注worker进程。
缓存加载进程负责检查磁盘上的缓存数据并且在内存中维护缓存元数据的数据库。基本上,缓存加载进程使⽤特定分配好的⽬录结构来管理已经存储在磁盘上的⽂件,为nginx提供准备,它会遍历⽬录,检查缓存内容元数据,当所有数据可⽤时就更新相关的共享内存项。
缓存管理进程主要负责缓存过期和失效。它在nginx正常⼯作时常驻内存中,当有异常则由master进程重启。
Nginx缓存简介
Nginx在⽂件系统上使⽤分层数据存储实现缓存。缓存主键可配置,并且可使⽤不同特定请求参数来控制缓存内容。缓存主键和元数据存储在共享内存段中,缓存加载进程、缓存管理进程和worker进程都能访问。⽬前不⽀持在内存中缓存⽂件,但可以⽤操作系统的虚拟⽂件系统机制进⾏优化。每个缓存的响应存储到⽂件系统上的不同⽂件,Nginx配置指令控制存储的层级(分⼏级和命名⽅式)。如果响应需要缓存到缓存⽬录,就从URL的MD5哈希值中获取缓存的路径和⽂件名。
将响应内容缓存到磁盘的过程如下:当nginx从后端服务器读取响应时,响应内容先写到缓存⽬录之外的⼀个临时⽂件。nginx完成请求处理后,就将这个临时⽂件重命名并移到缓存⽬录。如果⽤于代理功能的临时⽬录位于另外⼀个⽂件系统,则临时⽂件会被拷贝⼀次,所以建议将临时⽬录和缓存⽬录放到同⼀个⽂件系统上。如果需要清除缓存⽬录,也可以很安全的删除⽂件。⼀些第三⽅扩展可以远程控制缓存内容,⽽且整合这些功能到主发布版的⼯作已经列⼊计划。
14.3 Nginx配置⽂件
Nginx配置系统来⾃于Igor Sysoev使⽤Apache的经验。他认为可扩展的配置系统是web服务器的基础。当维护庞⼤复杂的包括⼤量的虚拟服务器、⽬录、位置和数据集等配置时,会遇到可伸缩性问题。对于⼀个相对⼤点的⽹站,系统管理员如果没有在应⽤层进⾏恰当的配置,那么这将会是⼀个噩梦。
所以,nginx配置为简化⽇常维护⽽设计,并且提供了简单的⼿段⽤于web服务器将来的扩展。
配置⽂件是⼀些⽂本⽂件,通常位于/usr/local/etc/nginx或/etc/nginx。主配置⽂件通常命名为f。为了保持整洁,部分配置可以放到单独的⽂件中,再⾃动地被包含到主配置⽂件。但应该注意的是,nginx⽬前不⽀持Apache风格的分布式配置⽂件(如.htaccess⽂件),所有和nginx⾏为相关的配置都应该位于⼀个集中的配置⽂件⽬录中。
Master进程启动时读取和校验这些配置⽂件。由于worker进程是从master进程派⽣的,所以可以使⽤⼀份编译好、只读的配置信息。配置信息结构通过常见的虚拟内存管理机制⾃动共享。
Nginx配置具有多个不同的上下⽂,如:main, http, server, upstream, location (以及⽤于邮件代理的 mail ) 等指令块。这些上下⽂不重叠,例如,⼀个location 指令块是不能放⼊main指令块中。并且,为了避免不必要的歧义,不存在⼀个类似于“全局web服务器”的配置。Nginx配置特意做的整洁和富有逻辑性,允许⽤户可以建⽴包含上千个指令的复杂的配置⽂件。在⼀次私⼈谈话中,Sysoev说:“全局服
务器配置中的位置、⽬录和其他⼀些指令是Apache中我所不喜欢的特性,所以这就是不在nginx实现这些的原因。”
配置语法、格式和定义遵循⼀个所谓的C风格协定。这种构建配置⽂件的⽅法以及在开源软件和商业软件中⼴泛的应⽤。通过设计,C风格配置很适合嵌套描述,富有逻辑性,易于创建、读取和维护,深受⼴⼤⼯程师喜欢。同时nginx的C风格配置也易于⾃动化。
虽然⼀些nginx配置指令看起来像Apahce配置的⼀部分,但是设置⼀个nginx实例是完全不同的体验。例如,虽然nginx⽀持重写规则,但是系统管理员要⼿⼯的转换Apache重写配置使之适合nginx风格。同样,重写引擎的实现也是不⼀样的。
通常来说,nginx设置也提供了⼏种原始机制的⽀持,对于⾼效的web服务器配置很有帮助。有必要简单了解下变量和try_files指令,这些差不多是nginx所独有的。Nginx开发了变量⽤于提供附加的更强⼤的机制来控制运⾏时的web服务器配置。变量为快速赋值做了优化,并且在内部预编译为索引。赋值是按需计算的,例如,变量的值通常只在这个请求的⽣命周期中计算⼀次,⽽后缓存起来。变量可在不同的配置指令中使⽤,为描述条件请求处理⾏为提供了更多弹性。
try_files指令对于⽤更适当的⽅式逐渐替换if 条件配置语句是很重要的,并且它设计来快速⾼效的尝试不同的URI与内容之间的映射。总的来说,try_files指令很好⽤,并且及其⾼效和有⽤。推荐读者完整
的看看这个指令,并在任何能⽤的地⽅⽤上它。
14.4 深⼊nginx
前⾯提到过,nginx代码包含核⼼和其他模块。核⼼负责提供web服务器的基础,web和邮件反向代理功能;实现底层⽹络协议,构建必要的运⾏环境,并且保证不同模块之间的⽆缝交互。但是,⼤部分协议相关以及应⽤相关的特性是由其他模块完成,⽽不是核⼼模块。
在内部,nginx通过模块流⽔线或模块链处理连接。换⾔之,每个操作都有⼀个模块做对应的⼯作。例如:压缩,修改内容,执⾏SSI,通过FastCGI或uwsgi协议同后端应⽤服务器通信,以及同memcached通信等。
在核⼼和实际功能模块之间,有两个模块http 和 mail。这两个模块在核⼼和底层组件之间提供了附加抽象层。这些模块处理同各⾃应⽤层协议相关的事件序列,如实现HTTP、SMTP或IMAP。与核⼼⼀起,这些上层模块负责以正确的次序调⽤各⾃的功能模块。虽然⽬前HTTP 协议是作为http模块的⼀部分实现的,但将来计划将其独⽴为⼀个功能模块,以⽀持其他协议,如SPDY(参考“SPDY: An experimental protocol for a faster web”)。
功能模块可以分为事件模块,阶段处理器,输出过滤器,变量处理器,协议模块,上游和负载均衡器
等类型。虽然事件模块和协议也⽤于mail模块,但是这些模块⼤部分⽤于补充nginx的HTTP功能。事件模块提供了基于操作系统的事件通知机制,如kqueue 或 epoll,这些取决于操作系统的能⼒和构建配置。协议模块允许nginx通过HTTPS, TLS/SSL, SMTP, POP3 和 IMAP等协议通信。
⼀个典型的HTTP请求处理周期如下:1. 客户端发送HTTP请求。2. nginx核⼼从配置⽂件查匹配该请求的位置,根据这个位置信息选择适当的阶段处理器。3. 如果配置为反向代理,负载均衡器挑选⼀个上游服务器⽤于转发请求。4. 阶段处理器完成⼯作,并且传递每个输出缓冲区给第⼀个过滤器。5. 第⼀个过滤器传递输出给第⼆个过滤器。6. 第⼆个过滤器传递输出给第三个等等。7. 最终响应发送给客户端。
Nginx模块是⾼度可定制化的。它通过⼀系列指向可执⾏函数的回调指针来⼯作。因⽽,带来的副作⽤就是为第三⽅开发者加重了负担,因为他们必须精确的定义模块应怎么运⾏和何时运⾏。Nginx的API和开发者⽂档都经过优化使之更具有可⽤性来减轻开发难度。
⼀些在nginx中插⼊模块的例⼦:
配置⽂件读取和处理之前
Location和server的每个配置指令⽣效时
Main配置初始化时
Server配置初始化时
Server配置合并到main配置时
Location配置初始化或者合并到上级server配置时
Master进程启动或退出时
新的worker进程启动或退出时
处理请求时
过滤响应头和响应体时
挑选,初始化和重新初始化上游服务器时
处理上游服务器响应时
完成与上游服务器的交互时
在Worker内部,⽣成响应的过程如下:
1. 开始ngx_worker_process_cycle()
2. 通过操作系统的机制处理事件(如 epoll 或 kqueue)。
3. 接受事件并调⽤对应的动作。
4. 处理或转发请求头和请求体。
5. ⽣成响应内容,并流式发送给客户端。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论