努力, 努力, 再努力
全部博文(220)
分类: 架构设计与优化
2015-05-10 19:26:28
第3章服务器并发处理能力
(原文PDF 60页)
本章结论:
本章结论1: 任何一种技术都有它的应用范围,能够意识到环境的不同而选择适合的技术才是明智之举
本章结论2: 并不存在一个对所有性质的请求都高效的并发策略
本章结论3 : 设计一个并发策略的目的,就是让I/O操作和CPU计算尽量重叠进行,一方面让CPU在I/O等待时不要空闲,另一方面让CPU在I/O调度上尽量花费最少的时间。
本章结论4: 并发策略的设计,如何合理的协调充分利用CPU计算和I/O操作,使其在较大并发用户数的情况下提供较高的吞吐率
本章结论5: 没有一个绝对的公式告诉你如何选择work进程数,而你要做的就是根据站点的实际情况来进行分析和调整.
本章总结:
1. 吞吐率, 并发数, 请求等待时间, 这些是评估一个WEB站点性能主要因素, 提高WEB站点的性能,就是要提高WEB服务器的吞吐率, 增加处理并发用户数的能力, 减少用户请求等待时间
吞吐率: WEB服务器单位时间内处理的请求数
并发数: 就是指在某一时刻同时向服务器发送请求的用户总数.
请求等待时间包括:
用户平均请求等待时间和服务器平均请求处理时间
用户平均请求等待时间主要用于衡量服务器在一定并发用户数的情况下,对于单个用户的服务质量
服务器平均请求处理的时间用于衡量服务器的整体服务质量,它其实就是吞吐率的倒数.
2. 提高WEB站点的性能具体的方法:
a) 尽量减少上下文的切换次数,最简单的做法就是减少进程数,尽量使用线程并配合其他I/O模型来设计并发策略
b) 减少不必要的系统调用, 对于降低请求处理时间有着不可忽略的作用
c) 长连接对于密集型的图片或网页等小数据请求有加速作用
设置不能过长,一般为30秒,过长则影响服务器性能。
==========================================================
本章概要: 相当于目录和重要的内容
3.1 吞吐率
吞吐率与压力测试
压力测试的前提条件
并发用户数
请求等待时间
硬件环境
来一次压力测试
继续压力测试
3.2 CPU并发计算
进程:
从本质上讲,多进程的好处并不仅仅在于CPU时间的轮流使用,还在于对CPU计算和I/O操作进行了很好的重叠利用,
轻量级进程
它由一个新的系统调用clone()来创建,允许共享资源,如地址空间,打开文件等。轻量级进程减少了内存开销,为多进程应用程序的数据共享提供了直接支持。
线程:
内核级线程和普通进程模拟多执行流的用户态线程
进程调度器:
时间片长度,单位是时钟个数,那么一个时钟个数需要多长时间?这眼CPU主频及操作系统平台有关,比如linux上一般为10ms, 那么 PR值为15则表示这个进程的时间片为150ms
如果各进程的时间片太短,那么CPU浪费在进程切换上的时间比例就比较大,整体效率降低;而如果时间片太长,则多任务实时性以及交互性就无法保证
系统负载
当运行队列中有不止一个进程的时候,即运行队列长度大于2,就说明此时CPU比较忙.
进程切换: 即通常所说的上下文切换
一个进程被挂起的本质就是将它在CPU寄存器中的数据拿出来暂存在内核态堆栈中,而一个进程恢复工作的本质就是将它的数据重新装入CPU寄存器,这段装入和移出的数据我们称为“硬件上下文”,它是进程上下文的一部分,除此之外,进程上下文中还包含了进程运行时需要的一切状态信息.
重点:如果我们希望服务器支持较大的并发数,那么就要尽量减少上下文的切换次数,最简单的做法就是减少进程数,尽量使用线程并配合其他I/O模型来设计并发策略
IOWait
IOWait它是指CPU空闲并且等待I/O操作完成的时间比例,注意,它是一个比例,而不是绝对的时间
IOWait的概念比较难于理解,请看下面IOWait相关内容,
锁竞争
比如在允许的情况下,关闭WEB服务器访问日志,这可以大大减少在锁等待时的延迟时间。
3.3 系统调用
减少不必要的系统调用,也是WEB服务器性能优化的一个方面。
系统调用的减少对于降低请求处理时间有着不可忽略的作用。
3.4 内存分配
频繁的内存分配和释放会引发一定的时间的内存整理,这本身影响了性能
由于apache机制问题,但内存池对于性能的弥补微不足道。
内存分配策略的设计是WEB服务器并发处理能力的重要保障
3.5 持久连接
从性能角度看,建立TCP连接的操作本身就是一项不小的开销,所以,连接次数越少,越有利于性能的提升
长连接对于密集型和图片或网页等小数据请求处理有明显加速作用。长连接的实施需要浏览器和WEB服务器的共同协作,缺一不可。
长连接对于密集型的图片或网页等小数据请求有加速作用。
长连接的时间设置不能过长,一般为30秒,过长则影响服务器性能。
3.6 I/O模型
PIO与DMA
同步阻塞I/O:
同步阻塞I/O中,进程实际等待的时间可能包括两部分:一个是等待数据就绪,另一个是等待数据复制。
同步的概念:在规范情况下,对磁盘文件调用read()将阻塞进程,一直到数据被复制到进程用户态内存空间,而对磁盘调用write()则不同,它会在数据被复制到内核缓冲区后立即返回。如果使用O_SYNC标志打开文件,则对文件操作产生影响,它使用得write()必须等待数据真正写入到磁盘后才返回。
同步非阻塞I/O
相比于阻塞I/O,这种非阻塞I/O结合反复轮询来尝试数据是否就绪,防止进程被阻塞,最大的好处便在于可以在一个进程里同时处理多个I/O操作
但正是由于需要进程执行多次的轮询来查看数据是否就绪,这花费了大量的CPU时间,便得进程处于忙碌状态。
非阻塞I/O一般只针对网络I/O有效. 值得注意的是,对于磁盘I/O,非阻塞I/O并不产生效果。
多路I/O就绪通知
Select()
Poll()
SIGIO()
/dev/poll
/dev/epoll
Epoll
Kqueue
内存映射
它可以将内存中某块地址空间和我们要指定的磁盘文件相关联,从而把我们对这块内存的访问转换为对磁盘文件的访问,这种技术称为内存映射(Memory Mapping).
内存映射可以提高磁盘I/O的性能,通过mmap()系统调用来建立内存和磁盘文件的关联,然后像访问内存一样自由的访问文件。
内存映射的撤销使用munmap()系统调用
Apache 2.x 相关设置:EnableMMAPOn|Off
直接I/O
数据从进程用户态空间到磁盘要经过两次复制:
磁盘---->内核缓冲区,内核缓冲区---->用户态内存空间
引入内核缓冲区的目的在于提高磁盘文件的访问性能,
O_SYNC选项,它只对写数据有效,它将写入内核缓冲区的数据立即写入磁盘,将机器故障时数据的丢失减少到最小,但是它仍然要经过内核缓冲区。
O_DIRECT选项:在open()系统调用中增加参数选项O_DIRECT,用它打开的文件便可以绕过内核缓冲区的直接访问,这样便有效避免了CPU和内存的多余时间开销。
Sendfile()
一个进程从磁盘上读取静态页面,然后再发送给客户端的过程
由于是静态数据,在这个过程中不需要CPU参与计算。
磁盘文件--->内核缓冲区--->用户内存空间------>网卡对应的内核缓冲区--->由网卡发送出去
Sendfile()系统调用,它可以将磁盘文件的特定部分直接传送到代表客户端的socket描述符,加快了静态文件的请求速度,同时也减少了CPU和内存的开销。
注意:sendfile并不适用任何场景,对于请求较小的静态文件,sendfile的作用并不那么明显
异步I/O
异步I/O的目的:让进程在发起I/O操作后继续运行,让CPU处理和I/O的操作达到更好的重叠。
3.7 服务器并发策略
设计一个并发策略的目的,就是让I/O操作和CPU计算尽量重叠进行,一方面让CPU在I/O等待时不要空闲,另一方面让CPU在I/O调度上尽量花费最少的时间。
一个进程处理一个连接,非阻塞I/O
一个线程处理一个连接,非阻塞I/O
实际测试中,Apache的work这种方式的表现并不比prefork有太大的优势,因为虽然它使用大量的线程代替进程,但是这些线程实际上都是由内核进程调度器管理的轻量级进程,它们的上下文切换开销依然存在。
在实际应用中,worker模型的处境比较尴尬,人们几乎很少使用它,因为它的优点并不明显,一旦人们意识到Apache无法满足需要时,便会马上使用其他轻量级的WEB服务器。
一个进程处理多个连接,非阻塞I/O
没有一个绝对的公式告诉你如何选择work进程数,而你要做的就是根据站点的实际情况来进行分析和调整.
一个线程处理多个连接,异步I/O
=====================================
3.1 吞吐率
吞吐率:指WEB服务器单位时间内处理的请求数。
吞吐率和压力测试
在WEB服务器的实际工作中,HTTP请求通常包括对不同资源的请求,比如获取图片,动态内容,访问数据库等等,服务器处理这些请求所花费的时间各不相同。
我们知道在一个含有较多变量的数据模型中求解最优结果是非常困难的,所以我们一般都简化模型,对一个特定的有代表性的请求进行压力测试,然后根据需要,对多个请求的吞吐率按照比例计算加权平均值
WEB服务器并发能力强弱的关键在于如何针对不同请求性质来设计最优的并发策略。
压力测试的前提条件
并发用户数:
总请求数:
请求资源描述:比如1KB的静态文件,或者包含10次数据库查询的动态内容
并发用户数
并发用户数就是指在某一时刻同时向服务器发送请求的用户总数。
假如100个用户同时向服务器分别进行10次请求,与1个用户向服务器连续进行1000次的请求,效果一样吗?
关键的区别就在于,是否真的“连续”
从微观层面看,1个用户向服务器连续进行1000次请求的过程中,任何时刻服务器的网卡接收缓冲区中只有来自该用户的1个请求,而100个用户同时向服务器分别进行10次请求的过程中,服务器网卡接收缓冲区中最多有100个等待处理的请求,显然这时候服务器压力更大。
人们常常把并发用户数和吞吐率混淆,它们并不是一回事。吞吐率是指在一定并发用户数的情况下,服务器的处理能力的量化体现。
书中用一个业务员处理业务的案例形象地说明了什么是“最大并发用户数“
很值得一看.
通常所讲的最大并发数是一有定利益前提的,那就是服务器和用户双方所期待的最大收益,服务器希望支持高并发数及高吞吐率,而用户只希望等待较少的时间或者得到更快的下载速度
WEB服务器使用什么样的并发策略,是影响最大并发数的关键。
从浏览器端来看并发数:
浏览器在下载一个网页以及网页中多个组件时,采用多线程的并发方式
但对于同一域名URL的并发数是有最大限制的。
IE7支持2个并发连接
IE8支持6个并发连接
Firefox支持4个并发连接
服务器支持的最大并发数,具体到真实用户数,可能并不是一对一的关系
一个真实的用户可能会给服务器带来两个或更多的并发用户数的压力
从WEB服务器端看并发数:
实际并发用户数也可以理解为WEB服务器当前维护的代表不同用户的文件描述符总数。
Apache的MaxClients参数会限制同时服务的最多用户数,而多出的这些请求,则在服务器内核的数据接收缓冲区中等待处理,这些请求在用户看来处于请求阻塞状态。
如果请求性质决定了处理每个请求花费的时间非常少,如1KB的静态文件,那么每个请求都可以被快速处理然后释放文件描述,这种情况下,我们希望服务器的最大并发用户数可以大于最大并发连接数
如果请求的性质决定了处理每个请求要花费相当长的时间,比如下载10MB文件,或者请求动态内容,在这种情况下,我们希望服务的最大并发用户数小于理论上的最大并发连接数
WEB服务器工作的本质,就是争取以最快的速度将内核缓冲区中的用户请求数据一个不剩的拿回来,然后尽最大努力同时快速处理完这些请求,并将响应数据放到内核维护的另一块用于发送数据的缓冲区中,接下来尽快处理下一拨请求,并尽量让用户请求在内核缓冲区中不要等太久.
请求等待时间
用户平均请求等待时间
服务器平均请求处理时间
WEB服务器一般会采用多进程或多线程的并发模型,通过多个执行流来同时处理多个并发用户的请求,而多执行流体系的设计原则便是轮流交错使用CPU时间片,所以每个执行流花费的时间都被拉长。对于每个用户而言,每个请求的平均等待时间必然增加,而对于服务器而言,如果并发策略得当,每个请求的平均处理时间可能减少.
用户平均请求等待时间主要用于衡量服务器在一定并发用户数的情况下,对于单个用户的服务质量
服务器平均请求处理时间与前者相比,则用于衡量服务器的整体服务质量,它其实就是吞吐率的倒数。
硬件环境:
本书的WEB服务器硬件环境:大部分内容不依赖特定硬件配置
CPU: Intel(R) Xeon(R) CPU 1.60GHz
内存: 4GB
硬盘转速: 15k/min
来一次压力测试
Ab可以在WEB服务器本地发起测试请求,这至关重要,因为我们希望测试的是服务器的处理时间,而不包括数据的网络传输时间以及用户PC本地的计算时间
另一些压力测试软件,包括LoadRunner,Jmeter等,则是不同程度上包含了服务器的处理之外的时间。比如,LoadRunner运行在用户PC上, 这种测试的结果往往侧重于站点用户的角度,有另外一些层面的参考意义.
[root@master html]# ab -n 1000 -c 10
Server Software: Apache/2.2.15
Server Hostname: 192.168.163.131
Server Port: 80
Document Path: /test.html
Document Length: 20 bytes
Concurrency Level: 10
Time taken for tests: 0.336 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 289728 bytes
HTML transferred: 20120 bytes
Requests per second: 2976.82 [#/sec] (mean)
Time per request: 3.359 [ms] (mean)
Time per request: 0.336 [ms] (mean, across all concurrent requests)
Transfer rate: 842.25 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 1 2 1.1 1 12
Processing: 1 2 1.2 1 12
Waiting: 0 1 1.0 1 11
Total: 2 3 1.9 3 20
Percentage of the requests served within a certain time (ms)
50% 3
66% 3
75% 4
80% 5
90% 5
95% 6
98% 7
99% 14
100% 20 (longest request)
命令解释:
abApacheBench WEB压力测试工具
-n 1000 总请求数为1000
-c 10 并发数 10
请求的URL
Document Path: 请求的URL的绝对路径,来自http头信息
Time taken for tests: 处理所有请求所花费的时间
Complete requests: 表示总请求数
Total transferred: 所有请求的响应数据长度总和,包括每个HTTP响应数据的头信息和正文数据的长度,即应用层数据长度. 注意,这里不包括HTTP请求数据的长度,仅是响应数据的长度
HTML transferred: 表示所有请求的响应数据中正文数据的总和,即减去了Total transferred中http响应数据中头信息的长度,即不包括头信息
Requests per second: 重点,吞吐率
Time per request: 用户平均请求等待时间
Time per request: 服务器平均请求处理时间,即吞吐率的倒数
Transfer rate: 表示这些请求在单位时间内从服务器获取的数据长度,它等于: Total transferred / Time taken for tests
这个统计项可以很好地说明服务器在处理能力达到极限时,其出口带宽的需求量。
继续压力测试
并发策略的设计就是在服务器同时处理较多请求的时候,如何合理的协调充分利用CPU计算和I/O操作,使其在较大并发用户数的情况下提供较高的吞吐率
“并不存在一个对所有性质的请求都高效的并发策略”
任何架构师只要获得应用了最佳并发策略的Web服务器软件便可以应付各种性质的并发用户请求, 显然那是不在在的!!!
只有了解并根据这些性质选择最佳的并发策略,高性能WEB站点才会离你更近一步
3.2 CPU并发计算
进程
从本质上讲,多进程的好处并不仅仅在于CPU时间的轮流使用,还在于对CPU计算和I/O操作进行了很好的重叠利用,这里的I/O主要指磁盘I/O和网络I/O.
DMA技术:可以让CPU不参与I/O操作的全过程
每个进程都有自己独立的内存地址空间和生命周期,当子进程被父进程创建后,便将父进程地址空间的所有数据复制到自己的地址空间,完全继承父进程的所有上下文信息,它们之间可以通信,但是不互相依赖。
进程的创建使用fork()系统调用。频繁的创建进程可能成为影响性能的主要因素。
进程缺点:相互独立,各自维护着自己的地址空间和上下文,无法很好的低成本的共享数据
进程的优越性,相互独立带来的稳定性和健壮性
Linux 2.6对于fork()的实现进行了优化,减少了一些多余的内存复制
轻量级进程
Linux2.0之后,提供了轻量级进程的支持,它由一个新的系统调用clone()来创建,这些进程由内核直接管理, 像普通进程一样独立存在, 拥有各自的进程描述符, 且允许共享资源,如地址空间,打开文件等。
轻量级进程减少了内存开销,为多进程应用程序的数据共享提供了直接支持。
线程
第一种:多线程只是一种普通的进程,它是由用户态通过一些库函数模拟实现的多执行流,所以多线程的管理完全是在用户态完成
这种线程上下文切换的开销相比普通进程和轻量级进程要少些,但对SMP多处理器的表现较差
第二种:内核级线程,通过clone()来创建进程
实现原理:将线程和轻量级进程进行一对一的关联,每个线程实际上就是一个轻量级进程
这种线程对于SMP多处理器支持较好,但线程切换开销相比用户态线程要多一些
进程调度器
在单CPU的机器上,从微观意义上讲,任何时刻只有一个进程处于运行状态,其他进程有的处于挂起状态并等待就绪
在linux中,调度器维护着两个队列:
运行队列:Run Queue 包括所有可运行的进程队列
等待队列:包括所有休眠进程和全局进程的列表
注意:运行队列长度< 2,如果大于2,表示CPU比较忙
进程优先级
每个进程需要告诉进程调度器它们的紧急程度,即进程优先级.
进程优先级除了可以由进程自己决定,进程调度器在进程运行时也可以动态调整它们的优先级,这些行为的出发点都是为了进程更好的重叠利用系统资源。
优先级动态调整值在TOP中用NI表示
进程调度器更加偏爱I/O操作密集型的进程,因为这些进程在发起I/O操作后通常都会阻塞,不会占用太多CPU时间,这就意味着其他进程都可以更好的交错运行。
TOP里的PR其实就是进程调度器分配给进程的时间片长度,单位是时钟个数,那么一个时钟个数需要多长时间?这眼CPU主频及操作系统平台有关,比如linux上一般为10ms, 那么 PR值为15则表示这个进程的时间片为150ms
如果各进程的时间片太短,那么CPU浪费在进程切换上的时间比例就比较大,整体效率降低;而如果时间片太长,则多任务实时性以及交互性就无法保证
系统负载
在进程调度器维护的运行队列中,任何时刻至少存在一个进程,那就是正在运行的进程,而当运行队列中有不止一个进程的时候,即运行队列长度大于2,就说明此时CPU比较忙,
[root@master ~]# cat /proc/loadavg
1.34 1.03 0.48 4/246 2171
注意:4/246 , 其中4表示此时运行队列中的进程个数,而246表示此时进程的总个数
最右边的2171表示到此时为止,最后创建的一个进程ID
1.34 1.03 0.48分别表示1分钟,5分钟,15分钟分别计算得出的系统负载,系统负载越高,表示CPU越繁忙
系统负载是如何计算的?它是在单位时间内运行队列中就绪等待的进程个数平均值,当负载为0.00时,说明任何进程只要就绪后就可以马上获得CPU,不需要等待,这时系统响应速度最快
Top , 和 w 都可以查看系统负载
加重系统负载的php脚本,不占用任何I/O操作,但长时间占用CPU时间的脚本
[root@master html]# cattest.php
$max = 10000000;
$sum = 0;
for ($i = 0; $i < $max; ++$i)
{
$sum += $i;
}
echo $sum;
?>
进程切换—上下文切换
为了让所有进程可以轮流使用系统资源,进程调度器在必要的时候挂起正在运行的进程,同时恢复以前挂起的某个进程,这种行为称为进程切换,即上下文切换,它表示进程运行到何种程度
一个进程被挂起的本质就是将它在CPU寄存器中的数据拿出来暂存在内核态堆栈中,而一个进程恢复工作的本质就是将它的数据重新装入CPU寄存器,这段装入和移出的数据我们称为“硬件上下文”,它是进程上下文的一部分,除此之外,进程上下文中还包含了进程运行时需要的一切状态信息
当硬件上下文频繁地装入和移出时,所消耗的时间是非常可观的.
TOP 部分表头说明
#top -b -n1
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 19364 1536 1228 S 0.0 0.2 0:02.41 init
2 root 20 0 0 0 0 S 0.0 0.0 0:00.05 kthreadd
每个进程的RES值:表示其占用的物理内存空间
SWAP表示其使用的虚拟内存空间
VIRT值等于RES和SWAP的总和
Apache与lighttp比较
Apache是多进程模型
Lighttp对于静态网页,它使用单进程单线程模型来处理多个请求
对于PHP脚本,它使用数量较少的fastcgi进程处理
此处: 书中有一个对比上下文切换次数的案例,应该看看
相比之下,Apache-prefork在执行相同数量的处理任务时,上下文切换的次数多得惊人,这些切块换时间一定在总处理时间占有不可忽视的地位。
而lighttp的内存开销,上下文切换都比较小。
本节重要的结论:
如果我们希望服务器支持较大的并发数,那么就要尽量减少上下文的切换次数,最简单的做法就是减少进程数,尽量使用线程并配合其他I/O模型来设计并发策略
IOWait
IOWait它是指CPU空闲并且等待I/O操作完成的时间比例,注意,它是一个比例,而不是绝对的时间
IOWait往往并不能真实地代表I/O操作的性能或者工作量,它的设计出发点是用来衡量CPU性能。
举个例子:由于这里概念不是很理解,所以把例子加进来,为便于理解,修改了该案例的部分参数,与原书不同。
例如有一个任务本身需要花费90ms的I/O操作时间和10ms的CPU时间,那么总时间为100ms,IOWait便为90ms/100ms,等于90%,这仅表示该任务的I/O等待时间与CPU计算时间的比例为90/10, 这时并不意味着I/O操作的繁忙程度为90%,或者I/O性能下降,或者硬盘快要挂掉。事实上,即时IOWait为100%,也不一定代表I/O出现性能问题或者瓶颈。同时IOWait为0时,I/O操作也可能很繁忙?
小结:I/O繁忙一定会引起IOWait的比例加大
但不能说IOWait的比较大一定是I/O繁忙导致,有可能这个任务本身就是I/O操作比较多,如nginx启用sendfile(), 就是I/O操作比较多,CPU时间相对比较少的情况(上面两段不太好理解,我把书中的案例改得极端了一点)
所以,如果你希望真正了解当前I/O性能,可以进行磁盘I/O测试或者查看网络I/O流量
即然IOWait来用描述CPU性能,那么当IOWait很高的时间,至少说明当前任务的CPU时间开销相对于I/O操作时间来说比较少,通常对于依赖磁盘I/O的应用来说,这是比较正常的。
这里有个IOwait监控的案例,要仔细看。
锁竞争
我们一般采用“锁”机制来控制资源的占用,当一个任务占用资源的时候,我们锁住资源,这时候其他任务都在等待锁的释放,这种现象我们称为锁竞争
我们不需要太担心目前流行的WEB服务器的锁设计本身。
比如在允许的情况下,关闭WEB服务器访问日志,这可以大大减少在锁等待时的延迟时间。
3.3 系统调用
Linux为进程设计两种运行级别:用户态和内核态。
进程通常运行在用户态,这时可以使用CPU和内存来完成某些任务(如数学计算),而当进程需要对硬件外设进行操作的时候(如读取磁盘文件,发送网络数据),就必须切换到内核态,当在内核态的任务完成后,又切换回用户态。
用户态的进程可以直接进行系统调用,也可以使用封装了系统调用的C API,比如write()系统调用,它的封装API之一就是用于发送网络数据的send()
这种用户态和内核态的分离,动机主要是在于提高系统底层安全性以及简化开发模型。所有进程必须通过内核提供的系统调用来操作硬件,所以不用担心应用程序对硬件进行非法操作。
由于系统调用涉及进程从用户态到内核态的切换,导致一定的内存空间交换,这也是一定程度上的上下文切换。
减少不必要的系统调用,也是WEB服务器性能优化的一个方面。
书中减少系统调用的案例: .htaccess的设置. 此处省略
系统调用的减少对于降低请求处理时间有着不可忽略的作用。
3.4 内存分配
Apache使用了基于内存池策略的管理方案,并将它抽象出来后移入APR库中作为通用内存管理模块,这种方案使得Apache在运行开始时便一次性申请大片的内存作为内存池。这样在随后需要的时候只要在内存池中直接获取,而不是需要再次分配。频繁的内存分配和释放会引发一定的时间的内存整理,这本身影响了性能
另一方面,内存池的使用使得Apache的内存管理更加安全,因为即便是某处内存使用后忘记释放也没关系,内存池在Apache关闭时会彻底释放。
由于apache机制问题,内存池对于性能的弥补微不足道。
单进程模型的nginx它的内存使用量更小。
此处: Nginx的压力测试案例
Nginx对于内存方面的良好表现,也正是来自于它的内存分配策略,它可以使用多线程来处理请求,这使得多个线程之间可以共享内存资源,从而令它的内存总体使用量大大减少。另外,它使用分阶段的内存分配策略,按需分配,及时释放,使用得内存量保持在很少的数量范围。官方介绍中声称,Nginx维持10000个非活跃的HTTP持久连接只需要2.5MB的内存。
内存分配策略的设计是WEB服务器并发处理能力的重要保障
3.5 持久连接
持久连接(Keep-Alive)有时候也称为长连接,即一次TCP连接中持续发送多份数据而不断开连接。与之相反的称为短连接,也就是建立连接后发送一份数据便断开,然后再建立连接发送下一份数据。
从性能角度看,建立TCP连接的操作本身就是一项不小的开销,所以,连接次数越少,越有利于性能的提升
长连接对于密集型和图片或网页等小数据请求处理有明显加速作用。长连接的实施需要浏览器和WEB服务器的共同协作,缺一不可。
在请求头中包含长连接的声明: Connection: Keep-Alive
在Apache中的相关配置项:KeepAlive Off
长连接在什么时候关闭? WEB服务器与浏览器双方都可以主动关闭,IE7默认的超时时间为1分钟,Apache的配置项:
KeepaliveTimeout 30
浏览器和WEB服务器各自的超时时间设置不一定一致,实际运行中,是以最短的超时时间为准。
Ab压力测试:ab–n10000 -c100 -k
-k 连接使用长连接
对于Apache这样的多进程模型来说,如果长连接超时时间过长,比如60秒,那么即便是浏览器没有任何请求,而Apache仍然维持着连接该浏览器的子进程,一旦并发用户较多,那么Apache将维持着大量空闲进程,严重影响服务器性能。
对于轻量级的WEB服务器(如nginx),长连接超时时间过长也不是一件好事。
持久连接的动机很简单,那就是尽量减少连接次数,尽量重用连接通道。
3.6 I/O模型
I/O操作根据设备的不同分为很多种类型,比如内存I/O,网络I/O,磁盘I/O。内存I/O速度足够快,计算机的性能瓶颈往往不在内存I/O本身,因此,本书中所说的I/O是指网络IO和磁盘I/O
如何让高速的CPU和慢速的I/O设备更好的协调工作,这是从现代计算机诞生到现在一直探讨的话题。
PIO与DMA
PIO: 磁盘和内存之间的数据传输是需要CPU控制的,也就是说如果我们读取磁盘文件到内存中,数据要经过CPU存储转发。这种方式需要占用大量的CPU时间来读取文件
DMA(直接内存访问:Direct Memmory Access)取代了PIO。在DMA模型下,CPU只需要向DMA控制器下达指令,让DMA控制器来处理数据的传送即可,DMA控制器通过系统总线来传输数据,传送完成再通知CPU,这在很大程度上降低了CPU占有率,大大节省了系统资源。
同步阻塞I/O
说到阻塞,首先得说说等待。
WEB服务器在等待用户的访问。
TCP连接建立,WEB服务器等待用户发送请求。
再比如,读取磁盘某文件的I/O操作,要等待其他的磁盘访问操作,因为磁头是有限的,只能排队等待。
我们说的阻塞是指当前发起I/O操作的进程被阻塞,并不是CPU被阻塞,事实上没什么能让CPU阻塞的。
同步的概念:在规范情况下,对磁盘文件调用read()将阻塞进程,一直到数据被复制到进程用户态内存空间,而对磁盘调用write()则不同,它会在数据被复制到内核缓冲区后立即返回。如果使用O_SYNC标志打开文件,则对文件操作产生影响,它使用得write()必须等待数据真正写入到磁盘后才返回。
同步阻塞I/O: 当进程调用某些涉及I/O操作的系统调用或库函数时,比如accept(),send(),recv()等,进程便暂停一下,等待I/O操作完成后再继续运行。这是一种简单有效的模型,它可以和多进程结合起来有效的利用CPU资源。但其代价就是多进程的大量内存开销.
这里书中有一个用户在小吃城等待吃面的案例,它会伴随着本节的各个内容
限于篇幅,不在此列出,但它对于后面的水平触发,边缘触发,EPOLL模型有利于加深理解.
同步非阻塞I/O
同步阻塞I/O中,进程实际等待的时间可能包括两部分:一个是等待数据就绪,另一个是等待数据复制。
同步非阻塞I/O的调用不会等待数据的就绪,如果数据不可读或者不可写,它会立即告诉进程。相比于阻塞I/O,这种非阻塞I/O结合反复轮询来尝试数据是否就绪,防止进程被阻塞,最大的好处便在于可以在一个进程里同时处理多个I/O操作
但正是由于需要进程执行多次的轮询来查看数据是否就绪,这花费了大量的CPU时间,便得进程处于忙碌状态。
非阻塞I/O一般只针对网络I/O有效,我们只要在socket的选项设置中使用O_NOBLOCK即可,这样,对于该socket的send()或recv()便采用非阻塞方式,值得注意的是,对于磁盘I/O,非阻塞I/O并不产生效果。
小吃城的案例, 同步非阻塞的术语不是很难理解,这个案例不在此列出
多路I/O就绪通知
在实际应用中,特别是WEB服务器,同时处理大量的文件描述符是必不可少的,但是使用同步非阻塞I/O显然不是最佳的选择。
多路I/O就绪通知的出现,提供了对大量文件描述符就绪检查的高性能方案,它允许进程通过一种方法同时监视所有文件描述符,并可以快速获得所有就绪的文件描述符,然后只针对这些文件描述符进行数据访问。
需要注意的是,I/O就绪通知只是帮助我们快速获得就绪的文件描述符,就访问数据本身而言,仍然需要选择阻塞或非阻塞的访问方式,一般我们选择非阻塞方式,以防止任何意外的等待阻塞整个过程。
这里又出现了小吃城的案例, 在此省略
多路就绪通知方法1 ---Select
Select通过一个select()系统调用来监视包含多个文件描述的数组,当select()返回后,该数组就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作
Select的优点:良好的跨平台支持
Select的缺点:在于单个进程能够监视的文件描述符的数据存在最大限制,在linux上为1024,不过可以通过宏定义甚至重新编译内核的方式提升这一限制,假如你使用了select的服务器已经维持了1024个连接,那么你的请求可能会被拒绝。
Select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长,
POLL
Poll和select在本质上没有多大差别,但是poll没有最大文件描述数量的限制。
Poll和select同样存在的一个缺点:包含大量文件描述符数组被整体复制到用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述数量的增加而线性增大。
水平触发(Level Triggered)
Select() 和poll() 将就绪的文件描述符告诉进程后,如果进程没有对其进行I/O操作,那么下次调用select()或poll()的时候再次报告这些文件描述符,所以它们一般不会丢失就绪消息,这种方式称为水平触发(Level Triggered)
SIGIO(实时信号:Real time Signal)边缘触发(Edge Triggered)
Linux 2.4提供了SIGIO。Select/poll 告诉我们哪些文件描述符是就绪的,一直到我们读写他们之前,每次select/poll都会告诉我们
而SIGIO则是告诉我们哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知我们,这种方式称为边缘触发(Edge Triggered)
SIGIO的缺点:在SIGIO机制中,代表事件的信号由内核中的事件队列来维护,信号按照顺序进行通知,这可能导致当一个信号到达时,该事件已经过期,它所描述的文件描述符已经被关闭。另一方面,事件队列是有长度的,无论你设置多大的上限,总有可能被事件装潢,这就很容易发生事件丢失,所以这时候需要采用其他方法来弥补。
/dev/poll
/dev/poll是Sun在Solaris中提供的虚拟设备。你可以将要监视的文件描述符数组写入这个设备,然后通过ioctl()来等待事件通知。你也可以从/dev/poll中读取所有就绪的文件描述符数组。
/dev/epoll
/dev/epoll在Linux 2.4中只是一个补丁,并没有加入内核,它提供类似/dev/poll的功能,而且增加了内存映射(mmap)技术
epoll
epoll具备之前所有的优点,被公认为Linux 2.6下性能最好的多路就绪通知方法。
epoll可以同时支持水平触发和边缘触发,理论上边缘触发性能要高一些,但是代码实现相当复杂,任何意外的丢失事件都会造成请求处理错误,在默认情况下epoll采用水平触发。
Lighttpd的epoll模型代码(src/fdevent_linux_sysepoll.c)采用水平触发。Nginx的epoll模型代码(src/event/modules/ngx_epoll_module.c)
使用边缘触发。
当我们调用epoll_wait()获得就绪文件描述符时,返回的并不是实际描述符,而是一个代表就绪描述符数量的值,这里也使用了内存映射(mmap)技术,这便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
此处书中还是引用吃面条的例子,很好的说明了水平触发,边缘触发,epoll事件机制。(如果上面的文字,不容易理解,请看原书的内容,特别是这个小吃城的例子)
Kqueue
FreeBSD中实现了kqueue,它和epoll的性能非常接近, 支持水平触发或边缘触发,还可以用来监控磁盘文件和目录, 但文档相当匮乏。
内存映射(mmap)
Linux内核提供一种访问磁盘文件的特殊方式,它可以将内存中某块地址空间和我们要指定的磁盘文件相关联,从而把我们对这块内存的访问转换为对磁盘文件的访问,这种技术称为内存映射(Memory Mapping).
内存映射可以提高磁盘I/O的性能,它无须使用read()或write()等系统调用来访问文件,而是通过mmap()系统调用来建立内存和磁盘文件的关联,然后像访问内存一样自由的访问文件。
内存映射的撤销使用munmap()系统调用
内存映射分为两种类型,共享型和私有型。前者可以将任何对内存的写操作都同步的磁盘文件,而且所有映射同一个文件的进程都共享任意一个进程对映射内存的修改;后者映射的文件只能是只读文件,所以不可以将对内存的写同步到文件,而且多个进程不共享修改。显然,共享型内存映射的效率偏低,因为如果一个文件被很多进程映射,那么每次的修改同步将花费一定的开销。
如果操作系统支持,Apache将默认使用内存映射。相关配置:EnableMMAPOn|Off
直接I/O
在Linux 2.6中,内存映射和直接访问文件没有本质上的差异,因为数据从进程用户态空间到磁盘要经过两次复制:
磁盘---->内核缓冲区,内核缓冲区---->用户态内存空间
引入内核缓冲区的目的在于提高磁盘文件的访问性能,因为当进程需要读取磁盘文件时,如果文件内容已经在内核缓冲区中,那么就不需要再次访问磁盘,而当进程需要向文件中写入数据时,实际上是只写到了内核缓冲区便告诉进程已经写成功,而真正的写入磁盘是通过一定的策略进行延迟的。
对于一些较为复杂的应用,比如数据库服务器,由进程自己在用户态空间实现并管理I/O缓冲区,包括缓存机制和写延迟机制等,另一方面,绕过内核缓冲区也可以减少系统内存的开销。
在mysql中,对于Innodb存储引擎,其自身可以进行数据和索引的缓存管理,所以它对于内核缓冲区的依赖不是那么重要。
在mysql中有两种方法可以实现直接I/O, 跳过内核缓冲区。
第一种:在my.cnf配置中,通过使用raw分区跳过内核缓冲区,实现直接I/O。这需要使用raw设备管理程序来加载。在my.cnf中配置如下:innodb_data_file_path = /dev/sda5:100Gnewraw
第二种方法:在my.cnf配置中加入如下选项:
Innodb_flush_method = O_DIRECT
O_SYNC选项,它只对写数据有效,它将写入内核缓冲区的数据立即写入磁盘,将机器故障时数据的丢失减少到最小,但是它仍然要经过内核缓冲区。
O_DIRECT选项:在open()系统调用中增加参数选项O_DIRECT,用它打开的文件便可以绕过内核缓冲区的直接访问,这样便有效避免了CPU和内存的多余时间开销。
Sendfile
一个进程从磁盘上读取静态页面,然后再发送给客户端的过程
由于是静态数据,在这个过程中不需要CPU参与计算。
磁盘文件--->内核缓冲区--->用户内存空间------>网卡对应的内核缓冲区--->由网卡发送出去
Sendfile()系统调用,它可以将磁盘文件的特定部分直接传送到代表客户端的socket描述符,加快了静态文件的请求速度,同时也减少了CPU和内存的开销。
此处,书中举了一个使用sendfile()的压力测试和一个不使用sendfile()压力测试的案例,对比两个测试的区别。
Apache开启sendfile()功能的配置选项:EnableSendfile off
注意:sendfile并不适用任何场景,对于请求较小的静态文件,sendfile的作用并不那么明显。
任何一种技术都有它的应用范围,能够意识到环境的不同而选择适合的技术才是明智之举
异步I/O
阻塞和非阻塞是指当进程访问的数据如果尚未就绪,进程是否需要等待,简单说这相当于函数内部的实现区别,即未就绪时是直接返回还是等待就绪
同步与异步是指访问数据的机制,同步一般主动请求数据并等待I/O操作完毕的方式,当数据就绪后在读写的时候必须阻塞; 异步则指主动请求数据后便可以继续处理其它任务,随后等待I/O操作完毕的通知,这可以让进程在数据读写时不发生阻塞。
POSIX 1003.1 标准为异步I/O访问定义了一套库函数,这里的异步I/O实际上就是指当用户态进程调用库函数访问文件时,进行必要的快速注册,比如进入读写操作队列,然后函数马上返回,这时真正的I/O传输还没有开始。这种机制是真正意义上的异步I/O,而且是非阻塞的。它可以使进程在发起I/O操作后继续运行,让CPU处理和I/O的操作达到更好的重叠。
AIO的实现,不同平台有不同的方法,甚至可以完全由库函数来实现而不需要内核支持,但性能便大打折扣,变得毫无意义,所以实际上很多平台同有实现它.
在Linux 2.6.16中, AIO的实现可以在/usr/include/libaio.h中看到,它采用了一套没有遵循POSIX AIO标准的接口,并且实现方式正是前面说到的linux内核级线程库,截至到目前,这个功能还在实现中, 目前Linux AIO只能通过O_DIRECT标志打开的文件.
3.7 服务器并发策略
设计一个并发策略的目的,就是让I/O操作和CPU计算尽量重叠进行,一方面让CPU在I/O等待时不要空闲,另一方面让CPU在I/O高度上尽量花费最少的时间。
一个进程处理一个连接,非阻塞I/O
Fork模式:由主进程负责accept()来自客户端的进程,一旦接收连接,便马上fork()一个新的work进程来处理,处理结束后,这个进程便被销毁。Fork()的开销成为影响性能的关键
Prefork模式:由主进程预先创建一定数量的子进程,每个请求由一个子进程来处理,但是每个子进程可以处理多个请求。父进程往往只负责管理子进程。
对于accept()的方式,有以下两种策略:
主进程使用非阻塞accept()来接收连接,当建立连接后,主进程将任务分配给空闲的子进程来处理。
所有子进程使用阻塞accept()来竞争接收连接,一旦一个子进程建立连接后,它将继续进行处理。
Apache 2.x 便采用上述第2种策略。按大多数TCP的实现方法,当一个请求连接到达时,内核会激活所有阻塞在accept()的子进程,但只有一个能够成功获得连接并返回到用户空间,而其余的子进程由于得不到连接而继续回到休眠状态,这种“抖动”也造成一定的额外开销。
这里有一个系统调用的案例,仔细看,如何得到的结论?
Apache这种多进程模型的开销限制了它的并发连接数,但Apache也有自身的优势,比如从稳定性和兼容性的角度看,多进程模型的优势正体现在它相对安全的独立进程,任何一个子进程的崩溃都不会影响Apache本身。
Apache功能模块丰富,对于一些并发数要求不高(150)的站点,如果同时对其他功能有所依赖那么Apache便是非常不错的选择。
一个线程处理一个连接,非阻塞I/O
这种方式允许在一个进程中通过多个线程来处理多个连接,其中一个线程处理一个连接。
Apache的work多路处理模块便采用这种方式,它的目的主要在于减少prefork模式中太多的进程开销,便Apache可以支持更多的连接。Apache主进程会创建很少的子进程,每个子进程又拥有一定数量的线程。
实际测试中,这种方式的表现并不比prefork有太大的优势,因为虽然它使用大量的线程代替进程,但是这些线程实际上都是由内核进程调度器管理的轻量级进程,它们的上下文切换开销依然存在。
此处worker和prefork两种模型的压力测试,两者的上下文切换次数相差很大
在实际应用中,worker模型的处境比较尴尬,人们几乎很少使用它,因为它的优点并不明显,一旦人们意识到Apache无法满足需要时,便会马上使用其他轻量级的WEB服务器。
一个进程处理多个连接,非阻塞I/O
在这种并发策略下,多路I/O就绪通知的性能成为关键. 通常我们将处理多个连接的进程称为worker进程,或者服务进程.
Nginx和lighttpd都支持配置worker进程数量
Nginx配置: worker_processes 2;
Lighttpd配置: server.max-worker = 2
在这种模型中,有时候还会设计独立的listen进程,专门负责接收新的连接,然后分配新任务给各个worker,这样做的好处是可以根据各个worker进程的负载来调度任务,但是任务调度也有一定的开销.有些时候WEB服务器会使用worker线程来代替worker进程,但是这种线程实际上通常都是内核级线程,它们在处理多路I/O的方式基本相同.
Lighttpd配置中关于epoll的条目:
server.event-hander = "linux-sysepoll"
书中测试案例: 用select, poll, epoll 三种模型来进行压力测试
对于动态内容,比如PHP脚本,worker进程通常只是负责转发请求给独立的fastcgi进程,或者作为反向代理服务器将请求转发给后端服务器,这时候,worker进程并不依赖太多的本地资源,所以为了提高并发连接数,我们可以适当地提高worker进程数.
在一般的情况下,动态内容本身的吞吐率是相当有限的,由于在在脚本解释器的开销,通常2000reqs/s的吞吐率就已经很高了,所以worker进程的压力并不是很大,但如果作为基于反向代理的负载均衡调度器时,多台后端服务器扩展了动态内容计算能力,worker进程数会逐渐成为整体性能的瓶颈. 所以为了提高并发连接数,我们可以适当地提高worker进程数.
但太多的进程又会带来更多的上下文切换开销和内存开销,从而整体上使用所有连接的响应时间变长.
没有一个绝对的公式告诉你如何选择work进程数,而你要做的就是根据站点的实际情况来进行分析和调整.
一个线程处理多个连接,异步I/O
即便是拥有高性能的多路I/O就绪通知方法,但是/O操作的等待还是无法完全避免,当我们对磁盘文件调用read()或者通过sendfile()直接发送数据时,设置文件描述符为非阻塞没有任何意义,如果需要读取的数据不在磁盘缓冲区,磁盘便开始动用物理设备来读取数据, 这个时候整个进程的其他工作都必须等待
更加高效的方法便是对磁盘文件操作使用异步I/O. 但在实际上,目前很少有WEB服务器支持这种真正意义上的异步I/O,理论上在某种特定的场景中它的性能要比sendfile()更加出色,但是对于大量小文件的并发请求,文件传送可能不是关键,而多路I/O就绪通知方法的性能更加重要
目前正在开发的Lighttpd 1.5试图使用的AIO实现,一旦发布,我们可以偿试一下
注: 今天2015-5-9 刚刚到 看了一下, 1.5版本还没出来.
附录:
一. WEB服务器的模块
1. Apache 模块:
模块名称 |
功能 |
备注 |
Mod_status |
查看服务器状态:并发数,总请求数,每秒请求数,每秒字节数,空闲进程 |
从apache启动到目前时刻的平均值 |
2. Lighttp模块
Mod_status |
查看服务器状态 |
启动到目前时刻的吞吐率及最近5秒的吞吐率,单位时间的流量 |
3. nginx模块
Sub_mod_status |
查看服务器状态: |
|
二. 系统调用
1本章的系统调用
名称 |
功能描述 |
Accept() |
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 在一个套接口接受一个连接。accept()是c语言中网络编程的重要的函数接受套接字的连接 如果没有错误产生,则accept()返回一个描述所接受包的SOCKET类型的值。否则的话,返回INVALID_SOCKET错误,应用程序可通过调用WSAGetLastError()来获得特定的错误代码。 |
Gettimeofday() |
获得当前精确时间(1970年1月1日到现在的时间) |
Getsockname |
int PASCAL FAR getsockname( SOCKET s, struct sockaddr FAR* name, int FAR* namelen);
getsockname()用于获取一个的名字 若无错误发生,getsockname()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。 |
Setsockopt() |
用于任意类型、任意状态的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。 若无错误发生,setsockopt()返回0。否则的话,返回SOCKET_ERROR错误,可通过WSAGetLastError()获取相应。 |
Fcntl64() |
定义函数 int fcntl(int fd, int cmd); fcntl函数可以改变已打开的文件性质
fcntl()针对(文件)描述符提供控制.参数fd 是被参数cmd操作(如下面的描述)的描述符. fcntl的返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列四个命令有特定返回值:F_DUPFD、F_GETFD、F_GETFL、F_GETOWN.第一个返回新的,接下来的两个返回相应标志,最后一个返回一个正的进程ID或负的ID。 |
Read() |
s read(int fd,void * buf ,size_t count); 会把参数fd 所指的文件传送count个字节到buf所指的内存中。若参数count为0,则read为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。 |
Write() |
ssize_twrite(int handle, void *buf, int nbyte); write函数把buf中nbyte写入handle所指的文档,成功时返回写的字节数,错误时返回-1. |
Writev() |
writev将多个数据存储在一起,将驻留在两个或更多的不连接的缓冲区中的数据一次写出去。 |
Stat64() |
功 能: 得到文件的信息,将其保存在buf结构中,buf的地址以参数形式传递给stat。 : int _stat(const char *path,struct _stat *buffer) const char *path: 文件名或者目录名 struct _stat *buffer:结构体对象地址 |
Open() |
LINUX中open函数作用:和创建文件。 open(constchar*pathname,intflags,mode_tmode); O_RDONLY只读模式 O_WRONLY只写模式 O_RDWR读写模式 Linux中,文件描述符有一个属性:CLOEXEC,即当调用exec()函数成功后,文件描述符会自动关闭。在以往的内核版本(2.6.23以前)中,需要调用 fcntl(fd, F_SETFD, FD_CLOEXEC) 来设置这个属性。而新版本(2.6.23开始)中,可以在调用open函数的时候,通过 flags 参数设置 CLOEXEC 功能,如 open(filename, O_CLOEXEC) |
Mmap2() |
mmap将一个文件或者其它对象映射进内存。 |
Munmap() |
函数说明 munmap()用来取消参数start所指的映射内存起始地址,参数length则是欲取消的内存大小。当进程结束或利用exec相关函数来执行其他程序时,映射内存会自动解除,但关闭对应的文件描述符时不会解除映射。 |
Times() |
|
Shutdown() |
|
Select() |
本用于确定一个或多个套接口的状态,对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息,用结构来表示一组等待检查的套接口,在调用返回时,这个结构存有满足一定条件的的子集,并且select()返回满足条件的套接口的数目。 select()调用返回处于就绪状态并且已经包含在fd_set结构中的描述字总数;如果超时则返回0;否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError获取相应错误代码。 当返回为-1时,所有描述符集清0。 当返回为0时,超时不修改任何描述符集。 |
Poll() |
poll和select实现功能差不多,但poll效率高,以后要多用poll |
Close() |
|
2. 系统调用中的典型参数
accept4(4, {sa_family=AF_INET6, sin6_port=htons(50736), inet_pton(AF_INET6, "::ffff:192.168.1.102", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28], SOCK_CLOEXEC) = 10
解释:
sa_family 表示地址簇, sa--socket address
AF_INET6 表示Internet IPV6, AF--address family
sin6_port 表示: 端口号
htons表示字节顺序的转换: Host to Network Short
inet_pton 表示: IP地址转换函数,将“点分十进制” -> “二进制整数”
int inet_pton(int af, const char *src, void *dst);
这个函数转换字符串到网络地址,第一个参数af是地址族,第二个参数*src是来源地址,第三个参数* dst接收转换后的数据。
三. 脚本
本章的PHP脚本
a) 加重系统负载,长时间占用CPU的脚本
[root@master html]# cattest.php
$max = 10000000;
$sum = 0;
for ($i = 0; $i < $max; ++$i)
{
$sum += $i;
}
echo $sum;
?>
四. 工具
3. 本章的工具
a) Nmon:是linux下的性能监视工具
b) Rpm包:nmon.x86_64 0:14i-8.el6
c) 该包必须先安装EPEL的YUM源,epel-release-6-8.noarch.rpm,
d) 进入界面,按k键,可查看内核相关内容