Dead Horse's Blog

构建高性能web站点(服务器并发能力)

2012-02-19

量化方法:吞吐率(reqs/s)

通过压力测试可以获得最大吞吐量。 压测的前提条件:

  • 并发用户数(要考虑请求等待时间)
  • 总请求数
  • 请求资源描述

CPU并发计算

进程

进程的好处不仅仅在于CPU时间的轮流使用,还在于对CPU计算和I/O操作进行了很好的重叠作用
多进程导致内存开销有时候非常大,linux2.0之后,提供了轻量级线程:由clone()系统调用创建。允许共享一些资源,减少了内存开销,但是上下文切换的开销是在所难免的。

线程

有多种实现,有些由内核直接支持,有些则通过用户层面来模拟多执行流。

  • 内核支持:LinuxThreads就是一种内核级的线程库,它通过clone()来创建线程,线程完全由内核进程调度器管理,对于SMP(多处理器的服务器)支持较好,但是线程切换开销大一些。
  • 用户态模拟:完全在用户态完成,线程切换开销小,但是SMP表现较差,因为只有内核的进程调度才可以分配多个CPU时间。

进程调度器

内核中的进程调度器维护着各种状态的进程队列。在linux中,进程调度器维护着一个包括所有可运行进程的队列,称为运行队列

系统负载

在进程调度器维护的队列中,任何时刻至少存在一个进程(正在运行的进程),如果运行队列中不止一个进程的时候,说明此时CPU比较抢手,有了竞争。通过查看/proc/loadavg可以获得当前的运行队列。(top也可以查看,来源于loadavg文件)

# cat /proc/loadavg
1.63 0.48 0.21 10/20 17145

前面三个值是1,5,15分钟内的系统负载,10/200代表运行队列中有10个进程,进程总数200个。17145表示最后一个创建的进程ID。

进程切换

为了让所有的进程可以轮流使用系统资源,进程调度器在必要的时候会挂起正在运行的进程,同时恢复以前挂起的某个进程(上下文切换)。本质是把运行的进程的CPU寄存器中的数据取出存放到内核态堆栈中,同时把要载入的进程的数据放入到寄存器中(硬件上下文)还会把所有一切的状态信息进行切换

IOWait

CPU空闲且等待IO操作的时间比例IOWait值的高不能够代表系统的瓶颈在于IO

锁竞争

一般采用锁机制来控制资源的占用,当一个任务占用资源的时候,我们锁住资源,这个时候其他任务都在等待锁的释放。这种现象称为锁竞争。

内存分配

在web服务器的工作中,需要大量的内存。Nginx的内存分配策略非常牛逼。10,000 inactive HTTP keep-alive connections take about 2.5M memory

持久连接

持久连接(长连接)本身是一种TCP的一种普通方式,在一次TCP连接中持续发送多份数据而不断开连接。通过http响应头来实现。

connection: Keep-Alive
Keep-Alive: timeout=5, max=100

I/O模型

I/O根据设备的不同,分为很多种。比如内存I/O, 网络I/O, 磁盘I/O。内存I/O的速度相对后两者非常快。而后两者,虽然可以通过RAID磁盘阵列并行磁盘访问,SSD硬盘,高带宽网络等提高速度,但是相对于CPU来说,仍然是非常慢速的I/O。

PIO和DMA

慢速I/O设备和内存之间的数据传输方式:

  • PIO:需要CPU控制。要占用大量的CPU时间。
  • DMA(直接内存访问):取代了PIO,可以不经过CPU,直接进行磁盘和内存访问。

同步阻塞I/O

当前发起I/O请求的进程被阻塞,当I/O完成再返回,进程继续执行。在这个过程中,此进程必须等待数据准备和数据获取两个过程。

同步非阻塞I/O

与同步阻塞不同的是,非阻塞不需要等待数据就绪阶段,大部分是通过轮询的方式,重复询问I/O设备数据是否准备,当准备好了,则开始数据传输,传输阶段进程被阻塞。

多路I/O就绪通知

相较于同步非阻塞I/O,通过一个中间层,管理所有的I/O操作,如果任何一个I/O设备就绪,则可以通过中间层获知。之后的数据传输则可以另行选择阻塞或者非阻塞的形式进行。

  • select : 通过一个系统调用select()监视包含多个文件描述符的数组,当select()返回的时候,数组中就绪文件描述符便会被内核修改标志位,进程从而可以获得这些文件描述符从而进行后续操作。缺点在于单个进程能够监视的文件描述符数量存在上限,linux一般为1024,同时扫描数组和维护数组也有一定的开销。
  • poll : 和select在本质上没有太大的区别,不过没有上限的限制。
  • SIGIO : SIGIO相较于前两者,只会在发生变化(就绪)的时刻告诉我们文件描述符刚刚变化了,只通知一遍,如果不采取行动则不会再次告知,称之为边缘触发
  • /dev/poll : 使用了虚拟的/dev/poll设备,可以将要监视的文件描述符写入这个设备,通过ioctl()来等待时间通知。节省了扫描所有文件描述符的开销。
  • /dev/epoll : 在上者的基础上增加了内存映射(mmap)技术。
  • epoll : linux 2.6才出现了内核直接支持的实现方法,几乎具备了之前所有的优点。可以同时支持水平触发和边缘触发。默认情况下epoll采用水平触发。
  • kqueue : 性能和epoll接近,API在很多平台不支持,且文档匮乏。

异步I/O

同步和异步是指访问数据的机制,同步一般指主动请求并等待I/O操作完毕的方式,数据就绪后在读写的时候必定阻塞,异步则指主动请求后可以继续处理其他任务,I/O操作完毕的时候会通知进程。

服务器并发策略

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

早期的一种方式采用fork()的方式。主进程accept()来自客户端的请求,fork一个worker来处理。另一种方式是prefork模式,先创建一定数量的子进程,每个请求由一个子进程处理,但是每个子进程可以处理多个请求。分进程只管理子进程,等于是动态维护于一个进程池。
accept的方式有两种,父进程accept,分配给worker。或者子进程竞争accept,容易发生惊群效应。

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

Apacke的worker多路处理模块就是采用这种方式。主要牡蛎在于减少prefork模式中太多进程的开销。

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

通过多路I/O就绪通知,达到一个进程处理多个连接的效果。多路I/O就绪通知的性能是这种方式的关键。

blog comments powered by Disqus