1.这里的吞吐率特指Web服务器单位时间内处理的请求。
2.压力测试的前提:1>并发用户数 2>总请求数 3>请求资源描述
3.用户平均请求等待时间主要用户衡量服务器在一定并发用户数的情况下,对于单个用户的服务器质量;而服务器平均请求处理时间与前者相比,则用于衡量服务器的整体服务质量,它其实就是吞吐率的倒数。
4.对http header中标记为Connection: Keep-Alive的请求,开启web服务器的长连接支持。减少系统调用accept的次数,即减少建立连接的开销。
5.进程,内核级线程和用户级线程在不同情况下的优劣。IO模型,mmap(内村映射),直接IO,例如sendfile syscall以及异步IO等。多路IO复用(select, poll,epoll and kqueue etc)
6.服务器并发策略
1> 一个进程处理一个连接,非阻塞IO。稳定性强,但context switch的开销随http request递增而快速增长。
2> 一个内核级线程处理一个连接,非阻塞IO,多进程多线程混合方式。Context switch的问题依然存在。理论上可以支持更多的并发连接。
3>一个进程处理多个连接,非阻塞IO。(epoll, kqueue)lighttpd, nginx。支持并发性能强劲。
吞吐率
这里的吞吐率特指Web服务器单位时间内处理的请求。
吞吐率:一个衡量web服务性能的指标,表征每秒处理请求的次数。该指标受到3方面的因素影响:并发用户数、总请求数、请求资源的类型。有时在请求总数一定的情况下,并发用户越多,吞吐率反而越高;另外,请求一个几kb的文件和请求一个几m的文件,最终完成处理的时间显然是不一样的。因此,吞吐率是一个比较综合的指标,并不是指并发能力。
cpu并发计算
多进程、多线程的选择和调度:进程切换和线程切换都需要一定的系统开销,通常使用多线程模型的web服务器软件比使用多进程,具备更优的性能。
一个进程被挂起的本质就是将它在CPU寄存器中的数据拿出来暂存在内核态堆栈中,而一个进程恢复工作的本质就是将它的数据重新装入CPU寄存器,这段装入和移出的数据称为硬件上下文
希望服务器支持较大的并发数,就要尽量减少上下文切换次数,最简单的做法是减少进程数,尽量使用线程并配合其它I/O模型来设计并发策略。
系统调用
系统调用:一些需要从用户模式切换到内核模式的函数调用可以称为系统调用,比如:打开文件。系统调用会有一定程度上的开销,减少系统调用是可以加快处理速度的程序设计细节。
进程通常运行在用户态,可以使用CPU和内存来完成一些任务,而当进程需要对硬件外设进行操作的时候(读取磁盘文件,发送网络数据),就必须切换到内核态
由于系统调用设计进程从用户态到内核态的切换,导致一定的内存空间交换,这也是一定程度上的上下文切换,所以系统调用的开销通常认为是比较昂贵
内存分配
nginx多线程来处理请求,多个线程之间可以共享内存资源,分阶段的内存分配策略,按需分配,及时释放,使内存总体使用量保持在很小的数量范围,10000个非活跃HTTP持久连接只需要2.5MB内存
持久连接
TCP链接保持:可以通过保持TCP链接来减少服务端和客户端之间的创建和关闭TCP链接的操作。HTTP中的Connection:Keep-Alive就有这样的功能
I/O模型
IO模型:由于CPU的速度远远比IO快,IO延迟往往成为性能瓶颈,因此,IO模型十分重要。
同步阻塞I/O:对于进程来说,一些系统调用为了同步IO,会不同程度上阻塞进程,比如accept、send、read等。
同步非阻塞I/O:对于进程来说,一些系统调用可以在调用完之后立即返回,告知进程IO是否就绪,避免阻塞进程。
多路I/O就绪通知:对于同步非阻塞I/O的方式,进程仍然需要轮询文件描述符(句柄)来得知哪些IO就绪了,而多路I/O就绪通知将这个过程改成回调通知。
特别是Web服务器,同时处理大量的文件描述符是必不可少的,但是使用同步非阻塞I/O显然不是最佳的选择,在这种模型下,我们知道如果服务器想要同时接收多个TCP连接的数据,就必须轮流对每个socket调用接收数据的方法,比如recv()。不管这些socket有没有可以接收的数据,都要询问一遍,假如大部分socket并没有数据可以接收,那么进程便会浪费很多CPU时间用于检查这些socket,这显然不是我们所希望看到的。
多路I/O就绪通知的出现,提供了对大量文件描述符就绪检查的高性能方案,它允许进程通过一种方法来同时监视所有文件描述符,并可以快速获得所有就绪的文件描述符,然后只针对这些文件描述符进行数据访问。
epoll可以同时支持水平触发和边缘触发,理论上边缘触发的性能要更高一些,但是代码实现相当复杂,因为任何意外的丢失事件就会造成请求处理错误。默认情况下epoll采用水平触发,如果要使用边缘触发,则需要在事件注册时增加EPOLLET选项。而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时拷贝的开销。
Linux的内核提供一种访问磁盘文件的特殊方式,它可以将内存中某块地址空间和我们要指定的磁盘文件相关联,从而把我们对这块内存的访问转换为对磁盘文件的访问,这种技术称为内存映射(MemoryMapping)。在大多数情况下,使用内存映射可以提高磁盘I/O的性能,它无需使用read()或write()等系统调用来访问文件,而是通过mmap()系统调用来建立内存和磁盘文件的关联,然后像访问内存一样自由的访问文件。
内存映射:将文件与内存的某块地址空间相映射,这样可以想写内存一样写文件。当然这种方式本质上跟写文件没有什么区别。
直接I/O:在用户进程地址空间和磁盘中间通常都会有操作系统管辖的内核缓冲区,当写入文件时,一般是写入这个缓冲区,然后由一些延迟策略来写入磁盘。这样做可以提高写效率。但是对于诸如数据库这样的应用来说,往往希望自己管理读写缓存,避免内核缓冲区的无畏内存浪费。Linux的open函数支持O_DIRECT参数来进行直接IO。sendfile:如果web服务器想发送一个文件,将会经历如下过程:打开文件,从磁盘中读取文件内容(这通常涉及到内核缓冲区数据复制到用户进程),然后进程通过socket发送文件内容(这通常设计到用户进程数据复制到网卡内核缓冲区),可以看到重复的数据复制是可以避免的。sendfile可以支持直接从文件内核缓冲区复制到网卡内核缓冲区。
服务器并发策略
PHP脚本,worker进程通常只是负责转发请求给独立的fastcgi进程或者作为反向代理服务器将请求转发给后端服务器,这时候worker进程并不依赖太多的本地资源,所以为了提高并发连接数,可以适当提高worker进程数。但在一般情况下,动态内容本身的吞吐率是相当有限,由于存在脚本解释器的开销,通常2000req/s的吞吐率就已经相当高了,所以worker进程的压力并不是很大。
但如果作为基于反向代理的负载均衡器时,多台后端服务器扩展了动态内容计算能力,woker进程数会逐渐成为整体性能的瓶颈。当然,太多的worker进程又会带来更多的上下文切换开销和内存开销,从而整体上使所有连接的响应时间变长
上述情况的适用范围不能一刀切,而且这里都是指单机并发,需根据实际情况(实际并发数)来选择。通常,在并发用户数较大的情况下,Web服务器使用什么样的并发策略,是影响最大并发数的关键。