C10K问题

C10K问题

即单机1万个并发连接问题

由来

早期互联网是一个小群体的集合,用户不多,一台服务器在线100人就已经是一个大型应用了,不存在C10K问题,后期www网站,浏览器,雅虎出现后,才出现这个问题。

早期QQ也存在这个问题,但是当时没有epoll这样的技术,所以他们直接使用了UDP绕开了这个问题,后来QQ和微信才改成TCP

解读

一个经典的基于select的程序能在服务器上处理1000并发的数据,但是他两倍性能的服务器上往往处理不了2000并发的数据。策略不当的时候,大量操作的消耗和当前连接数n成线性相关,会导致单个任务的资源消耗和当前的连接数会是O(n),而服务器需要同时对数以万计的socket进行IO处理,这显然会导致系统的吞吐量不能和机器性能匹配

C10K问题的解决方案

一、每个进程/线程处理一个连接

这个思路最直接,但是由于申请进程/线程会占用相当可观的系统资源,会对系统造成压力,且不具备良好的扩展性。

二、每个进程/线程同时处理多个连接(I/O多路复用)

1. 循环,挨个处理各个连接

这也是传统的方式,循环挨个处理各个连接,每个连接对应一个socket,当所有socket都有数据的时候这种方案是可行的,但是当应用读取某个socket的文件数据不ready的时候,整个应用会阻塞在这里等待该文件句柄,即时别动文件ready,也无法往下处理

小结:直接循环处理多个连接

归纳:任意文件句柄的不成功会阻塞住整个应用

2. select方案

在读取文件句柄之前,先查下他的状态,ready了就进行处理,不ready就不处理。用一个fd_set结构体来告诉内核同时监控多个文件句柄,当其中有文件句柄发生变化的时候,则调用返回。之后应用用fd_isset来逐个查看哪个文件句柄状态发生变化,这样,小规模的连接问题不大,连接数过多的时候,检查就较慢。select存在管理的句柄上限(1024),同时在使用上,因为只有一个字端关注和发生事件,每次调用的时候都要重新初始化fd_set结构体

小结:有连接请求抵达了再检查处理

归纳:句柄上限+重复初始化+逐个排查所有文件句柄状态效率不高

3. poll方案

poll主要解决select的前两个问题,通过一个pollfd数组向内核传递需要关注的事件,消除文件句柄上限(因为select是bitmap写死的,poll是动态分配内存),同时使用不同字端分别标注关注事件和发生事件,来避免重复初始化。

小结:设计新的数据结构提升使用效率

归纳:逐个排查所有文件句柄状态效率不佳

4. epoll方案

返回调用的时候只给应用提供发生了状态变化的文件句柄(可能是数据ready),进行排查的效率就高了。epoll使用这种设计,适用于大规模应用场景。实验证明,当文件句柄数目超过10之后,epoll性能将优于select和poll;当文件句柄数量达到10K的时候,epoll已经超过select和poll两个数量级。

小结:只返回状态变化的文件句柄

归纳:依赖特定平台(Linux)

补充:
因为Linux是互联网企业中使用率最高的操作系统,epoll成为了C10K killer,高并发,高性能,异步非阻塞的代名词。FreeBSD推出了kqueue,Linux推出了epoll,Windows推出了IOCP,Solaris推出了/dev/poll。这些操作系统提供的功能就是为了解决C10K问题。epoll技术的编程模型就是异步的非阻塞回调,也可以叫做Reactor,事件驱动,事件循环(EventLoop),Nginx,libevent,node.js这些就是epoll时代的产物

5. 移植:libevent库

由于epoll、kqueue、IOCP每个接口都有自己的特点,程序移植非常困难,于是要对这些接口进行封装,以让他们易于使用和移植,其中libevent就是其中之一。