简单分析 nginx 与 apache 的性能 *****
"Apache 就像 Microsoft 的 Word,它有一百万个选项,但你只需要做六个。Nginx 只做了这六件事,但他做的这六件事中有五件事比 Apache 快 50 倍"
常见的 web 服务器:nginx apache lighttpd tomcat jetty iis
web 服务器的基本功能:基于 REST 架构风格,以统一资源描述符或者统一资源定位符作为沟通依据,通过 HTTP 为浏览器等客户端程序提供各种网络服务
tomcat 和 jetty 面向 java 语言,先天就是重量级的 WEB 服务器,他的性能与 nginx 没有可比性,iis 只能在 windows 上运行,windows 作为服务器在稳定性上与其它一些性能上都不如类 UNIX 操作系统
apache 有很多优点,如稳定,开源,跨平台等,但是它是一个重量级的,不支持高并发的 web 服务器
nginx 和 lighttpd 一样,都是轻量级的,高性能的 web 服务器,欧美开发者更钟爱 lighttpd, 而国内的开发者更青睐 nginx
###nginx###
nginx 是一个跨平台的 web 服务器,可以运行在 linux,freebsd,solaris,aix,macos,windows 等操作系统上,并且它可以使用当前操作系统特有的一些高效 API 来提高性能
nginx 设计的目的是为了解决 c10k 问题
对于处理大规模并发连接,他支持 linux 上的 epoll,windows 无 epoll, 所以在 windows 下的 nginx 服务器会损失性能
对于 linux,nginx 支持其独有的 sendfile 系统调用,这个系统调用可以高效的把硬盘上的数据发送到网络上,不需要先把硬盘数据复制到用户态内存上再发送,这极大减少了内核态与用户态数据间的复制动作
nginx 使用基于事件驱动的架构能够并发处理百万级别的 TCP 连接:
事件驱动架构:由一些事件发生源来产生事件,由一个或者多个事件收集器来收集,分发事件然后许多事件处理器会注册自己感兴趣的事件,同时会消费这些事件
nginx 采用完全的事件驱动架构来处理业务,这与传统的 WEB 服务器(如 apache)是不同的。对于传统的 web 服务器而言,采用的所谓事件驱动往往局限在 TCP 连接建立,关闭事件上,一个连接建立以后,在其关闭之前的所有操作都不再是事件驱动,这时会退化成按序执行每个操作的批处理模式,这样每个请求在连接建立以后都始终占用着系统资源,知道连接关闭才会释放资源,整个事件消费进程只是在等待某个事件而已,造成了服务器资源的极大浪费,影响了系统可以处理的并发连接数,这种传统的 web 服务器往往会把一个进程或者线程作为事件消费者,当一个请求的事件被该进程处理时,直到这个请求处理结束时进程资源都将被这一个请求所占用,nginx 不会使用进程或者线程作为事件消费者,事件消费者只能是某个模块,只有事件收集、分发器才有资格占用进程资源,他们会在分发某个事件时调用事件消费模块使用当前占用的进程资源。
传统 web 服务器与 nginx 间的重要区别:前者是每个事件消费者独占一个进程资源,后者的事件消费者只是被事件分发者进程短期调用而已。这种设计使的网络性能,用户感知的请求时延都得到了提升,每个用户的请求所产生的事件会及时响应,整个服务器的网络吞吐量都会由于事件的及时响应而增大。但这也会带来一个重要的弊端,即每个事件消费者都不能由阻塞行为,否则将会由于长时间占用事件分发者进程而导致其他事件得不到及时响应。尤其是每个事件消费者不可以让进程转变为休眠状态或等待状态
请求的多阶段异步处理:
请求的多阶段异步处理只能基于事件驱动架构实现,把一个请求的处理过程按照事件的触发方式划分为多个阶段,每个事件都可以由事件收集、分发器来触发
异步处理和多阶段是相辅相成的,只有把请求分为多个阶段,才有所谓的异步处理。也就是说,当一个事件被分发到事件消费者中处理时,事件消费者处理完这个事件只相当于处理完 1 个请求的某个阶段,等待内核的通知才可以处理下一个阶段,即当下一次事件出现时,epoll 等事件分发器将会获取到通知,在继续地调用事件消费者处理请求,这样每个阶段中的事件消费者都不清楚本次完整的操作究竟什么时候才能完成,只能异步被动的等待下一个事件的通知
请求的多阶段异步处理配合事件驱动架构,将会极大的提高网络性能,同时使得每个进程都能全力运转,不会或者尽量少的出现进程休眠状态。
管理进程,多工作进程设计:
nginx 采用一个 master 管理进程,多个 worker 工作进程的设计方式:
(1)利用多核系统的并发处理能力:nginx 中的所有 worker 工作进程都是完全平等的,这提高了网络性能,降低了请求的时延
(2)负载均衡:一个请求到来时更容易被分配到负载较轻的 worker 工作进程中处理,这降低了请求的时延,并在一定程度上提高网络性能
(3)管理进程会负责监控工作进程的状态,并负责管理其行为:管理进程不会占用多少系统资源,他只是用来启动,停止,监控或者使用其他行为来监控工作进程。首先,这提高了系统的可靠性,当工作进程出现问题时,管理进程可以启动新的工作进程来避免系统性能的下降,其次,管理进程支持 nginx 服务运行中的程序升级、配置项的修改等操作,这种设计使得动态可扩展性、动态定制性、动态可进化性较容易实现
热部署:master 管理进程与 worker 工作进程的分离设计,使 nginx 能够提供在 7*24 小时不间断服务的前提下,升级 nginx 的可执行文件,也支持不停止服务就更新配置项,更换日志文件等功能
如何解决惊群问题:
只有打开了 accept_mutex 锁,才可以解决惊群问题
惊群问题:master 进程开始监听 web 端口, fork 出多个 worker 子进程,这些子进程开始同时监听同一个 web 端口。一般情况下,有多少 cpu 核心,就会配置多少个 worker 子进程,这样所有的 worker 子进程都在承担着 web 服务器的角色,在这种情况下,就可以利用每一个 cpu 核心可以并发工作的特性,充分发挥多核的作用。假设这样一个情景:没有用户连入服务器,某一时刻恰好所有的 worker 子进程都休眠且等待新连接的系统调用(如 epoll_wait), 这时有一个用户向服务器发起了连接,内核在收到 TCP 的 SYN 包时,会激活所有的休眠 worker 子进程,当然,此时只有最先开始执行 accpet 的子进程可以成功建立新连接,而其他 worker 子进程都会 accept 失败,这些 accept 失败的子进程被内核唤醒是不必要的,他们唤醒后的执行很可能是多余的,那么这一时刻他们占用的本不需要占用的系统资源,引发了不必要的进程上下文切换,增加了系统开销
"惊群" 是多个子进程同时监听同一个端口引起的,如果同一时刻只能由唯一一个 worker 子进程监听 web 端口, 就不会发生惊群了,此时新连接事件只能唤醒唯一正在监听端口的 worker 进程
如何实现负载均衡:
只有打开了 accept_mutex 锁,才可以解决 worker 子进程间的负载均衡
支持 mmap 内存映射:
传统的 web 服务器,都是先将磁盘的内容县复制到内核缓存中,然后从内核缓存中复制一份到 web 服务器上,mmap 就是让内核缓存与磁盘进行映射,web 服务器直接复制就可以,不用将磁盘上的内容复制到内核缓存中
###apache
apache 的三种工作模式:
(1)prefork:prefork 模式算是很古老但是非常稳定的 apache 模式。apache 在启动之初,就先 fork 一些子进程,然后等待请求进来。之所以这样做,是为了减少频繁创建和销毁进程的开销。每个子进程只有一个线程,在一个时间点内,只能处理一个请求
优点:成熟稳定,兼容所有新老模块。同时,不需要担心线程安全问题
缺点:一个进程相对占用更多的系统资源,消耗更多的内存。而且,他并不擅长处理高并发请求,在这种场景下,它会将请求放进队列里,一直等到有可用进程,请求才会被处理
(2)worker: 和 prefork 模式相比,worker 使用了多线程和多进程的混合模式,worker 模式也同样会先预派生一些子进程,然后每个子进程创建一些线程,同时包括一个监控线程,每个请求过来会被分配到一个线程来服务。线程比进程更轻量,因为线程是通过共享父进程的内存空间,因此,内存的占用会减少一些,在高并发的场景下会比 prefork 有更多可用的线程,表现更优秀些
优点:占用更少的内存,高并发下表现优秀
缺点:必须考虑线程安全的问题,因为多个子线程是共享父进程的内存地址的。如果使用 keep-alive 长连接的方式,某个线程会一直被占据,也许中间几乎没有请求,需要一直等待到超时才会被释放。如果太多的线程,被这样占据,也会导致在高并发场景下的无服务线程可用
(3)event:它和 worker 模式很像,不同的是它解决了 keep-alive 长连接的时候占用线程资源被浪费的问题,在 event 模式中,会有一些专门的线程来管理这些 keep-alive 类型的线程,当有真实请求过来的时候,将请求传递给服务器的线程,执行完毕后,又允许它释放,增强了在高并发场景下的请求处理
###epoll 与 select、poll 的比较
假设有 100 万用户同时与一个进程保持着 TCP 连接,而每一个时刻只有几十个或者几百个 TCP 连接是活跃的,也就是说,在每一时刻,进程只需要处理这 100 万连接中的一小部分连接。select 和 poll 在每次收集事件时,都把这 100 万连接的套接字传给操作系统(这首先时用户态内存到内核态内存的大量复制),而由操作系统内核寻找这些连接上有没有未处理的事件,将是巨大的资源浪费,因此他们最多只能处理几千个并发连接。而 epoll 没有这样做,它在 linux 内核系统内申请了一个简易的文件系统,把原先的一个 select 或者 poll 调用分成了三个部分:调用 epoll_create 建立一个 epoll 对象、调用 epoll_ctl 向 epoll 对象添加这 100 万连接的套接字、调集 epoll_wait 收集发生事件的连接。这样,只需要在进程启动时建立一个 epoll 对象,并在需要的时候向他添加或者删除连接就可以了,因此,在实际收集事件时,epoll_wait 的效率就会非常高,因为调用 epoll_wait 时并没有向它传递这 100 万个连接,内核也不需要去遍历全部的连接
###cgi 与 fastcgi
cgi 为每一个请求产生一个唯一的进程,从一个请求到另一个请求,内存和其他的信息将全部丢失
fastcgi 使用了能够处理多个请求的持续过程,而不是针对每个请求都产生新的进程
来源: http://www.bubuko.com/infodetail-2448085.html