但C10K问题,让我们意识到在超大数量的网络连接下,机器设备和网络速度不再是瓶颈,瓶颈在于操作系统和IO应用程序的沟通协作的方式。
举个例子,一万个socket连接过来,传统的IO编程模型要开万个线程来应对,还要注意,socket会关闭打开,一万个线程要不断的关闭线程重建线程,资源都浪费在这上面了,我们算建立一个线程耗1M内存,1万个线程机器至少要10G内存,这在IA-32的机器架构下基本是不可能的(要开PAE),现在x64架构才有可能舒服点,要知道,这仅仅是粗略算的内存消耗。别的资源呢?
所以,高性能的网络编程(即IO编程),第一,需要松绑IO连接和应用程序线程的对应关系,这就是非阻塞(nonblocking)、异步(asynchronous)的要求的由来(构造一个线程池,epoll监控到有数的fd,把fd传入线程池,由这些worker thread来读写io)。第二,需要高性能的OS对IO设备可读写(数据来了)的通知方式:从level-triggered notification到edge-triggered notification,关于这个通知方式,我们稍后谈。
需要注意异步,不等于AIO(asynchronous IO),Linux的AIO和java的AIO都是实现异步的一种方式,都是渣,这个我们也接下来会谈到。
针对前面说的这两点,我们看看select和poll的问题
这两个函数都在每次调用的时候要求我们把需要监控(看看有没有数据)的文件描述符,通过数组传递进入内核,内核每次都要扫描这些文件描述符,去理解它们,建立一个文件描述符和IO对应的数组(实际内核工作会有好点的实现方式,但可以这么理解先),以便IO来的时候,通知这些文件描述符,进而通知到进程里等待的这些select、poll。当有一万个文件描述符要监控的时候呢(一万个网络连接)?这个工作效率是很低的,资源要求却很高。
我们看epoll
epoll很巧妙,分为三个函数,第一个函数创建一个session类似的东西,第二函数告诉内核维持这个session,并把属于session内的fd传给内核,第三个函数epoll_wait是真正的监控多个文件描述符函数,只需要告诉内核,我在等待哪个session,而session内的fd,内核早就分析过了,不再在每次epoll调用的时候分析,这就节省了内核大部分工作。这样每次调用epoll,内核不再重新扫描fd数组,因为我们维持了session。
说道这里,只有一个字,开源,赞,众人拾柴火焰高,赞。
epoll的效率还不仅仅体现在这里,在内核通知方式上,也改进了,我们先看select和poll的通知方式,也就是level-triggered notification,内核在被DMA中断,捕获到IO设备来数据后,本来只需要查找这个数据属于哪个文件描述符,进而通知线程里等待的函数即可,但是,select和poll要求内核在通知阶段还要继续再扫描一次刚才所建立的内核fd和io对应的那个数组,因为应用程序可能没有真正去读上次通知有数据后的那些fd,应用程序上次没读,内核在这次select和poll调用的时候就得继续通知,这个os和应用程序的沟通方式效率是低下的。只是方便编程而已(可以不去读那个网络io,方正下次会继续通知)。
于是epoll设计了另外一种通知方式:edge-triggered notification,在这个模式下,io设备来了数据,就只通知这些io设备对应的fd,上次通知过的fd不再通知,内核不再扫描一大堆fd了。
基于以上分析,我们可以看到epoll是专门针对大网络并发连接下的os和应用沟通协作上的一个设计,在linux下编网络服务器,必然要采用这个,nginx、PHP的国产异步框架swool、varnish,都是采用这个。
注意还要打开epoll的edge-triggered notification。而java的NIO和NIO.2都只是用了epoll,没有打开edge-triggered notification,所以不如JBoss的Netty。
接下来我们谈谈AIO的问题,AIO希望的是,你select,poll,epoll都需要用一个函数去监控一大堆fd,那么我AIO不需要了,你把fd告诉内核,你应用程序无需等待,内核会通过信号等软中断告诉应用程序,数据来了,你直接读了,所以,用了AIO可以废弃select,poll,epoll。
但linux的AIO的实现方式是内核和应用共享一片内存区域,应用通过检测这个内存区域(避免调用nonblocking的read、write函数来测试是否来数据,因为即便调用nonblocking的read和write由于进程要切换用户态和内核态,仍旧效率不高)来得知fd是否有数据,可是检测内存区域毕竟不是实时的,你需要在线程里构造一个监控内存的循环,设置sleep,总的效率不如epoll这样的实时通知。所以,AIO是渣,适合低并发的IO操作。所以java7引入的NIO.2引入的AIO对高并发的网络IO设计程序来说,也是渣,只有Netty的epoll+edge-triggered notification最牛,能在linux让应用和OS取得最高效率的沟通。
《Linux高性能服务器编程》(游双)电子书网盘下载免费在线阅读
资源链接:
链接:https://pan.baidu.com/s/1yc7SJ6UluWS11Q6YOaPUAw
提取码:pbox书名:Linux高性能服务器编程
作者:游双
豆瓣评分:7.9
出版社:机械工业出版社
出版年份:2013-5-1
页数:360
内容简介:
本书是Linux服务器编程领域的经典著作,由资深Linux软件开发工程师撰写,从网络协议、服务器编程核心要素、原理机制、工具框架等多角度全面阐释了编写高性能Linux服务器应用的方法、技巧和思想。不仅理论全面、深入,抓住了重点和难点,还包含两个综合性案例,极具实战意义。
全书共17章,分为3个部分:第一部分对Linux服务器编程的核心基础——TCP/IP协议进行了深入的解读和阐述,包括TCP/IP协议族、TCP/IP协议,以及一个经典的TCP/IP通信案例;第二部分对高性能服务器编程的核心要素进行了全面深入的剖析,包含Linux网络编程API、高级I/O函数、Linux服务器程序规范、高性能服务器程序框架、I/O复用、信号、定时器、高性能I/O框架库Libevent、多进程编程、多线程编程、进程池和线程池等内容,原理、技术与方法并重;第三部分从侧重实战的角度讲解了高性能服务器的优化与监测,包含服务器的调制、调试和测试,以及各种实用系统监测工具的使用等内容。
作者简介:
游双,资深Linux软件开发工程师,对Linux网络编程,尤其是服务器端的编程,有非常深入的研究,实战经验也十分丰富。曾就职于摩托罗拉,担任高级Linux软件工程师。此外,他还精通C++、Android、QT等相关的技术。活跃于Chinaunix等专业技术社区,发表了大量关于Linux网络编程的文章,深受社区欢迎。
1、减少内存分配和释放
服务器在运行过程中,需要大量的内存容量来支撑,内存的分配和释放就尤为关键。用户在使用服务器的时候,可以通过改善数据结构以及算法制度来减少中间临时变量的内存分配和数据复制时间。
另外,可以选择使用共享内存模式来降低内存的分配和释放问题。共享内存在多处理器系统中,可以被不同的中央处理器访问,也可以有不同的进程共享,是一种非常快的进程通信方式。
2、使用持久链接
持久链接也被称为场链接,是通过TCP通信的一种方式。在一次TCP链接中持续发送多份数据而不断开连接。
从性能角度上来讲,建立TCP链接次数越少,越有利于性能的提升,尤其对于密集型图片或者网页等数据处理上来说有明显的加速作用。
3、改进I/O模型
I/O操作根据设备形式有不同的类型,例如我们常见的内存I/O,网络I/O,磁盘I/O。针对网络I/O和磁盘I/O, 它们的速度要慢很多,可以选择采用高带宽网络适配器可以提高网络I/O速度。
以上的I/O操作时需要CPU来调度的,这就需要CPU空出时间来等待I/O操作。如果在CPU调度上使用时间较少,也就能节约出CPU的处理时间,从这一点上来说也是提升高服务器并发处理能力的方式。
4、改进服务器并发数策略
服务器高并发策略的调整,是为了让I/O操作和CPU计算尽量重叠进行。一方面使CPU在I/O操作时等待时间内不要空闲,另一方面也是为了最大限度缩短等待时间。【感兴趣的话点击此处,了解一下】
欢迎分享,转载请注明来源:夏雨云
评论列表(0条)