TCP link

在网络开发中,我发现有很多同学对一个基础问题始终是没有彻底搞明白。那就是一台机器最大究竟能支持多少个网络连接?我想我有必要单独发一篇文章来好好说一下这个问题。很多同学看到这个问题的第一反应是65535。原因是:“听说端口号最多有65535个,那长连接就最多保持65535个了”。是这样的吗?还有的人说是应该受TCP连接里四元组的空间大小限制,这样算起来就是非常非常大的一个数字了。这两个答案都对,也都不对。

其实要想把这个问题搞清楚,最最最关键的地方在于要把TCP连接的两端里的角色分清楚-客户端和服务器端。你手头的任何一台服务器,一般情况下都即是服务器,又是客户端。例如对于你的开发的后端接口,对于用户来说你是服务器端。但你得请求Redis、Mysql去获取数据,这时候又变成了客户端。如果不把这台机器作为客户端和服务器端两种角色拆开来理解,你将永远被这个问题困惑下去。 所以本文分别从客户端、服务端两块来展开聊聊。

客户端

现在我们单独来说客户端,当一台机器作为客户端的时候,究竟能支持多少个TCP连接? 空嘴说没有啥意思,我们直接用代码来试试。

1.小试牛刀

开始实验之前我们先来check下手头机器上的端口数量的配置

$ sysctl -a | grep ip_local_port_range
net.ipv4.ip_local_port_range = 15000    65000

通过上述内核参数的输出看到内核开放了50000个端口可以供TCP连接使用。接下来是一段看起来长,但其实非常简单的TCP客户端连接的代码。用它来连接你的任意一个TCP Server,比如Nginx、Redis啥的都可以。

我通过这段代码对我的某台机器上的Nginx发起了连接

$ php client1.php 某台服务器IP 80

通过netstat命令看到连接数量稳步上升,但当上升到5W的时候,出现了一条报错

socket_connect() 失败的原因是:Cannot assign requested address

回头想想我们的ip_local_port_range参数值,65000-15000就只开放了5万个。其实是超过这个限制了。 这个时候我们似乎可以初步得出一个相结论.当Linux作为客户端建立连接的时候,最大连接数量是受内核参数net.ipv4.ip_local_port_range限制 而ip_local_port_range是可配置的,最大理论范围是0-65535

进阶

如果这个时候你相信了我上面的结论的话,就又被我带了沟里了。为什么这么说,让我们来看下面的实验。

首先通过ifconfig命令看到我的机器上有两块网卡,每块网卡都已经配置好了一个ip。

接着我们修改一下第一个实验的代码,在发起连接之前允许使用socket_bind来绑定ip。

接下来我们分别启动两个控制台,分别执行一下代码。其中10.143.x.x和10.153.x.x是实验用机的两个网卡ip。

$ php client1.php 10.143.x.x 某台服务器IP 80
$ php client2.php 10.153.x.x 某台服务器IP 80

这个时候通过ss命令监控本机的ESTABLISH连接,发现已经突破5万,并向10万逼近了。

$ ss -n | grep ESTAB | wc –l
90005 

现在我们终于可以得出更为正确的结论了,对于有1个Ip的客户端来说,受限于ip_local_port_range参数,也受限于65535。但单Linux可以配置多个ip,有几个ip,最大理论值就翻几倍

多张网卡不是必须的。即使只有一张网卡,也可以配置多ip。k8s就是这么干的,在k8s里,一台物理机上可以部署多个pod。但每一个pod都会被分配一个独立的ip,所以完全不用担心物理机上部署了过多的pod而影响你用的pod里的TCP连接数量。在ip给你的那一刻,你的pod就和其它应用隔离开了。

服务端

我们现在在来回头考虑服务器端。对于服务器来说,最大支持的并发连接是多少呢?就有人开始可爱地糊涂了:“服务器端理论也是端口限制吗?”。好,假设如果受影响的话,那我们的Nginx服务器只监听了一个80端口。那Nginx只能接受一个TCP连接喽?这明显是太荒唐了。

好,我们再看另外一个靠谱一点的答案。那就是一条TCP连接是由一个四元组组成的。不考虑地址重用(unix的SO_REUSEADDR选项)的情况下,对于我们这台Nginx Server来说,它的IP和端口是固定的。cp连接4元组中只有remote ip(也就是client ip)和remote port(客户端port)是可变的。它可能建立的最大的连接数是2的32次方(ip数)×2的16次方(port数)。这是2.8*10的14次方的一个大数字,两百万亿!!

Linux上除了监听80以外,还可以监听其它的端口,例如Mysql的3306, Redis的6339,当然所有65535个端口你都可以用来监听一遍。这样理论上线就到了2的32次方(ip数)×2的16次方(port数)×2的16次方(服务器port数)个。感兴趣你可以算一下,这个基本相当于无穷个了。

不过理想和实际总是会有差距的,因为Linux每维护一条TCP连接都要花费资源。处理连接请求,保活,数据的收发时需要消耗一些CPU,维持TCP连接主要消耗内存。我们题目的问题是考虑最大多少个连接,所以我们先不考虑数据的收发。那么TCP在静止的状态下,就不怎么消耗CPU了,主要消耗内存。而Linux上内存是有限的。
我们今天先直接把结论抛出来,一条TCP连接如果不发送数据的话,消耗内存是3.3K左右。如果有数据发送,需要为每条TCP分配发送缓存区,大小受你的参数net.ipv4.tcp_wmem配置影响,默认情况下最小是4K。如果发送结束,缓存区消耗的内存会被回收详细的分析过程敬请期待接下来的另一篇文章。

假设你只保持连接不发送数据,那么你服务器可以建立的连接最大数量 = 你的内存/3.3K。 假如是4GB的内存,那么大约可接受的TCP连接数量是100万左右。

这个例子里,我们考虑的前提是在一个进程下hold所有的服务器端连接。而在实际中的项目里,为了收发数据方便,很多网络IO模型还会为TCP连接再创建一个线程或协程。拿最轻量的golang来说,一个协程栈也需要2KB的内存开销。

结论

一台机器最大究竟能支持多少个网络连接?这个简单的问题里其实埋了坑,导致无数的英雄好汉被困惑不解。就和树上九只鸟打死一只还剩几只的问题一样,没有和你说清楚树上是真鸟,还是假鸟。也没有说枪是有声还是无声的。通过今天的分析,相信你终于可以扬眉吐气把这个问题踩在脚下摩擦了。来,总结下:

  • TCP连接的客户端机:每一个ip可建立的TCP连接理论受限于ip_local_port_range参数,也受限于65535。但可以通过配置多ip的方式来加大自己的建立连接的能力。
  • TCP连接的服务器机:每一个监听的端口虽然理论值很大,但这个数字没有实际意义。最大并发数取决你的内存大小,每一条静止状态的TCP连接大约需要吃3
    .3K的内存。

Leave a Reply

Your email address will not be published. Required fields are marked *