读《构建高性能Web站点》服务器并发处理能力 - 2

系统调用java

进程有两种运行模式:用户态和内核态。进程一般在用户态,这时可使用CPU和内存,而当进程须要对硬件外设进行操做的时候(如读取磁盘文件、发送网络数据),就必须切换到内核态,当在内核态的任务完成后,进程又切回到用户态。数据库

因为系统调用涉及进程从用户态到内核态的切换,致使必定的内存空间交换,这也是必定程度上的上下文切换,因此系统调用的开销一般是比较昂贵的。数组

减小没必要要的系统调用,也是Web服务器性能优化的一个方面。浏览器

咱们使用 strace 来跟踪 Nginx 的一个子进程,得到某次请求处理的一系列系统调用,以下所示:缓存

1_20150424083613.jpg

内存分配
性能优化

Apache在运行时的内存使用量是很是惊人的,这主要归咎于它的多进程模型,该模型使得Apache在运行开始便一次性申请大片的内存做为内存池。而Nginx的内存分配策略,它使用多线程来处理请求,这使得多线程之间能够共享内存资源,从而令它的内存整体使用大大减小,Nginx维持10000个非活跃HTTP持久链接只须要2.5MB内存。服务器

  • 持久链接网络

持久链接(Keep-Alive)也称为长链接。HTTP/1.1对长链接有了完整的定义,HTTP请求数据投中包含关于长链接的生命:数据结构

Connection : Keep-Alive


长链接的有效使用能够减小大量从新创建链接的开销,有效的加速性能。对于Apache这样的多进程模型来讲,若是长链接超时时间过长,那么即使是浏览器没有任何请求,而Apache仍然维持着链接的子进程,一旦并发用户数较多,那么Apache将维持着大量空闲进程,严重影响了服务器性能。多线程

I/O模型

有人说,比特天生就是用来别复制的,数据的生命意义便在于输入输出。

事实上,如何让高速的CPU和慢速的I/O设备更好的协调工做,这是从现代计算机诞生到如今一直探索的话题。

PIO与DMA

很早之前,磁盘和内存之间的数据传输是须要CPU控制的,也就是说如何读取磁盘文件到内存,数据要通过CPU存储转发,这种方式称为PIO。

后来,DMA(直接内存访问,Direct Memory Access)取代了PIO,它能够不通过CPU而直接进行磁盘和内存的数据交换。在DMA模式下,CPU只须要向DMA下达指令,由DMA来处理数据的传送便可,DMA经过系统总线来传输数据,传送完毕通知CPU,这样下降了CPU占有率。

同步阻塞I/O1_20150204174710.jpg

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

举个例子,好比你去逛街,饿了,你看到小吃城,就在一家面馆买了一碗面,交了钱,可面条作起来须要时间,你知不知道何时能够作好,只好坐在那里等,等面条作好吃完再继续逛街。—— 这里吃面条即是I/O操做。

同步非阻塞I/O1_20150204180739.jpg

在同步阻塞I/O中,进程实际上等待的时间包括两部分,一个是等待数据的就绪,另外一个是等待数据的复制(copy data from kenrel to user)。

同步非阻塞I/O的调用不会等待数据的就绪,若是数据不可读或者不可写,它会马上告诉进程。

回到买面的故事,假如你不甘心等面条作好就想去逛街,可又担忧面条作好了没有及时领取,因此你逛一会便跑回去看看面条是否作好,往返了不少次,最后虽然即便吃上了面条,可是却累得气喘吁吁。

多路I/O就绪通知1_20150205125946.jpg

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

回到买面的故事,加入你不止买了一份面,还在其余小吃店买了饺子、粥、馅饼等,这些东西都须要时间来制做。在同步非阻塞I/O模型中,你要轮流不停地去各个小吃店询问进度。如今引入多路I/O就绪通知后,小吃城在大厅里安装了一块电子屏幕,之后全部小吃店的食物作好后,都会显示在屏幕上,这样你只须要间隔性地看看大屏幕就能够了也许你还能够同时逛逛附近的商店。

须要注意的是,I/O就绪通知只是帮助咱们快速获取就绪的文件描述符,当得知就绪后,就访问数据自己而言,让然须要选择阻塞或非阻塞的方式,通常我么你选择非阻塞方式。

多路I/O就绪有不少不一样的实现:

select

select 最先于1983年出如今4.3BSD中,它经过一个select()系统调用来监视包含多个文件描述符的数组,当select()放回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程能够得到这些文件描述符从而进行后续的读写操做。

select目前在全部平台上都支持,但select的一个缺点在于单个进程可以监视文件描述符数量存在最大限制,在Linux上通常为1024,不过能够经过修改宏定义甚至从新编译内核的方式提高这一限制。

另外,select()所维护大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增加。同时,因为网络响应时间的延迟使得大量TCP链接处于非活跃状态,可是调用select()会对全部socket进行一次线性扫描,因此这也会浪费必定的开销。

poll

poll在1986年诞生于System V Release3(UNIX),它和select本质上没有多大差别,除了没有监视文件数量的限制,select 缺点一样适用于 poll。

另外,select()和poll()将就绪的文件描述符告诉进程后,若是进程没有对其进行I/O操做,那么下次调用的时候将再次报告这些文件描述符,因此通常不会丢失就绪通知的消息,这种方式称为水平触发(Level Triggered)。

SIGIO

Linxu2.4提供SIGIO,它经过实时信号(Real Time Signal)来实现select/poll的通知方式,可是它们的不一样在于,select/poll告诉咱们哪些文件描述符是就绪的,一直到咱们读写以前,每次select/poll都会告诉咱们;而SIGIO则是告诉咱们哪些文件描述符刚刚变为就绪状态,它只说一遍,若是咱们没有采起行动,那么它就不会再告诉咱们,这种方式称为边缘触发(Edge Triggered)。

/dev/poll

Sun在Solaris中提供了新的实现,它使用虚拟的/dev/poll设备,你能够将要监视的文件描述符数组写入这个设备,而后经过ioctl()来等待时间通知,当ioctl()放回就绪的文件描述符后,你能够从/dev/poll中读取全部就绪的文件描述符数组,这点相似于SIGIO。

在Linux下有不少方法能够实现相似/dev/poll的设备,可是都没有 提供直接的内核支持,这些方法在服务器负载较大时性能不稳定。

/dev/epoll

随后,名为/dev/epoll的设备以不定的形式出如今Linux2.4上,它提供了相似/dev/poll的功能,并且增长了内存映射(mmap)技术,在必定程度上提升了性能。

可是,/dev/epoll仍然只是一个补丁,Linux2.4并无将它的实现加入内核。

epoll

直到Linux2.6才出现了有内核直接支持的实现方法,那就是epoll,它被公认为Linxu2.6下性能最好的多路I/O就绪通知方法。

epoll能够同时支持水平触发和边缘出发,在默认状况下,epoll采用水平触发,若是要使用边缘出发,须要在事件注册时增长EPOLLET选项。

在Nginx的epoll模型代买(src/event/modules/ngx_epoll_module.c)中,能够看到它采用了边缘触发:

ee.events = EPOLLIN | EPOLLOUT | EPOLLET;

另一个本质的改进在于epoll采用基于事件的就绪通知方法。在select/poll中,进程只有在调用必定的方法后,内核才对全部监视的文件描述符进行扫描,而epoll事先通知epoll_ctl()来注册每个文件描述符,一旦某个文件描述符就绪时,内核会采用相似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便获得通知。

回到买面的故事,虽然有了电子屏幕,可是显示的内容是全部食品的状态,包括正在制做和已经作好的,这显然给你形成阅读上的麻烦,就好像select/poll每次返回全部监视的文件描述符同样,若是可以只显示作好的食品,随后小吃城进行了改进,就像/dev/poll同样只告知就绪的文件描述符。在显示作好的食品时,若是只显示一次,而无论你有没有看到,这就至关于边缘出发,而若是在你领取以前,每次都显示,就至关于水平触发。

但尽管如此,一旦你走远了,还得回到小吃城去看电子屏幕,能不能让你更加轻松地得到通知呢?小吃城采起了手机短信通知的方式,你只须要到小吃城管理处注册后, 即可以在餐点就绪时及时收到短信通知,这相似于epoll的事件机制。

内存映射

Linux内核提供一种访问磁盘文件的特殊方式,它能够将内存中某块地址空间和指定的磁盘文件相关联,从而把这块内存的访问转换为对磁盘文件的访问,这种技术称为内存映射(Memory Mapping)。

使用内存映射能够提升磁盘I/O性能,它无需使用read()或write()等系统调用来访问文件,而是经过mmap()系统调用来创建内存和磁盘文件的关联,而后想访问内存同样访问磁盘。

直接I/O

在Linux2.6中,内存映射和直接访问磁盘文件没有本质上差别,由于数据从进程用户态内存空间到磁盘都要通过两次复制,即"磁盘-->内核缓冲区"和"内核缓冲区-->用户态内存空间"。

引入内核缓冲区的目的在于提升磁盘文件的访问性能,由于当进程须要写磁盘文件时,实际上只是到了内核缓冲区便告诉进程已经成功。然而,对于一些复杂应用,如数据库服务器,它们为了充分提升性能,但愿绕过内核缓冲区,由本身在用户态空间实现并管理I/O缓冲区。

Linxu提供了对这种需求的支持,即在open()系统调用中增长了参数选项O_DIRECT,用它打开的文件即可以绕过内核缓冲区的直接访问,这样便避免了CPU和内存的多余开销。

在MySQL中,对于Innodb存储引擎,其自身能够进行数据和索引的缓存管理,因此对于内核缓冲区的依赖不是那么重要。

sendfile

在向Web服务器请求静态文件的过程当中,磁盘文件的数据要先通过内核缓冲区,而后到用户态内存空间,由于是不须要处理的静态数据,因此它们又被送到网卡对应的内核缓冲区,接着在被送入网卡进行发送。

数据从内核出去,又回到内核,没有任何变化。在Linux2.4的内核中,引入了一个称为khttpd的内核级Web服务器程序,它只处理静态文件的请求。引入的目的便在于内核但愿请求的处理尽可能在内核完成,减小内核态的切换以及用户态数据复制的开销。

Linux经过系统调用将这种机制提供给开发者,那就是sendfile()系统调用。它能够将磁盘文件的特定部分直接传送到表明客户端的socket描述符。

异步I/O1_20150210191402.jpg

阻塞和非阻塞是指当进程访问的数据若是还没有就绪,进程是否须要等待。

同步和异步是指访问数据的机制,同步指请求并等待I/O操做完毕方式,当数据就绪后在读写的时候必须阻塞;异步是指请求数据后即可以继续处理其余任务,随后等待I/O操做完毕的通知,这使进程在数据读写时不发生阻塞。

参考文章:高性能 IO 模型浅析


—————————— 本文同步发布于 ZHANGSR 个人我的博客  ——————————

相关文章
相关标签/搜索