[关闭]
@dungan 2018-11-15T08:20:40.000000Z 字数 5611 阅读 67

Nginx

Nginx 介绍

nginx是一个十分轻量级的 HTTP 服务器,同时也是一个 IMAP/POP3/SMTP 代理服务器,其特点是占有内存少,并发能力强!
nginx 以事件驱动的方式编写,所以有非常好的性能,能够提供非常高效的反向代理、负载平衡。其拥有匹配 Lighttpd 的性能,同时还没有 Lighttpd 的内存泄漏问题!

nginx 对比 apache

nginx

相比 apache,nginx 使用更少的资源,支持更多的并发连接,能够支持高达 50000 个并发连接数的响应,体现更高的效率
nginx是异步的,多个连接(万级别)可以对应一个进程,而apache是同步多进程模型,一个连接对应一个进程;
处理请求是异步非阻塞的,负载能力比 apache 高很多,而 apache 则是阻塞型的。在高并发下 nginx 能保持低资源低消耗高性能 ,而 apache 在 PHP 处理慢或者前端压力很大的情况下,很容易出现进程数飙升,从而拒绝服务的现象

apache

apache 的 rewrite 比 nginx 强大,在 rewrite 频繁的情况下,用 apache 在处理动态请求有优势,一般 cpu 密集型的应用适合 apache 去做

apache 和 nginx 的区别本质上是工作模型的区别

传统上基于进程或线程模型架构的web服务通过每进程或每线程处理并发连接请求,这势必会在网络和I/O操作时产生阻塞,其另一个必然结果则是对内存或CPU的利用率低下。生成一个新的进程/线程需要事先备好其运行时环境,这包括为其分配堆内存和栈内存,以及为其创建新的执行上下文等。这些操作都需要占用CPU,而且过多的进程/线程还会带来线程抖动或频繁的上下文切换,系统性能也会由此进一步下降

nginx 的工作模式

上文也谈到由于基于多进程和多线程的设计或多或少会带来性能问题,受启发于多种操作系统设计中基于“事件”的高级处理机制,nginx采用了模块化、事件驱动、内存映射,基于磁盘的异步IO、单线程及非阻塞的架构,并大量采用了多路复用及事件通知机制,使得 nginx 能够轻松应对高并发的业务,但话又说回来,IO 如果处理的连接数不是很高的话,使用 select/epoll 的 web server 不一定比使用 multi-threading + blocking I/O 的 web server 性能更好,可能延迟还更大!

apache 三种工作模式分别为prefork、worker、event。

  • prefork:多进程,每个请求用一个进程响应,这个过程会用到select机制来通知。
  • worker:多线程,一个进程可以生成多个线程,每个线程响应一个请求,但通知机制还是select不过可以接受更多的请求,系统资源的开销要小于基于进程的服务器!
  • event:基于异步I/O模型,一个进程或线程,每个进程或线程响应多个用户请求,它是基于事件驱动(也就是epoll机制)实现的,在这个过程中,进程本身一直处于空闲状态,可以一直接收用户请求,可以实现一个进程程响应多个用户请求。支持持海量并发连接数,消耗更少的资源!

选择合适的服务器软件

由于我们的应用有 io 密集型和 cpu密集型区别,同时要想做到 低延迟高吞吐 是互相矛盾的 ,不同场景下有不同的选择而已,apache 和 nginx 各有好处!
更为通用的方案是,前端负载均衡用 nginx 抗并发,后端 apache 集群,这是典型应用场景,当然由于两套服务器软件会导致维护成本!

关于IO模型

三种工作模型

多进程 : 为每个请求启动一个进程来处理。由于在操作系统中,生成进程、销毁进程、进程间切换都很消耗CPU和内存,当负载高时,性能会明显降低!

多线程方式:一个进程中用多个线程处理用户请求。由于线程开销明显小于进程,而且部分资源还可以共享,因此效率较高!

异步:异步是用来解决IO阻塞的场景的有效手段, 无需多线程,一个进程在同一时间就能维持成千上万个连接,这就是所谓的"并发",使用非阻塞方式处理请求,是三种方式中开销最小的。但异步方式虽然效率高,但要求也高,因为多任务之间的调度如果出现问题,就可能出现整体故障,因此使用异步工作的,一般是一些功能相对简单,但却符合服务器任务调度、且代码中没有影响调度的错误代码存在的程序!

接下来看下操作系统对于 web 请求的处理,有助于理解各种 IO 模型!

操作系统对于 web 请求的处理过程

  1. 客户发起情况到服务器网卡;
  2. 服务器网卡接受到请求后转交给内核处理;
  3. 内核根据请求对应的套接字,将请求交给工作在用户空间的Web服务器进程
  4. Web服务器进程根据用户请求,向内核进行系统调用,申请获取相应资源(如index.html)
  5. 内核发现web服务器进程请求的是一个存放在硬盘上的资源,因此通过驱动程序连接磁盘
  6. 内核调度磁盘,获取需要的资源
  7. 内核将资源存放在自己的缓冲区中,并通知Web服务器进程
  8. Web服务器进程通过系统调用取得资源,并将其复制到进程自己的缓冲区中
  9. Web服务器进程形成响应,通过系统调用再次发给内核以响应用户请求
  10. 内核将响应发送至网卡
  11. 网卡发送响应给用户

一次 IO 的过程分两个阶段

我们知道工作在用户空间的web服务器进程是无法直接操作IO的,需要通过系统调用进行,其关系如下:

进程向内核进行系统调用申请IO,内核将资源从IO调度到内核的buffer中(kernel wait for data阶段),内核还需将数据从内核buffer中复制(copy data from kernel to user)到 web 服务器进程所在的用户空间,才算完成一次IO调度

五种 IO 模型

根据 wait 和 copy 阶段的处理等待的机制不同,可将 I/O 动作分为如下五种模式

同步与异步,阻塞与非阻塞

各 IO 模型的详细介绍

阻塞IO

  1. 准备数据: 对于网络请求来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的 UDP 包),这个时候 kernel 就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。
  2. 数据返回:kernel 一但等到数据准备好了,它就会将数据从 kernel 中拷贝到用户内存,然后 kernel 返回结果,用户进程才解除 block 的状态,重新运行起来。

非阻塞I/O

nonblocking IO 的特点是用户进程需要不断的主动询问kernel数据好了没有

  1. 开始准备数据:如果 Kernel 中的数据还没有准备好,那么它并不会 block 用户进程,而是立刻返回一个 error。
  2. 数据准备中: 从用户进程角度讲,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个 error 时,它就知道数据还没有准备好,于是它可以再次发送 read 操作(重复轮训)。
  3. 一旦 kernel 中的数据准备好了,并且又再次收到了用户进程的 system call,那么它马上就将数据拷贝到了用户内存,然后返回。

I/O复用
也叫 event driven I/O,Linux select/epoll 的好处就在于单个 process 就可以同时处理多个网络连接的 I/O(即一个进程响应多个请求)。它的基本原理就是 select会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程

  1. 用户进程调用 select 整个进程会被 block,与此同时kernel 会 “监听” 所有 select 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返回
  2. 用户进程再调用 read 操作,将数据从 kernel 拷贝到用户进程。这时和 blocking I/O 的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个 system call (select 和 recvfrom),而 blocking I/O 只调用了一个 system call (recvfrom)。
  3. 在 I/O multiplexing Model 中,实际中,对于每一个 socket,一般都设置成为 non-blocking,但是,如上图所示,整个用户的 process 其实是一直被 block 的。只不过 process 是被 select 这个函数 block,而不是被 socket I/O 给 block

I/O 复用轮询轮询是所有的 socket(轮询队列),而非阻塞 I/O 是对某个 socket 不断轮询(不断轮询一个),即 I/O 复用发现某个 sokcet 没有数据返回则立马会去检测别的 socket 有没有数据,不会在一棵树上吊死,所以可以把 I/O 复用看做是对非阻塞 I/O 的优化

信号驱动I/O(SIGIO)
首先,我们允许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据

异步I/O(aio)
当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过 signal 通知调用者

  1. 用户进程发起 read 操作之后,并不需要等待,而是马上就得到了一个结果,立刻就可以开始去做其它的事。
  2. 从 kernel 的角度,当它受到一个 asynchronous read 之后,首先它会立刻返回,所以不会对用户进程产生任何 block。然后,kernel 会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个 signal,告诉它 read 操作完成了

    根据上面的描述,按照消息通知的类型,我们又可以将这 IO 模型分为两类

    • 同步IO:阻塞I/O,非阻塞I/O,多路复用I/O,信号驱动I/O(第二次 system call 时阻塞)
    • 异步 IO:异步I/O

上述各种 IO 模型的实现方式有

epoll(Linux实现)、kqueue(FreeBSD实现)、/dev/poll(Solaris实现)是Reacor模式,IOCP是Proactor模式,其中select和 poll 是标准的工作模型,kqueue 和 epoll 是高效的工作模型,Apache 2.2.9之前只支持select模型,2.2.9之后支持epoll模型!

IO 多路复用 select,poll,epoll 的区别

select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作 !

epoll 通过epoll_ctl() 注册一个文件描述符,一旦某个文件描述符就绪时,内核会采用类似 call back 的回调机制,迅速激活这个文件描述符,epoll_wait()便会得到通知,调用一次epoll_wait()获得就绪文件描述符时,返回的并不是实际的描述符,而是一个代表就绪描述符数量的值,拿到这些值去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里使用内存映射(mmap)技术, 避免了复制大量文件描述符带来的开销!

参考

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注