type
status
date
slug
summary
tags
category
icon
password
前面三讲我们讲解了select,poll,epoll三种I/O多路复用技术,为高性能网络编程提供了必要的知识储备。这一讲,我们了解一下历史上赫赫有名的 C10K 问题,并借着 C10K 问题一起探讨下高性能网络编程的几种方法。

9.1 The C10K problem

C10K 问题是由一个叫 Dan Kegel 的工程师提在 1999 年提出来的:如何在一台物理机上,同时保持 10000 条连接(concurrently handling ten thousand connections)。
当然,在现在的条件下,我们使用 libevent 或者 Java Netty 等框架轻轻松松就可以写出支持并发数量超过一万的服务器,经过优化之后,甚至可以达到十万,乃至百万。但在 2000 年左右,解决 C10K 问题可是一个了不起的成就。
💡
现在的问题称为C10M,也就是如何在一台物理机上同时保持一千万条连接!
我们首先来看一下,在系统资源层面是否能够同时支持 10000 条连接(TCP 连接)。
Q: 同时支持 10000 条连接需要大量消耗哪些资源呢? A: 文件描述符,内存,网络带宽。
文件描述符
每个客户端连接都需要在服务器上占用一个文件描述符。一旦文件描述符不够用了,新的连接就会被丢弃。在 Linux 下,每个进程的文件描述符数目是有限制的,没有经过修改的值一般都是 1024。
不过,我们可以对这个值进行修改,使得系统可以支持 10000 个以上的文件描述符。
内存
每个 TCP 连接都需要占用一定的内存,比如发送缓冲区和接收缓冲区。下面命令可以显示 TCP 连接接收缓冲区和发送缓冲区的大小:
这三个值分别表示最小分配值、默认分配值和最大分配值。按照默认分配值计算,一万个连接需要的内存消耗为:
当然,应用程序也需要一些缓冲区来收发数据,所以一万个连接大约需要 1.5G 左右的内存空间。在 2000 左右,1.5G 的内存开销并不是一个大的问题。
网络带宽
假设每个连接每秒传输 1KB 的数据,那么一万个连接所需的带宽为。千兆以太网卡是在 2000 左右出现的,所以网络带宽在当时也是够用的。
综上所述,C10K 问题在系统资源层面是可以解决的。

9.2 解决之道

在系统资源层面可以解决,并不代表在应用层面可以很好地解决 :(
我们知道,在网络编程中会频繁地涉及到用户态—内核态间的数据拷贝。如果应用程序设计地不够好,在低并发的情况下可能可以良好地工作,但是到了高并发的情况,其性能会急剧下降。
要想真正解决 C10K 问题,我们需要从两个层面来统筹考虑:
  • I/O 模型:应用程序如何和操作系统配合,感知 I/O 事件的发生,并调度处理各个套接字上的 I/O 事件。我们前面讲过的阻塞 I/O, 非阻塞 I/O 以及 I/O 多路复用都是 I/O 模型。(注:W. Richard Stevens 在 Unix Network Programming, Volume 1: The Sockets Networking API 中总结了 5 种 I/O 模型)。
  • 应用程序如何分配进程、线程来服务上万个连接?(注:这是我们接下来的课程要讨论的内容)。
这两个层面一些解的组合,就形成了 C10K 问题的几种解决方案,下面我们一起来看一看。

9.2.1 阻塞 I/O + 多进程

9.2.2 阻塞 I/O + 多线程

9.2.3 非阻塞 I/O + 轮询 + 单线程

9.2.4 I/O 多路复用 + 单线程(多线程)

9.2.5 异步 I/O + 多线程

9.3 归纳总结

支持单机 1 万并发的问题被称为 C10K 问题,为了解决 C10K 问题,需要重点考虑这两个方面:
  • 如何和操作系统配合,感知 I/O 事件的发生?
  • 如何分配和使用进程、线程资源来服务上万个连接?
基于这两个问题解的组合,产生了一些通用的解决方法。在 Linux 下,解决高性能问题的利器是非阻塞 I/O 加上epoll机制,再加上多线程。

📎 参考文章

 
💡
有关Notion安装或者使用上的问题,欢迎您在底部评论区留言,一起交流~
 
Linux系统编程(0)——前言高性能网络编程(8)——I/O 多路复用之 epoll