C10K问题描述

132 阅读8分钟

1.C10K问题

C10K就是Client 100000问题,即[在同时连接到服务器端的客户端数量超过10000个环境中,即便硬件性能足够,依然无法提供服务],简而言之,就是单机1万个并发连接问题。

甚至,在设计UNIX的PID的时候,采用了有符号的16位整数,这就导致一台计算机能够创建的进程不超过32767个。

当然,这个问题随着技术的发展很快解决了。现在大部分的个人电脑os可以创建64位进程。但是我们依然不能无限制的创建进程,因为随着并发连接数的上升会占用大量的内存,同样导致系统不可用。

为什么我们现在会有这么大的连接需求呢?因为业务驱动和技术发展。即时通信和实时互动交流越来越成为主流需求,keep-alive技术也能让浏览器产生长连接,实时在线的客户端越来越多,如果不能解决C10K问题,就需要购置大量服务器。

当然我们已经突破了这个瓶颈,具体的思路就是通过单个进程或县城服务于多个client请求,通过异步编程和事件触发机制替换轮训,IO采用非阻塞的方式、减少不必要的性能消耗。

*** 如果往两端用户拉一条由很多环组成的锁链,其中最脆肉的一个连接会断掉。因此,锁链整体的强度取决于最脆弱的一环。

1.1 高性能网络编程(一):单台服务器并发TCP连接数到底有多少?

常识1:文件句柄限制

在linux下编写网络服务器程序的朋友肯定知道,每个tcp连接都要占一个文件描述符,一旦这个文件描述符使用完了,新的连接就会返回错误"Soclet/File:Can't open so many files"

这时就需要明白os可以打开的最大文件数的限制

  • 进程限制

执行ulimit -n 输入1024,说明对于一个进程而言最多只能打开1024文件,所以要采用此默认配置最多可以并发上千个TCP连接。

ulimit -SHn number 可以永久修改进程允许打开的最大文件描述符

  • 全局限制

执行cat /proc/sys/fs/file -nr,分别为:

fs.file-max = 1000000 #已经分配的文件句柄数 net.ipv4.ip_conntrack_max = 1000000 #已经分配但没有使用的文件句柄数 net.ipv4.netfilter.ip_conntrack_max = 1000000 #最大文件句柄数

常识2: 端口范围限制

os的port1024以下是系统保留的,从1024-65535是用户使用的。由于每个TCP连接都要占一个端口号,所以我们最多可以有60000多个并发连接。(这个是一个错误思路)

  • 如何识别一个TCP连接

系统用一个四元组来唯一标识一个TCP连接(localhost ip,localhost port,remote ip,remote port)

  • server最大tcp连接数

server通过固定在某个端口上监听,等待client的连接请求。不考虑地址重用的情况下,即使server端有多个ip,本地监听端口也是独占的,因此server端tcp连接4元组中只有remote ip和remote port是可变的,因此最大tcp连接为客户端ip数 * 客户端port数,对IPV4,不考虑ip地址分类等因素,最大tcp连接数约为2^32次方 * 2^16(port),server最大tcp连接数为2^48

本文小结

上面给出的结论都是理论上的TCP并发连接数,实际上单机并发连接数肯定受硬件资源、网络资源限制。

引用:www.52im.net/thread-561-…

1.2 高性能网络编程(二):上一个10年,著名的C10K并发连接问题

过去的10年,高性能的网络编程技术领域已经解决C10K问题,下一个十年要面对10M问题(单机1000W个并发)

1.2.1 C10K问题的本质

C10k问题本质上是操作系统的问题。对于Web1.0/2.0时代的os,传统的同步阻塞模型都是一样的,传统的同步阻塞IO模型都是一样的,处理方式都是request per second,并发10K和100的区别关键在于CPU

创建的进程线程多了,数据拷贝频繁(缓存IO、内核将数据拷贝到用户进程空间、阻塞),进程/线程上下文切换消耗大,导致操作系统泵鬼,这就是C10K的本质.

可见,解决C10K问题的关键就是尽可能减少这些CPU等核心计算资源消耗,从而榨干单台服务器的性能。

1.2.2 C10K问题的解决方案探讨

要解决这一问题,从纯网络编程技术角度看,主要思路有两个:

  • 一个是对于每个可连接处理分配一个独立的进程/线程
  • 另一个思路是同一进程/线程来处理若干连接

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

这个思路最直接。但是由于申请进程/线程会占用相当可观的系统资源,同时对于进程或线程的管理会对系统造成压力.

这个思路在服务器资源还没有富裕到足够程度的时候,是不可行的。即使资源足够富裕,效率也不够高。

每个进程/线程同时处理多个连接(IO多路复用)

IO多路复用从技术实现又分很多种

  • 实现方式1:传统思路最简单的方式是循环挨个处理各个连接,每个连接对应一个socket,当所有socket都有数据的时候,这种方法是可行的。但是当读取某个socket的文件数据不ready的时候,整个应用会阻塞等待该文件句柄,即使别的文件句柄ready,也无法往下处理.

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

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

  • 实现方式2: select要解决上面阻塞的问题,思路很简单,如果我在读取文件句柄之前,先查一下它的状态,ready就处理,不ready就不处理,这不就解决问题了么?用一个fd_set结构来告诉内核同时监控多个文件句柄,当其中有文件句柄状态发生指定变化或超时,则调用返回。FD_ISSET来逐个查看是哪个文件句柄的状态发生了变化。这样做,小规模的连接问题不大,但当连接数很多的时候,逐个检查状态就会很慢。因此select往往存在管理的句柄上限(FD_SETSIZE)。
intselect(intnfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,structtimeval *timeout);

实现小结: 有连接请求了再检查处理

问题归纳:句柄上限+逐个排查所有文件句柄状态效率不高

  • 实现方式3:poll主要解决select的前两个问题:通过一个pollfd数组向内核传递需要关注的事情消除文件句柄上限,同时使用不同字段分别标注关注事件和发生事件,来避免重复初始化.

实现小结:设计新的数据结构提供使用效率。

问题归纳:逐个排查所有文件句柄状态效率不高。

  • 实现方式4: epoll既然逐个排查所有文件句柄状态效率不高,如果调用返回的时候只给应用提供发生了状态变化的文件句柄,进行排查的效率就高了很多。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

由于epoll, kqueue, IOCP每个接口都有自己的特点,程序移植非常困难,于是需要对这些接口进行封装,以让它们易于使用和移植,其中libevent库就是其中之一 目前,libevent已在以下操作系统中编译通过:Linux,BSD,Mac OS X,Solaris和Windows