我在学习网络编程的时候经常看到C10K问题,那么究竟什么是C10K问题呢?我看到了一篇好文章就转了过来,原文地址为:c10k问题

  所谓c10k问题,指的是服务器同时支持成千上万个客户端的问题,也就是concurrent 10 000 connection(这也是c10k这个名字的由来)。由于硬件成本的大幅度降低和硬件技术的进步,如果一台服务器同时能够服务更多的客户端,那么也就意味着服务每一个客户端的成本大幅度降低,从这个角度来看,c10k问题显得非常有意义。

为了解决C10K问题,有各种各样的IO策略,它们的分歧或者说不同之处大概如下:

1、是否在一个线程当中处理多个IO调用

<1> 单线程处理多个IO调用不靠谱,还是使用阻塞的,同步的调用方法。在这种情况下利用多线程或者多进程来达到并发

<2> 使用非阻塞的IO(比如被设置成O_NONBLOCK模式的socket的write方法)来启动IO,利用IO的就绪通知(比如poll()方法或者/dev/poll)来确认什么时候可以在同一个通道上启动一轮新的IO。通常这种方法只是适合网络IO,对于磁盘IO这种方法并不合适(注:下文会讲到为什么不合适)

<3> 使用异步IO调用(比如aio_write())来启动IO ,当IO完成时,会有通知(比如信号量或者完成端口)告诉你什么时候IO操作结束了。这种方法对于网络IO和磁盘IO都很适合。

2、如何控制服务于每个客户端的代码

<1> 一个进程服务一个客户端。(这是Unix采用的经典的方法,从80年代采用直到现在一直在使用)

<2> 一个OS-Level的线程来同时处理很多客户端,每个客户端的控制是通过:

    <2.1> 一个用户态的线程(比如GNU状态线程,经典的java绿色线程)

    <2.2> 一个状态机

    <2.3> 线程延伸

<3> 为每个客户端分配一个OS Level的线程

<4> 为每个活跃的客户端分配一个OS Level的线程

  是否使用标准的OS 服务,更直接的方法是把部分代码放入内核中。

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

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

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

  需要注意的是:内核的就绪通知只是一个提示,提示也就意味着这个通知消息未必是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模式以及边沿触发的就绪通知(注:目前最主流的方式)

  所谓边沿触发是相对水平触发而言的,也就是说内核只是在文件描述符的状态发生变换的时候才进行通知。这就意味着在大多数情况下,当内核通知某个读描述符就绪后,除非该读描述符内部缓冲区的所有数据已经完全被读取从而使得就绪状态发生了变化,否则内核不会发出任何新的通知,会永远沉默下去。如果该文件描述符的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的。另外题外话再插一句,实际上微软的windows在很多概念上还是比较先进的,比如windows是微内核的,又比如这里的AIO,所以微软其实没有大家所想象的那么不堪,还是挺牛的。如果专业一点来讲,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来讲,其实社区的意见还是不倾向于这么做,原因也很好理解,在内核中为应用开一个口子怎么看都不像一个好主意,一个更好的思路还是尽可能让用户空间的程序更快吧,别动不动就塞进内核里来。

  好了,这就是目前为止关于解决C10K问题的一些思路,实际上思路2还是解决问题的主流方式。关于这个问题更多的资料大家可以参阅http://www.kegel.com/c10k.html#threaded,这个页面上对于C10K问题有着比较全面的分析和总结,本文实际上也只不过这个页面主要内容的一个摘要以及评注。

[转]何为C10K问题的更多相关文章

  1. 垃圾回收基本算法 内存管理 GC大统一理论

    <垃圾收集> (豆瓣) https://book.douban.com/subject/1157908/ 第1章 简介1.1 内存分配的历史1.1.1 静态分配1.1.2 栈分配1.1.3 ...

  2. [linux]如何为Virtualbox虚拟硬盘扩容(转载)

    前言 这个教程介绍如何为Virtualbox虚拟硬盘扩容,虚拟硬盘分为动态分配大小和固定虚拟硬盘,扩容的方法不一样: 如何为动态分配的Virtualbox虚拟硬盘扩容 如何为固定大小的Virtualb ...

  3. 【原创】高性能网络编程(二):上一个10年,著名的C10K并发连接问题

    1.前言 对于高性能即时通讯技术(或者说互联网编程)比较关注的开发者,对C10K问题(即单机1万个并发连接问题)应该都有所了解."C10K"概念最早由Dan Kegel发布于其个人 ...

  4. AngularJS入门心得2——何为双向数据绑定

    前言:谁说Test工作比较轻松,最近在熟悉几个case,差点没疯.最近又是断断续续的看我的AngularJS,总觉得自己还是没有入门,可能是自己欠前端的东西太多了,看不了几行代码就有几个常用函数不熟悉 ...

  5. C10K 问题引发的技术变革

    C10K 问题引发的技术变革 http://rango.swoole.com/archives/381

  6. 何为“精通Java”

    何为精通Java?本来Java仅仅是一门语言,但从应用技术的角度来看,精通Java是可以无边无际的.很可能你可以对James说:我精通J2EE.JVM.Java服务器.大数据等等一些和Java相关的应 ...

  7. c10k问题及其解决方案

    本文主要讲述高并发http应用中的c10k瓶颈问题:在很多服务器初始状态下,无法服务1w左右的并发连接.这与每次服务的资源消耗.服务器的硬件配置固然有关,但很多时候是被linux的默认配置以及软件st ...

  8. C10K问题渣翻译

    The C10K problem [Help save the best Linux news source on the web -- subscribe to Linux Weekly News! ...

  9. The C10K problem

    原文链接:http://www.kegel.com/c10k.html It's time for web servers to handle ten thousand clients simulta ...

随机推荐

  1. LabVIEW串口通信的一个例子-串口"示波器"

    1.程序界面:<ignore_js_op> 功能介绍:左边上方串口接收区,下方为串口字符发送区.右方为一个波形图表,在程序内部每次将串口发送过来的数据,以f%格式化,显示在波形图表上.按钮 ...

  2. u32 mac以及arp匹配

    # Examples that match MAC (a big "thank you" to Julian Anastasov for this!): M0 through M5 ...

  3. Link Aggregation and LACP with Open vSwitch

    In this post, I’m going to show you how to use link aggregation (via the Link Aggregation Control Pr ...

  4. Eclipse 手机开发不能运行

    ADB server didn't ACK问题 The connection to adb is down, and a severe error has occured. 1.先把eclipse关闭 ...

  5. Windows与Linux的双系统的安装顺序及Linux的补救办法

    如果安装多重引导,最好先安装Windows再安装Linux系统,因为: Linux在安装的时候,你可以选择将引导加载程序安装在MBR或个别分区的启动扇区,而且Linux的loader可以手动设置菜单( ...

  6. [转载]oracle 11g不能导出空表的多种解决方法

    原文地址:11g不能导出空表的多种解决方法">oracle 11g不能导出空表的多种解决方法作者:Anlin_Yang ORACLE 11g 用exp命令导出库文件备份时,发现只能导出 ...

  7. 瓜娃《guava》api快速入门

    1,大纲 让我们来熟悉瓜娃,并体验下它的一些API,分成如下几个部分: Introduction Guava Collection API Guava Basic Utilities IO API C ...

  8. spark Streaming的Receiver和Direct的优化对比

    Direct 1.简化并行读取:如果要读取多个partition,不需要创建多个输入DStream然后对它们进行union操作.Spark会创建跟Kafka partition一样多的RDD part ...

  9. HDU-5536 Chip Factory (字典树)

    题目大意:给n个数,编号为1~n,取三个编号不同的数,使表达式(a+b)^c的值最大. 题目分析:将这n个数按二进制位建立一棵trie.枚举i.j的和,查询亦或最大值,但在查询之前要把i.j在trie ...

  10. kuangbin_UnionFind J (POJ 2492)

    加权并查集mod2模板 本体的难点是bug的释义(笑) #include <iostream> #include <string> #include <cstdio> ...