服务器并发处理能力


3、服务器并发处理能力

   3.1 吞吐率

   3.2 CPU并发计算

   3.3 系统调用

   3.4 内存分配

   3.5 持久连接

   3.6 I/O模型

   3.6 服务并发策略

名词定义:

3.1 吞吐率(Throughput)

       :单位时间内服务器处理的请求数来描述其并发的处理能力。

3.1.1 吞吐率和压力测试

       :通过模拟足够数目的并发用户数,分别持续发送一定的HTTP请求,并统计测试持续的总时间,计算出基于这种压力下的吞吐率,即为一个平均值。

3.1.2 压力测试的前提条件

   :一些潜在的前提,那就是压力的描述和请求性质的描述:

       . 并发用户数

   :指在某一时刻同时向服务器发送请求的用户总数(HttpWatch)

       . 总请求数

       . 请求资源描述

       . 请求等待时间(用户等待时间)

       . 用户平均请求的等待时间。

       . 服务器平均请求处理的时间。

       . 硬件环境

3.2 CPU并发计算

      :服务器之所以可以同时处理多个请求,在于操作系统通过多执行流体系设计使得多个任务可以轮流使用系统资源,这些资源包括CPU、内存以及I/O等。

3.2.1 进程

       :多执行流一般实现是进程,多进程的好处并不仅仅在于CPU时间的轮流使用,还在于对CPU计算和I/O操作进行了很好的重叠利用,这里的I/O主要是指磁盘I/O和网络I/O,进程大多时间都消耗在I/O操作上,现代计算机的DMA技术可以让CPU不参与I/O操作的全进程。

3.2.2 轻量级进程

       :它是由一个新的系统调用clone()来创建,并由内核直接管理,像普通的进程一样独立存在,各自拥有进程描述符,但是这些进程已经允许共享一些资源,比如地址空间,打开的文件等。轻量级进程减少了内存的开销,并为多进程应用程序的数据共享提供了直接支持,但是其上下文切换的开销还是在所难免的。

 3.2.3 线程

       :Posix 1003.1c 为linux定义了线程的接口"pthread",有些不是由内核来直接支持,多线程只是一个普通的进程,它是由用户态通过一些库函数模拟实现的多执行流,所以多线程的管理完全在用户态完成,这种实现方式下线程切换的开销相比于进程和轻量级进程都要少些,但是它在多处理器的服务器(SMP)中表现较差,因为只有内核的进程调试器才有权利分配多个CPU时间。

       :LinuxThreads,它可以说是内核级线(Kernel-Lerel-Threads),因为它通过clone()来创建进程,它的实现原理是将线程和轻量级进程进行一对一关联,每个线程实际上就是一个轻量级进程,这样使得线程完全由内核的进程调试器来管理,所以它对于SMP的支持较好,但是线程切换的开销相比于用户态线程要多一些。

3.2.4 进程高度器

       :单CPU同时刻只有一个进程处于运行状态,而其它进程有的处于挂起状态并等待就绪,有的已经就绪并等待CPU时间片,还有的处于其它状态。内核中的进程高度器(Scheduler)维护着各种状态的进程队列,在Linux中,进程调度器维护着一个包括所有可运行进程的队列,称为“运行队列”(Run Quere),以及一个包括所有休眠进程和僵尸进程的列表。进程调试器的一项重要工作就是决定下一个运行的进程,如果运行队列中有不止一进程就按照优先级。调试器也动态的调整它们的优化级。具体可听听(视频: 嵌入式Linux性能监控和调优--华清远见嵌入式培训视http://v.youku.com/v_show/id_XMTAyODIzNTM2.html)

3.2.5  系统负载

       :在进程调试器维护的运行队列中,任何时刻至少存在一个进程,那就是正在运行的进程,正当运行队列中有不止一个进程的时候,就说明此时的CPU比较抢手,其它进程还在等着,进程调试器应该尽快让正在运行的进程释放CPU。通过 cat /proc/loadavg

1
2
[root@web102 ~] # cat /proc/loadavg
0.00  0.00  0.00  1 / 102  23534

0.00 0.00 0.00   最近1分钟、5分钟、15分钟分别计算得出的系统负载

1/102            运行队列中的进程个数/此时的进程总数

23534            代表到此为止,最后创建的一个进程ID


简单密集型CPU计算程序:

1
2
3
4
5
6
7
8
9
<?php
  $max  = 10000000000;
  $sum  = 0;
  for  ( $i  = 0;  $i  $max ; ++ $i )
   {
     $sum  +=  $i ;
   }
  echo  $sum ;
?>

3.2.6 进程切换

       :为了让所有的进程可以轮流使用系统资源,进程调试器在必要的时候挂起正在运行的进程,同时恢复以前挂起的进程,这种行为称为进程切换。 Nmon:ContextSwitch 上下文切换平均每秒次数。(单进程的上下文切换速度要比多进程的上下文切换速度快。)

3.27 IOWait

       : 它是指CPU空闲并且等待I/O操作完成的时间比例。是一个比例而不是绝对时间,并不能衡量什么东西。

3.28 锁竞争

       :大量并发请求时会以一些资源抢占竞争,需要一种机制来维护秩序,一般采用锁机制。(进程与线程的一个简单解释 http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html)、

3.3 系统调用

       :进程有用户态和内核态两种运行模式,两种模式之间切换,也需要一定的开销,当进程需要对硬件外设进来操作时(读取磁盘文件、发送网络数据等),就必须切换到内核态,当内核态的任务完成后,进程又切换回用户态。内核提供了一系列的系统调用,同时C函数(libc)将系统调用封装在编程接口中,提供给用户态的进程,用户态的进程可以直接进行系统调用,也可以使用封装了系统调用的C APi,比如write().

3.4 内存分配

   :内存机制以及内存与交换的交换数据,以及内存的分配和释放。根据算法(LRU)来进行分配。(浅谈Linux的内存管理机制http://ixdba.blog.51cto.com/2895551/541355)

       . Nginx内存分配

               它可以使用多线程来处理请求,这使得多个线程之间可以共享内存资源,从而令它的内存总体使用量大大减少,另外,它使用分阶段的内存分配策略,按需分配,及时释放,使得内在使用量保持在很小的数量范围。

       . Apache内存分配

                Apache使用多进程模型,使用基于内存池策略的内存管理方案。并将它抽象出来后移入APR库中作为通用内存管理模块,这种方案使用apache在开始运行便一次性申请大片的内存作内存池,这样在随后需要的时候只要在内在池中直接获取,而不需要再次分配,我们知道频繁的内存分配和翻译会引发一定时间的内存整理,这本身便影响了性能。内存池对于性能的弥补微不足道。

3.5 持久连接(Keep-alive)

   : 也称长连接,它本身是TCP通信的一种普通方式,即在一次TCP连接中持续发送多份数据而不断开连接,与它相反的称之为短连接,也就是建立连接后发送一份数据便断开,然后再次建立连接发送下一份数据,周而复始。(网站相关技术探究keepalive_timeout:http://cwtea.blog.51cto.com/4500217/1170957)

3.6 I/O模型

       :I/O操作主要是网络数据的接收和发送,以及磁盘文件的访问,我们将其归纳为多种模型,称为I/o模式,它们的本质区别在于CPU的参与方式。划为很多类型,例如:内存I/O、网络I/O、磁盘I/O。

           . 使用RAID磁盘陈列可以通过并行磁盘访问加快磁盘I/O速度。

3.6.1 PIO与DMA

   :PIO,很早以前,磁盘和内存之间的数据传输是需要CPU控制的,也就是说,如果我们读取磁盘文件到内存中,数据要经过CPU存储转发,这种方式称为PIO,需要占用大量CPU时间来读取文件,造成文件访问时系统几乎停止响应

   :DMA(直接内存访问,Direct Memory/Access)取代了PIO,它可以不经过CPU而直接进行磁盘和内存的数据交换。在DMA模式下,CPu只需要向DMA控制器下达指令,让DMA控制器来处理数据的传送即可,DMA控制器通过系统总线来传略数据,传送完毕再通知CPU。

3.6.2 同步阻塞I/O

   :是指当进程调用某些涉及I/O操作的系统调用或库函数时,比如accept(),send(),recv()等,进程便暂停下来,等待I/O操作完成后再继续运行,在等待时间上分为两步,一个是等待数据的就绪另一个是等待数据的复制。

       . I/O等待

       :用户请求的数据在网络上传输需要时间,而上下等待的时间称为I/O等待时间。

           . 阻塞

           :指当前发起I/O操作的进程被阻塞,并不是CPU被阻塞。

               . 同步

               :对于磁盘文件的访问,也有一个所谓的“同步”选项,即使用O_SYNC标志打开文件,在规范情况下,对磁盘文件调用write()则不同,它会在数据被复制到内核缓冲区后立即返回。如果使用O_SYNC票志打开文件,则对写文件操作产生影响,它使用write()必须等待数据真正写入磁盘后才返回。

1
2
3
[root@web102 ~]# strace -p 23361
Process 23361 attached - interrupt to quit
restart_syscall(<... resuming interrupted call ...>

3.6.3 同步非阻塞I/O

   :同步非阻塞I/O的调用不会等待数据的就绪,如果数据不可读或者不可写,它会立即告诉进程。比如我们使用非阻塞recv()接收网络数据的时候,如果网卡缓冲区中没有可接收的数据,函数就会及时返回,告诉进程没有数据可读了,相比于阻塞I/O,这种非阻塞I/O结合反复轮询来尝试数据是否就绪,防止进程被阻塞,最大的好处便在于可以在一个进程里同时处理多个I/O操作。(这花费了太多的CPU时间,使得进程处理忙碌的等待状态。)

3.6.4 多中I/O不绪通知

   :多路I/O就绪通知的出现,提供了对大量文件描述符就绪检查的高性能方案,它允许进程通过一种方法来同时监视所有的文件描述符,并可以快速获得所有就绪的文件描述符,然后只针对这些文件描述符进行数据访问。

   . select

   : 它通过一个select()系统调用来监视包含多个文件描述符的数组,当select()返回时,该数组中就绪的文件描述符便会被内核修改标识位,使得进程可以获得这些文件描述符从而进行后续的写操作。

   优点:

       1、良好的跨平台支持

   缺点:

       1、单进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核来提升这一限制

       2、select()所维护的存储大量文件描述符的数据结构,承受着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。

           . poll

           : 与select()没有本质上的区别,但是poll没有最大文件描述符限制。

           优点:

               1、select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行I/O操作,那么下次调用select()或poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的信息,这种方式称为水平触发(Level Triggered)。

           . SIGIO

           :它通过实时信号(Real Time Signal)来实现select/poll的通知方法,它们的不同之处在于select/poll告诉我们哪些文件描述符是就绪的,一直到我们读写它之前,每次select/poll都会告诉我们,而SIGIO则是告诉我们哪些文件描述符刚刚变为就绪状态,它只说一次,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发(Edge Triggered)。SIGIO几乎是linux 2.4下性能最好的多路I/O就绪通知方法。

           . /dev/poll

           : Sun 的solaris提供了新的实现方法,它使用了虚拟的/dev/poll设备,你可以将要监视的文件描述符数组写入这个设备,然后通过ioctl()来等待事件通知,当ioctl()返回就绪的文件描述符后,你可以从/dev/poll中读取所有就绪的文件描述符数组,这点于SIGIO节省了扫描所有文件描述符的开销。

           . /dev/epoll

           : linux2.4以/dev/epoll的设备补丁的形式出现,它提供了类似/dev/poll的功能,而且增加了内在映射(mmap)技术,在一定程序上提高了性能。

           . epoll

           : linux2.6内核直接支持的实现方法,它几乎具备了之前所说的一切优点,被公认为linux2.6下性能最好的多路I/O就绪通知方法。可以同时支持水平触发和边缘触发,理论上边缘角发的性能要更高一些,默认情况下epoll采用水平触发,如果要使用边缘触发,则需要在事件注册时增加EPOLLET选项。另外,epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的并不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。epoll事先通过epoll_ctl()来注册每一个文件描述符,一但某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait(0时便得到通知。

                   lighttpd:

                       src/fdevent_linux_sysepoll.c 注释掉了EPOLLET,并没有使用边缘触发方式。

                   nginx:

                       src/event/modules/ngx_epoll_module.c 使用了边缘触发。

           . kqueue

           : 它像epoll一样可以设置水平触发或边缘触发,同时kqueue还可以用来监视磁盘文件和目录,但是它的APi在很多平台都不支持,而且文档相当少。

3.6.5 内存映射

           : Linux提供了一种访问磁盘文件的特殊方式,它可以将内存中某块地址空间和我们要指定的磁盘文件相关联,从而把我们对这块内在的访问转换为对磁盘文件的访问,这种技术称为内存映射。

               . 共享型

                   : 任何对内存的写操作都同步到磁盘文件,而且所有映射同一个文件的进程都共享任意一个进程对映射内存的修改。

               . 私有型

                   :映射文件只能是只读文件,所以不可以将对内在的写同步到文件,而且多个进程不共享修改。

3.6.6 直接I/O

           :不经过内核缓冲区直接写文件,linux提供了对这种需要的支持,即在open()系统调用中增加参数选项O-DIRECT,用它打开文件便可以绕过内核缓冲区的直接访问,这样便有效避免了CPU和内存的多余时间开销。

3.6.7 sendfile

           : 在linux2.4内核中尝试着引入了一个称为khttpd的内核级web服务程序,它只处理静态文件的请求,引入它的目的便在于内核希望请求的处理尽量在内核完成,减少内核态的切换以及用户态数据的复制的开销。同时linux通过系统调用把这种机制提供给了开发者,那就是sendfile()系统调用。它可以将磁盘文件的特定部分直接传送到客户端的socket描述符,加快了静态文件的请求速度,同时也减少了CPU和内存的开销。

  3.6.8 异步I/O

           :阻塞和非阻塞是指当进程访问的数据如果尚未就绪,进程是否等等,简单说这相当于函数内部的实现区别,即未就绪时是直接返回还是等待就绪,而同步和异步是指访问数据的机制,同步一般指主动请求并等待I/O操作完毕的方式,当数据就绪后在读写的时候必须阻塞,异步则指主动请求数据后便可以继续处理其它任务,随后等待I/O操作完毕的通知,这可以使进程在数据读写时也不发生阻塞。

3.7 服务并发策略

       :从本质上讲,所有到达服务器的请求都封装在IP包中,位于网卡的接收缓冲区,这时侯,WEB服务器不断地读取这些请求,然后进行处理,并将结果写到发送缓冲区,这其中包含一系列的I/O操作和CPU计算,而设计一个并发策略的目的,就是让I/O操作和CPU计算尽量重叠进行,一方面要让CPU在I/O等待时不要空闲,另一方面让CPU在I/O设度上尽量花费最少的时间。

3.7.1 一个进程处理一个连接,非阻塞I/O

3.7.2 一个线程处理一个连接,非阻塞I/O

apache prefork模式

                   主进程预先创建一定数量的子进程,每个请求由一个子进程来处理,但是每个子进程可以处理多个请求。父进程往往只负责管理子进程,根据站点负载来调整子进程的数量,相当于动态维护一个进程池。

           3.7.3 一个进程处理多个连接,非阻塞I/O

apache worker模式

                   这种方式允许在一个进程中通过多个纯种来处理多个连接,其中每个线程处理一个连接。它的目的主要在于减少prefork模式中太多的进程开销,使apache支持更多的并发连接。

           3.7.4 一个线程处理多个连接,非阻塞I/O

nginx lighttpd worker模式

                   一个进程处理多个连接,存在一定的潜在条件,就是多路I/O就绪通知的应用。

           3.7.5 一个线程处理多个连接,异步I/O


你可能感兴趣的:(并发,web服务器)