有读者分享过自己的面试经历:好不容易熬到技术终面,却冷不丁被问了这么一个问题——“一台服务器到底能支持多少条 TCP 连接?”当时直接懵了。
今天我们就来把这个问题掰扯清楚。
1. 一台服务器最大能打开的文件数
在 Linux 一切皆文件,那么一台服务器最大能打开多少个文件呢?Linux 上能打开的最大文件数量受三个参数影响:
fs.file-max(系统级别参数):描述整个系统可以打开的最大文件数量。但 root 用户不受该参数限制(比如:现在整个系统打开的文件描述符数量已达到 fs.file-max,此时 root 用户仍然可以使用 ps、kill 等命令或打开其他文件描述符)。
soft nofile(进程级别参数):限制单个进程上可以打开的最大文件数。只能在 Linux 上配置一次,不能针对不同用户配置不同的值。
fs.nr_open(进程级别参数):限制单个进程上可以打开的最大文件数。可以针对不同用户配置不同的值。
这三个参数之间还有耦合关系,配置值时需要留意三点:
- 如果想加大
soft nofile,那么 hard nofile 参数值也需要一起调整。如果 hard nofile 设置得低,soft nofile 调再高也没有用,实际生效的值会取二者最小值。
- 如果增大了
hard nofile,那么 fs.nr_open 也需要跟着上调(fs.nr_open 一定要大于 hard nofile)。如果不小心把 hard nofile 设置得比 fs.nr_open 还大,后果比较严重——会导致该用户无法登录;如果设置的是 *,则所有用户都无法登录。
- 如果加大了
fs.nr_open,却是用 echo "xxx" > ../fs/nr_open 方式修改的,刚改完可能看不出问题,但机器一重启,之前通过 echo 写入的值就会失效,用户依然无法登录。所以非常不建议用 echo 的方式修改内核参数。
调整服务器能打开的最大文件数示例
假设要让进程可以打开 100 万个文件描述符,这里给出一个修改 conf 文件的建议供参考:
vim /etc/sysctl.conf
fs.file-max=1100000 // 系统级别设置成110万,多留点buffer
fs.nr_open=1100000 // 进程级别也设置成110万,因为要保证比 hard nofile大
使上面的配置生效:sysctl -p
vim /etc/security/limits.conf
// 用户进程级别都设置成100完
soft nofile 1000000
hard nofile 1000000
2. 一台服务器最大能支持多少连接
TCP 连接的本质是 client 端和 server 端在内存中维护的一组 socket 内核对象(对应 TCP 四元组:源 IP、源端口、目标 IP、目标端口),只要双方能够找到对方,就算是一条连接。那么一台服务器最多能建立多少条连接呢?
- 从理论上讲,应该是
2^32 (ip数) * 2^16 (端口数) 条连接(约等于两百多万亿)。
- 但实际上受软硬件限制,一台服务器不可能建立这么多连接(主要受 CPU 和内存限制)。
如果只计算 ESTABLISH 状态的空闲连接(只建立连接,不收发数据也不处理业务逻辑),以一台 4GB 内存的服务器为例:
- 这种情况下连接数量主要取决于 内存的大小,因为 ESTABLISH 状态的空闲连接几乎不消耗 CPU(虽然会有 TCP 保活包,但影响极小可忽略)。
- 一条 ESTABLISH 状态的连接大约消耗 3.3KB 内存,计算下来一台 4GB 内存的服务器可以支持 100w+ 的 TCP 连接。不过这只是纯粹的空连接;真实场景中只要有数据收发和处理(接收/发送都需要申请内存,处理数据需要 CPU),内存和 CPU 压力都会飙升,并发量不可能达到 100w+。
上面讨论的都是理想情况。现实中如果有频繁的数据收发和处理(比如压缩、加密等),一台服务器能撑 1000 并发就算很不错了。所以一台服务器能支撑多少连接必须结合具体场景分析,脱离业务逻辑谈并发没有太大实际意义。
服务器的开销大头往往并不是连接本身,而是每条连接上的数据收发,以及请求业务逻辑处理。
3. 一台客户端机器最多能发起多少条连接
客户端每与服务器建立一个连接,就会消耗 client 端一个端口。机器的端口范围是 0 ~ 65535,那是不是说一台 client 最多只能和一台 server 建立 65535 个连接呢(其中还有保留端口,实际可用约 64000 个)?
由 TCP 四元组特性可知,只要四元组中任一元素不同,就是不同的 TCP 连接。所以需要分情况讨论:
情况一:一台 client 仅有一个 IP,server 端也仅有一个 IP 并只启动一个程序监听一个端口。此时 client 与这台 server 最大可建立连接数就是 65535 个。
因为源 IP 固定,目标 IP 和端口固定,四元组中唯一可变化的就是 源端口,可用范围 0~65535,所以最大 65535 个连接。
情况二:一台 client 有多个 IP(假设有 n 个 IP),server 端仅有一个 IP 并只监听一个端口。此时最大连接数为 n × 65535 个。
目标 IP 和端口固定,有 n 个源 IP,每个 IP 可变化 65535 个源端口。
情况三:一台 client 仅有一个 IP,server 端也仅有一个 IP,但 server 端启动了 m 个程序,每个程序监听不同端口。此时最大连接数为 65535 × m 个。
源 IP 固定,目标 IP 固定,目标端口有 m 个,可变化的仍是源端口。
- 客户端的可用端口范围一般达不到 65535 个,受内核参数
net.ipv4.ip_local_port_range 限制,可通过修改该参数扩大范围。
- 所以,不光 server 端可以接收 100w+ 个 TCP 连接,一台 client 照样能发出 100w+ 个连接。
4. 其他关键点
- 三次握手时的全连接队列长度由
net.core.somaxconn 控制,默认是 128。当两台机器距离很近但建连并发极高时,可能导致半连接队列或全连接队列溢出,server 端丢弃握手包,导致 client 超时重传(至少 1s 以后),拖慢建连速度。可以增大 net.core.somaxconn 来减小丢包影响。
- 有时候通过
ctrl + c 终止进程后立即重启却报错端口被占用,原因是操作系统还没来得及回收端口,等一会儿再重启即可。
- client 程序建连时如果没有调用
bind 指定端口,内核会随机选择端口;一旦调用了 bind 传入指定端口,client 就会固定使用该端口与 server 通信。所以不建议在 client 端轻易调用 bind,这会改变内核端口选择策略。
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open();
// 客户端还可以调用bind方法
sc.bind(new InetSocketAddress("localhost", 9999));
sc.connect(new InetSocketAddress("localhost", 8080));
System.out.println("waiting..........");
}
- 在 Linux 一切皆文件,socket 也不例外。进程打开一个 socket 需要创建好几个内核对象,说白了就是吃内存。所以 Linux 从多个维度限制了可打开文件描述符的数量,以防恶意进程耗尽系统资源。
- 内核通过 hash 表 管理所有已建立连接的 socket,以便基于 TCP 四元组 快速查找到对应的 socket 对象。
- 在 epoll 模型中,使用 红黑树 来管理 epoll 对象下辖的所有 socket,兼顾快速删除、插入和查找的效率。
5. 常见实际问题
“too many open files” 报错是怎么回事,该如何解决
线上你可能遇到过 too many open files 错误。每打开一个文件(包括 socket),都会消耗一定内存,Linux 对打开的文件描述符数量做了限制。如果进程触发内核限制,这个错误就产生了。
解决办法是修改 fs.file-max、soft nofile、fs.nr_open 这三个参数的值,并注意它们之间的耦合关系。
一台服务端机器最大究竟能支持多少条连接
这里只考虑最大数,先不管连接上的数据收发和处理,仅计算 ESTABLISH 状态的空连接。在这种情况下,一台服务器最大可支持的 TCP 连接数量基本上由内存大小决定。
四元组唯一确定一条连接,但服务端可以接收任意客户端的请求,按公式算出的理论值太大,没有实际意义。文件描述符限制本质是内核为防止应用程序无节制打开文件句柄而设的,加大这几个内核参数即可破解。
一个 socket 大约消耗 3KB 左右内存,拿 4GB 内存的服务器来说,可以支持的 TCP 连接数量大约是 100w+。
一台客户端机器最大究竟能支持多少条连接
和服务端不同,客户端每次建立连接都需要消耗一个端口。端口是 2 字节整数,范围 0~65535。那客户端是不是只能建 65535 条连接?有没有办法突破?
从数字上看确实如此,但实际上有两种办法突破 65535 限制:
方式一:为客户端配置多个 IP。
方式二:分别连接不同的服务端。
所以一台 client 发起百万条连接是没有问题的。
做一个长连接推送产品,支持 1 亿用户需要多少台机器
假设你作为架构师,要设计一个类似友盟 push 的长连接推送产品,服务端需要与客户端保持大量空闲长连接,每天只推送两三次。总用户规模预计 1 亿,评估一下需要多少台服务器。
对于此类场景,给客户端发送数据只是偶尔的,绝大部分时间 TCP 连接都是空闲的,CPU 开销可以忽略。
从内存维度估算,假设单机内存 128G,一台服务器可以支撑约 500w 条并发连接。以每条连接约 3KB 内存计算,500w 连接消耗约 15~20GB 内存,剩下 100GB 以上的内存用于接收/发送缓冲区及其他开销绰绰有余。因此,1 亿用户,仅需 20 台左右的服务器就差不多够用了。
(本文由云栈社区编辑整理,更多技术讨论与干货欢迎访问 yunpan.plus)