Skip to the content.

如何管理大量TCP连接

本章的目标是介绍如何管理大量的TCP连接(UDP要用好真不简单,功力有限,抛开不谈),思路如下:

挑战

经典问题:C10K和C10M

引言

我印象中小学(2003年)时代一个月才有1次的计算机打字课(进去还要换拖鞋,也是很奇怪),是用的大头机和windows98系统:

大头机

到初中年代(2008),县城里的网吧也还有许多电脑是这种大头机(辐射非常大,玩一会头晕眼花),然后慢慢的才有液晶显示器,直到大学才有自己的第一款笔记本(2010)。

也是从2010年后,自己各种折腾电脑,重装系统,到自己工作,才开始关注CPU,i3、i5和i7,甚至最近2020年新款的mac pro的i9处理器,从单核到双核到4核8线程等等。

计算机飞速发展,现在一台普通的笔记本甚至智能手机都能媲美20世纪的服务器了。【PK图,后续添加】

大学时代,我在i3配置的笔记本上用C#实现了一个windows tcp server,一开始只能支持几百个连接(客户端启动和退出一频繁,服务就崩溃),然后使用了IOCP的技术,实现了几千个连接的支持(受限于笔记本)。直到工作,任意找一台台式机,用Linux写一个epoll服务端,就可以实现上万的连接支持,所以现在C10K(单机支持10*1000个连接)已经不在是问题了。

C10K

简单来说,C10K就是指单机如何支持1万个(10*1000)连接,主要是在互联网的早期,Web服务器在硬件有限的情况下,如何能支持更高的并发(众所周知HTTP协议是基于TCP之上的,所以需要建立连接)。

虽然目前服务器性能越来越高,但是如果方法不对(Linux下如果使用select I/O复用模型,则默认最大只能支持1024个连接,更多就需要修改内核并重新编译了),也是做不到1万个连接的支持的。

左耳朵耗子大叔的专栏里面有提到,提出这个问题的人叫丹·凯格尔(Dan Kegel),目前在 Google 任职。可以阅读一下这篇文章了解下这个问题:The C10K problem(翻译版)

这个问题的本质,引用左耳朵耗子大叔的话来解释:

C10K 问题本质上是操作系统处理大并发请求的问题。对于 Web 时代的操作系统而言,客户端过来的大量的并发请求,需要创建相应的服务进程或线程。这些进程或线程多了,导致数据拷贝频繁(缓存 I/O、内核将数据拷贝到用户进程空间、阻塞), 进程 / 线程上下文切换消耗大,从而导致资源被耗尽而崩溃。这就是 C10K 问题的本质。

了解这个问题,并了解操作系统是如何通过多路复用的技术来解决这个问题的,有助于你了解各种 I/O 和异步模型,这对于你未来的编程和架构能力是相当重要的。

C10M

更近一步,单机如何支持千万级(10*1000*1000)连接可以看一下这篇文章:The Secret To 10 Million Concurrent Connections -The Kernel Is The Problem, Not The Solution

解决之道

高性能I/O

理论部分内容太多,可以跳转到《第10章 高性能I/O》一节详细了解。

总体而言,要解决C10K问题,单机支持1万连接在当下是没有任何挑战的,只需要使用Epoll I/O复用即可。主流的网络库都能轻松支持:

特别需要注意的是,使用epoll也有三种不同的方案,单Reactor单线程、单Reactor多线程和主从Reactor多线程,读者应区分他们的应用场景,选择适合自己的模型,这三种模型在《第10章 高性能I/O:2种设计模式》一节中有详细介绍。上面列举的各种网络库,应该都有开放接口来设置,比如muduo库中 TcpServer::setThreadNum() 就可以设置使用哪种模型,注意线程数应当和CPU数量保持一致。

  /// Set the number of threads for handling input.
  ///
  /// Always accepts new connection in loop's thread.
  /// Must be called before @c start
  /// @param numThreads
  /// - 0 means all I/O in loop's thread, no thread will created.
  ///   this is the default value.
  /// - 1 means all I/O in another thread.
  /// - N means a thread pool with N threads, new connections
  ///   are assigned on a round-robin basis.
  void setThreadNum(int numThreads);

其他细节

除了上面提到的高性能I/O之外,还有诸多细节可以优化我们的性能,这里进行简单的罗列,详细介绍和实战后期再补充:

实现

假设我们要开发一个简单版IM服务器,他具备以下特性:

  1. 在2C4G的机器上,可以同时支持50,000个以上的TCP连接
  2. 具备简单的认证功能:客户端连接上来后,必须进行账号和密码认证,才能进行后续的聊天。这里为了简单起见,账号密码硬编码到了本地。
  3. Echo聊天功能:认证完成后,客户端发送任何的文字,服务器都将回复同样的文字,就像有一个人在和你聊天一样,只不过比较傻。
  4. 简单版多端消息同步:现在主流的IM,都支持手机和PC软件同时登录,并且手机发了一个消息后,PC上能同步显示,我们这里也希望能实现这个功能。

别看只有这么几个功能,大多数的IM服务里面都有这么一个角色,它是服务器程序和客户端通信的桥梁,通常称之为网关(TCP)。

数据模型

因为用户可能在不同的设备上同时登录,故我们需要抽象出一个User实体,接下来我们看一下他们的关系和主要的接口。

User-Connection模型

Domain-User-Connection模型

C++实现

Go实现

Java实现

改进

万级别的架构:单机

十万级别的架构:多机

百万级别的架构:集群

万到百万的挑战

三高:高性能、高可用、高并发

高可用技术

高并发技术

连接”桶”

分布式集群技术