从C10K问题来看常见的中小型服务器I/O模型

问题描述:

关于C10问题的经典描述可以查看这个网页 http://www.kegel.com/c10k.html

具体来说就是服务器如何处理10k个客户端的并发连接,即 concurrent 10,000 connection 。如果在很早以前互联网还不普及的时候,一个服务器很少会同时出现有10k的连接,但是现在互联网高速发展,这种规模的连接可能随处可见,所以如何来解决C10k的问题对于服务提供者来说是一个最先需要解决的问题。有人可以说现在硬件成本很低,连接增多可能会消耗很大的内存那么我扩充内存就可以了,cpu的负载很高,那么我提高cpu的性能就可以了。

OK,这种解决方案类似于兵来将挡,水来土掩,感觉很easy,但不幸的是,连接的客户端数超过一定的规模之后对服务器的资源的要求往往是不是线性的,大多数是O(n^2)的需求,所以你再任性,再有钱,传统的解决方案也是解决不了的。

同时,服务商一般都会从经济的角度去考虑问题,而对于互联网公司来说,很大的一笔成本就是硬件的投入和消耗,当然电费也在里面。所以如果相同的硬件成本和电费可以处理更多的请求完成更多的事情,我想他们何乐不为?

解决方案:

解决C10k问题的总体的思路就是从两个方面来考虑:

1.应用程序以何种方式和操作系统合作来处理I/O的问题,阻塞,非阻塞,还是异步I/O?

2. 应用程序如何处理任务和线程/进程的关系, one task one thread/process, 还是one thread/process more tasks, or thread poll ?

展开来说主要有:

应用程序以何种方式和操作系统合作来处理I/O

0.  阻塞式的I/O,例如read()函数,这种模型如果想实现并发就必须要使用多线程机制来完成。

1.  非阻塞I/O,例如I/O多路复用中的select,poll等通过设置句柄的方式在I/o就绪的时候由内核通知应用程序进行读写,epoll则直接通知进行那个文件描述符的读写,当然还有将传统的阻塞式I/O函数,如write()设置为O_NONBLOCK的模式来进行非阻塞的写。当然,这种方法只是适合网络IO,对于磁盘IO这种方法并不合适。

2.  使用异步IO调用(比如aio_write())来启动IO ,这种I/O由内核线程来完成I/O操作,并在完成之后通知应用程序,由于内核线程的效率较高,所以可以取得很好的效率,但是遗憾的是在*nix中并没有对此种模式直接的支持,而在windows中是可以的。这种方法对于网络IO和磁盘IO都很适合。

应用程序如何处理任务和线程/进程的关系

以客户端连接服务器的情况来说,主要有下面的几个模型

0. 一个进程服务一个客户端。即每次监听到一个链接就会创建一个进程来服务这个连接。这是(这是Unix采用的经典的方法,从80年代采用直到现在一直在使用)

1. 为每个客户端分配一个OS Level的线程,因为往往进程消耗的资源较多,并且在创建和切换上比较麻烦,所以后来OS开始支持轻量级的进程,也即线程来为每个连接做服务。但是,后来随着硬件资源越来越便宜,进程模型又变的比线程模型要好,因为进程之间不牵扯到资源的同步问题,关于这些这里不做讨论

2. 一个线程处理多个客户端等。

具体的I/O模型

下面就来看看5种最流行的IO策略

(注:这里的内容来自于网上其他作者的资源,主有:http://www.360doc.com/content/13/0522/18/1542811_287328391.shtml)

1. 用一个线程来同时为很多的客户端来服务,非阻塞IO以及水平触发方式(Level-Triggered)的就绪通知

这种方式很简单,它将所有的网络文件句柄的工作模式都设置成NON-BLOCKING,通过调用select()方法或者poll()方法来告诉应用层哪些个网络句柄有正在等待着并需要被处理的数据。这是一种非常传统的方法。通过这种机制,内核能够告诉应用层一个文件描述符是否准备好了(这里的准备好有着明确的含义,对于读描述符,准备好了意味着此时该描述符的缓冲区内数据已经准备好,读取该描述符的数据不会发生阻塞,而对于写描述符而言,准备好了意味着另外一层含义,它意味着写缓冲区已经准备好了,此时对该操作符的写操作也将不会导致任何阻塞发生),以及你是否已经利用该文件描述符作了相应的事情。因为这里的就绪通知方式是水平触发,也就说如果内核通知应用层某一个文件描述符已经就绪,而如果应用层此后一直没有完整的处理该描述符(没有读取完相应的数据或者没有写入任何数据),那么内核会不断地通知应用层该文件描述符已经就绪。这就是所谓的水平触发L-T:只要条件满足,那内核就会触发一个事件(只要文件描述符对应的数据没有被读取或者写入,那内核就不断地通知你)。

需要注意的是:内核的就绪通知只是一个提示,提示也就意味着这个通知消息未必是100%准确的,当你读取一个就绪的读文件描述符时,实际上你有可能会发现这个描述符对应的数据并没有准备好。这就是为什么如果使用就绪通知的话一定要将文件描述符的模式设置成NOBLOCK的,因为NOBLOCK模式的读取或者写入在文件描述符没有就绪的时候会直接返回,而不是引起阻塞。如果这里发生了阻塞,那将是非常致命的,因为我们只有一个线程,唯一的线程被阻塞了的话,那我们就玩完了。

这种方式的一个缺陷就是不适用磁盘文件的IO操作。将磁盘文件的操作句柄的工作模式设置成NOBLOCK是无效的,此时对该磁盘文件进行读写依然有可能导致阻塞。对于缺乏AIO(异步IO)支持的系统,将磁盘IO操作委托给worker线程或者进程是一个好方法来绕过这个问题。一个可行的方法是使用memory
mapped file,然后调用mincore(),mincore会返回一个向量来表示相应的page是否在ram缓存中,如果page不在ram缓存中,则意味着读取该页面会导致page
falut,从而引起阻塞,那么就需要通过委托的worker线程来进行IO操作。这种方式的实现方法在Linux上就是select,poll这样的系统调用。

2.使用一个线程同时服务很多个客户端,采用 noblock的IO模式以及边沿触发(Edge-Level)的就绪通知。

所谓边沿触发是相对水平触发而言的,也就是说内核只是在文件描述符的状态发生变换的时候才进行通知。这就意味着在大多数情况下,当内核通知某个读描述符就绪后,除非该读描述符内部缓冲区的所有数据已经完全被读取从而使得就绪状态发生了变化,否则内核不会发出任何新的通知,会永远沉默下去。如果该文件描述符的

receive操作返回EWOULDBLOCK错误码,这就意味着该描述符的就绪状态已经被打破,你需要等待下一次的边沿触发通知。

除了上面所说的问题,一旦使用了边沿触发,另外一个随之而来的问题就是,你需要注意一个常见的“意外事件”的问题。因为os实现边沿触发的一个常见的实现bug就是在某些情况下内核一旦收到新数据包就会通知就绪,不管你上一次的就绪通知是否被用户处理。因此你必须小心组织你的代码,你需要处理好每一个就绪通知,如果某一次就绪通知的数据没有被正确得完整得处理你就急急忙忙得开始等待下一次通知,那么下一次的就绪通知就会覆盖掉前面的数据,那么你就会永远不会恢复了。

相比之下,这种方式对于程序员编码的要求可能会更高一些,一旦应用程序错过了一次通知,那么与之对应的客户端就永远崩溃了(意外事件)或者沉默(没有读取完上一次事件产生的数据)。而方式1则会不断提醒用户缓冲区内还有数据。因此,对于边沿触发方式的就绪通知,应用层必须在每次就绪通知后读取数据,一直读到EWOULDBLOCK为止。

这种方式在Linux中主要通过epoll实现。实际上java nio采用的也是这种IO策略。Epoll和poll有一些共同之处,epoll在默认情况下也是水平触发的,此时你可以认为epoll是一个增强版的poll,它的效率更高,这是因为epoll采用了一些优化,比如只关心活跃的连接,通过共享内存空间避免了内存拷贝等等。

3 .用一个线程同时服务很多个客户端,采用异步IO。

这种IO策略实际上Linux并没有原生支持,尽管POSIX定义了它。相比之下windows就提供了很好的支持。异步IO也有内核通知,只不过这种通知不是就绪通知,而是完成通知,这就意味着一旦获得内核通知,那么IO操作就已经完成了,用户无需再调用任何操作来获取数据或者发送数据,此时数据已经好端端得放在用户定义的buffer中或者数据已经妥妥得发送出去了。与前两种方式相比,实际上aio是由内核线程或者底层线程异步地,默默得完成了IO操作,而方式1,方式2还得由用户线程来自己读取数据。相比之下,内核线程自然要高效很多。因此从IO模型的效率上来讲,windows是要优于Linux的。如果专业一点来讲,1和2这种方式一般被称之为reactor模式,3这种模式被称之为proactor模式。

4为每个客户端分配一个线程来进行IO操作

这种IO策略就比较老土了,也就是我们最常用的的一种IO模型,而且这种IO模型已经存在了几十年了。这种策略下,read和write调用都是阻塞的。它最大的问题就是每个线程都需要占据一个完整的栈帧,这个对内存的考验还是比较大的。而且过多的线程对OS也有很大的压力,很多OS如果有过多的线程其性能会有指数级别的下降。来算一下吧,假设栈帧的空间为2M,那么1G的内存最多服务512个线程,显然和我们的要求10K有不小的差距。当然,由于硬件的资源会越来越便宜,线程的内存开销可能不太会成为瓶颈。但多线程带来的进程切换的开销却有可能会长期存在。这种IO策略的关键在于OS的线程要足够强大,高效。

5 把应用层代码装进内核里

这种方式比较疯狂,如果你的team有足够的人手,而且服务器的需求量也比较大,你其实可以考虑这种方式。用专有的方法来解决问题其实也并非不可以,比如有的公司会把常用的核心算法放到FPGA或者ASIC芯片上去来解决问题,这两者的思路其实是如出一辙。对于Linux来讲,其实社区的意见还是不倾向于这么做,原因也很好理解,在内核中为应用开一个口子怎么看都不像一个好主意,一个更好的思路还是尽可能让用户空间的程序更快吧,别动不动就塞进内核里来。

时间: 2024-12-06 23:41:45

从C10K问题来看常见的中小型服务器I/O模型的相关文章

几种常见的Web服务器

Apache与Tomcat的区别 ,几种常见的web/应用服务器 APACHE是一个web服务器环境程序 启用他可以作为web服务器使用 不过只支持静态网页 如(asp,php,cgi,jsp)等动态网页的就不行 如果要在APACHE环境下运行jsp 的话就需要一个解释器来执行jsp网页 而这个jsp解释器就是TOMCAT, 为什么还要JDK呢?因为jsp需要连接数据库的话 就要jdk来提供连接数据库的驱程,所以要运行jsp的web服务器平台就需要APACHE+TOMCAT+JDK 整合的好处是

目前常见的Web服务器

首先介绍Web服务器是什么 Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,可以向浏览器等Web客户端提供文档,我们可以放置网站文件,让全世界浏览:可以放置数据文件,让全世界下载 常见的Web服务器有哪些 Apache(http://httpd.apache.org/): Apache仍然是世界上用得最多的Web服务器,市场占有率60%左右,模块非常丰富,系统非常稳定,可移植性好,但比较耗资源. Lighttpd(http://www.lighttpd.net/): Lig

常见的WEB服务器和应用服务器

转自:http://hi.baidu.com/lclkathy/blog/item/dae3be36763a47370b55a970.html 一 常见的WEB服务器和应用服务器 在UNIX和LINUX平台下使用最广泛的免费web服务器是W3C.NCSA和APACHE服务器,而Windows平台NT/2000/2003使用IIS的WEB服务器. 在选择使用WEB服务器应考虑的本身特性因素有:性能.安全性.日志和统计.虚拟主机.代理服务器.缓冲服务和集成应用程序等,下面介绍几种常用的WEB服务器.

bootstrap 栅格系统 HTTP协议 软件架构 B/S C/S 常见的WEB服务器

Day32 bootstrap Bootstrap就是响应式布局最成功的实现,为了兼容不同的浏览器采用jQuery,为了适配不同的终端采用CSS3 Media Query (媒体查询) 1.1.1 栅格系统 l 帮助手册:全部CSS样式/栅格系统,http://v3.bootcss.com/css/#grid-options l Bootstrap 提供了一套响应式.移动设备优先的流式栅格系统,随着屏幕或视口(viewport)尺寸的增加,系统会自动分为最多12列. l 栅格特点 n "行(ro

5种服务器网络编程模型讲解(转)

作者:快课网——Jay13 原文链接:http://www.cricode.com/3510.html 本文介绍几种服务器网络编程模型.废话不多说,直接正题. 1.同步阻塞迭代模型 同步阻塞迭代模型是最简单的一种IO模型. 其核心代码如下: 1 2 3 4 5 6 7 8 bind(srvfd); listen(srvfd); for(;;){ clifd = accept(srvfd,...); //开始接受客户端来的连接 read(clifd,buf,...);       //从客户端读取

常见的网站服务器架构有哪些?

1. 初始阶段的网站架构 一般来讲,大型网站都是从小型网站发展而来,一开始的架构都比较简单,随着业务复杂和用户量的激增,才开始做很多架构上的改进.当它还是小型网站的时候,没有太多访客,一般来讲只需要一台服务器就够了,这时应用程序.数据库.文件等所有资源都在一台服务器上,网站架构如下图所示: 2. 应用服务和数据服务分离 随着网站业务的发展和用户量的增加,一台服务器就无法再满足需求了.大量用户访问导致访问速度越来越慢,而逐渐增加的数据也会导致存储空间不足.这时就需要将应用和数据分离,应用和数据分离

常用/常见Java Web 服务器/应用服务器介绍

常用/常见应用服务器介绍:自己总结.整理一些常见的应用服务器,资料主要来源于网上. 常用Java Web 服务器Java Web应用程序需要部署在Java web服务器中运行,常用的Java Web服务器有Tomcat.GlassFish.WebLogic.JBoss.WebSphere.Jetty.JRun等. Apache Apache HTTP Server(简称Apache)是Apache软件基金会的一个开放源码的网页服务器,可以在大多数计算机操作系统中运行,由于其多平台和安全性被广泛使

从餐厅运营来看如何构建高性能服务器

运营一个餐厅和构建一个高性能的服务器有很多相似的地方,我们一起来看看吧. https://www.zybuluo.com/jewes/note/59732 单打独斗的单线程模式 小明的餐厅刚起步,规模很小,就只有小明一个人在忙活.当客人到来的时候,他得首先去招呼客人,等客人下单,然后去后厨炒菜,然后再把饭菜端给客人.如果在这个过程中有新的客人到来,他是没有功夫去招呼新客人,新客人就只能在那里等候,或者等不及了就离开了.这就类似于服务器在一个线程里面等待客户端的连接请求并在同一个线程中读取数据.处

几种常见的Windows 服务器无法联网/无法连接远程桌面等故障解决方案

SEO优化扫我一.服务器无法连接远程桌面 1.Ping不通IP,网站打不开,不可以远程连接.可能是服务器死机了,或者网络有问题,请尝试Web重启服务器或联系服务商确认. 2.Ping正常,网站可以打开,远程桌面无法连接,请尝试Web重启服务器或者联系服务商确认.另外你是否修改了远程桌面端口,而没有在防火墙例外该端口. 3.终端服务器超出了最大允许连接数,Windows 2003 系统默认可以同时登陆2个用户,如果第3个人登陆会有此提示.所以,每次使用完毕请在开始菜单注销你当前的登陆线程.出现此问