当前位置:首页 > 技术学院 > 技术前线
[导读]谈到Linux的最大并发数,很多开发者会本能想到系统配置里的ulimit -n,觉得改大这个值就能支持更多并发,甚至默认“Linux最大并发可以到几十万上百万”。但实际生产环境中,经常遇到明明把文件句柄数改到了10万,并发跑到几万系统就崩了的情况。到底Linux的最大并发数有没有固定值?它到底受哪些因素限制?我们该怎么合理规划并发数?其实这个问题没有标准答案,需要从系统资源、网络协议、应用场景多个维度拆解,才能搞清楚真正的瓶颈在哪里。

谈到Linux的最大并发数,很多开发者会本能想到系统配置里的ulimit -n,觉得改大这个值就能支持更多并发,甚至默认“Linux最大并发可以到几十万上百万”。但实际生产环境中,经常遇到明明把文件句柄数改到了10万,并发跑到几万系统就崩了的情况。到底Linux的最大并发数有没有固定值?它到底受哪些因素限制?我们该怎么合理规划并发数?其实这个问题没有标准答案,需要从系统资源、网络协议、应用场景多个维度拆解,才能搞清楚真正的瓶颈在哪里。

先理清概念:我们说的“并发数”到底指什么

很多讨论一开始就混淆了概念:不同场景下说的“并发数”根本不是一回事,自然得不出一致的结论。通常我们说Linux的并发数,主要指三类场景:

‌TCP并发连接数‌:最常见的场景,指服务器当前同时维持的TCP连接数量,比如Nginx反向代理、Redis服务、网关服务都关注这个指标,每个TCP连接对应一个socket,每个socket占一个文件句柄;

‌进程/线程并发数‌:指系统同时运行的进程或线程数量,CPU调度是以线程为单位的,线程太多会导致大量上下文切换,降低整体性能;

‌请求并发数‌:指同时处理的用户请求数量,和连接数不同,HTTP长连接下一个连接可以顺序处理多个请求,短连接下一个请求对应一个连接,请求并发数受应用处理能力限制更大。

我们通常说的“Linux最大并发数”,默认指最常见的TCP并发连接数,这也是服务端开发最关心的指标,本文也围绕这个维度展开讨论。

常见的限制因素:从文件句柄到内存,到底哪里是瓶颈

很多人以为ulimit -n就是最大并发数的上限,其实它只是一个用户级的限制,真正的瓶颈在更底层的系统资源上,我们一层一层拆解:

第一层限制:文件描述符,最直接的限制

Linux中一切皆文件,每个TCP socket对应一个打开的文件描述符(fd),所以理论上最大并发连接数不会超过系统允许打开的最大文件描述符数量。

文件描述符的限制分两级:

‌单进程限制‌:ulimit -n默认是1024,这就是为什么新手开发的服务,并发跑到1000多就报错“too many open files”,就是因为单进程打开的文件描述符超过了限制。这个限制可以改,我们通常会把它改成65535甚至更大,满足高并发场景需求。

‌系统全局限制‌:/proc/sys/fs/file-max控制整个系统能打开的最大文件描述符总数,这个值默认是根据系统内存大小自动计算的,比如1G内存的机器默认大概是10万左右,8G内存会到几十万,这个也可以手动修改,只要内存足够,能改到百万级别。

所以文件描述符本身不是硬瓶颈,只要改了配置,就能支持几十万的fd,对应几十万的并发连接。但问题是,每个socket不是只占一个fd编号,它还要占用内核内存和用户空间内存,真正的瓶颈其实在内存。

第二层限制:内存,每个连接都要占用空间

每个TCP连接,不管有没有流量,内核都要分配一块内存来维护连接状态(tcp_sock结构体),还有发送和接收缓冲区,默认每个连接的接收缓冲区大概是87KB左右,发送缓冲区大概是16KB,加起来每个连接内核就要占大概100KB左右的内存。

我们算一笔账:如果要支持10万并发连接,光内核态就要消耗10万 * 100KB = 约10GB内存,如果是100万并发连接,就要100GB内核内存,这还没算应用层用户态分配的缓存、业务数据内存。也就是说,内存大小直接决定了能支撑的最大并发数,没有足够的内存,哪怕文件描述符改得再大,连接创建多了也会把内存耗尽,导致系统OOM杀死进程。

如果应用层还要给每个连接分配额外的缓冲区,比如web服务器要给每个连接分配读写缓冲区,那内存占用会更高。比如每个连接应用层再占100KB,10万并发就要再多10GB,对内存的要求更高。

所以很多说Linux能支持百万并发的场景,都是针对低配的长连接服务,比如网关、推送服务,每个连接几乎没有流量,只需要维持连接状态,内存消耗低;如果是每个连接都要处理大流量,内存开销更大,能支撑的并发数也会更低。

第三层限制:网络协议栈与端口范围

如果我们说的是‌** outgoing 主动连接**‌的并发,还有一个端口范围的限制:Linux中一个主动连接需要占用一个本地端口,端口是16位的,最多也就65535个,所以单个IP主动发起并发连接,最多也就几万,没法更高了。

如果是服务端‌被动接受连接‌,端口范围不限制并发数,因为服务端只占用一个监听端口,所有新连接都复用这个监听端口,只分配新的socket,不需要占用新的本地端口,所以百万并发连接也只需要一个监听端口就行,这时候端口范围不构成限制。

很多人会混淆主动连接和被动连接的端口限制:比如爬虫程序需要用一台机器爬很多网站,每个请求一个新连接,主动连接并发最多也就几万,要支持更高并发就需要给机器绑定多个IP,每个IP可以用65535个端口,n个IP就能支持n*6万并发。

第四层限制:CPU与上下文切换

当并发连接都有流量需要处理的时候,CPU就会成为新的瓶颈。每个连接处理请求都需要CPU时间,如果并发请求数超过CPU能处理的容量,就会导致大量线程等待,上下文切换开销飙升。

我们知道,CPU的核心数是有限的,每个核心同一时间只能跑一个线程,如果我们开了100个线程处理请求,CPU核心只有8个,就会有92个线程在排队,操作系统需要频繁切换线程,上下文切换会占用大量CPU时间,真正用来处理业务的时间反而变少,整体性能会急剧下降。

所以对于CPU密集型的业务,最大有效并发数其实不会超过CPU核心数太多,太多反而会降低性能;对于I/O密集型的业务,比如web服务、数据库代理,线程大部分时间在等I/O,并发数可以比核心数高很多,但也不是越高越好,到一定程度上下文切换开销就会成为瓶颈。

第五层限制:系统底层架构

比如32位Linux系统,地址空间最多只有4G,内核地址空间只有1G,所以能支撑的并发连接远低于64位系统,32位系统最多也就几万并发,而64位系统没有地址空间限制,可以支持更高的并发。现在生产环境都是64位系统,这个限制已经很少遇到了,但在一些嵌入式设备上还是存在。

实际生产中,Linux到底能支持多少并发

我们结合不同场景来看实际的并发上限:

场景一:长连接网关/推送服务,只有连接很少流量

这类场景只需要维持TCP连接,每个连接几乎没有数据传输,内存只需要消耗内核的socket开销,每个连接大概100KB左右。如果是一台32G内存的服务器,去掉系统和应用占用的内存,剩下20G给socket,理论上可以支撑20G / 100KB = 200万并发连接,实际生产中,优化得当的话,单台服务器跑到百万并发是可以做到的,国内很多做推送服务的公司都已经实测过。

但如果是只有8G内存的普通服务器,一般也就支持10-30万左右的并发,再多内存就不够了。

场景二:HTTP短连接服务,大量请求频繁创建销毁

短连接场景下,每个请求处理完就断开连接,并发连接数不会很高,瓶颈一般在请求处理能力和CPU上。比如普通的web服务,单核CPU每秒能处理几百到几千个请求,8核机器每秒能处理几千到几万个请求,并发请求数一般在几千到几万之间,再多就会导致响应延迟升高,CPU占用过高。

场景三:反向代理服务,每个连接都要转发流量

反向代理比如Nginx,每个连接都要分配读写缓冲区,既要维护客户端连接,也要维护回源连接,内存开销是两倍。一般8核16G的Nginx服务器,优化之后能跑到几万到十几万并发,要支持更高并发就需要加机器做负载均衡。

场景四:单机器主动爬取并发,端口限制

前面说过,单个IP主动发起连接,最多也就6万左右并发,要更高就得加IP,一般爬虫单IP不会超过5万并发,再多就会超出端口范围,创建连接报错。

常见误区:关于最大并发数的错误认知

很多开发者对Linux并发数有几个常见的误区,我们一一澄清:

误区一:ulimit -n改到65535,就能支持6万并发

很多人以为改了ulimit -n就万事大吉,其实不是:ulimit -n只是单进程打开文件描述符的限制,它只是一个准入门槛,不代表真的就能支持这么多并发。如果你的内存只有2G,哪怕把ulimit改到10万,实际跑到3万并发就会OOM,所以修改配置只是第一步,真正的瓶颈在内存。

误区二:最大并发数是Linux的固定值

很多人会问“Linux最大并发数到底是多少”,就默认它有一个固定值,其实根本没有:它完全取决于你的机器配置、应用场景、优化程度,1核1G的机器和32核64G的机器,最大并发差几十倍,长连接场景和短连接请求场景也差几十倍,不可能有固定答案。

误区三:并发数越高越好

很多开发者追求“百万并发”,觉得并发越高性能越好,其实不对:并发数只是表示能同时维持多少连接,不代表处理能力强。对于很多业务来说,单台服务器几千到几万并发就足够了,盲目追求更高并发只会浪费资源,甚至因为并发太高导致内存耗尽、性能下降。合适的才是最好的,根据业务压力水平扩展,比单台硬扛高并发更合理。

误区四:C10K问题已经解决,所以C1000K随便上

十几年前业界讨论C10K问题(支持1万并发),现在C10K早就解决了,很多人就觉得C1000K(百万并发)也随便上。其实C10K对现在的机器来说太简单了,百万并发确实能做到,但它需要足够的内存、合理的优化,不是改几个配置就能随便达到的,没有足够硬件资源,说百万并发都是空谈。

生产环境怎么合理规划并发数

我们总结几个实际开发中常用的原则,帮助你合理规划并发数:

第一,先分清楚场景:是被动连接还是主动连接,是长连接还是短连接。被动长连接看内存,主动连接看端口,请求处理看CPU,先找到核心瓶颈再调整配置。

第二,不要卡着极限跑,留足够的缓冲:比如你预计最大并发是10万,就按照15万的规格准备资源,不要卡着10万的极限配置,突发流量上来很容易把系统打崩。

第三,合理调整内核参数:除了改ulimit -n和file-max,还要调整TCP相关的参数,比如调大TCP缓冲区、开启TCP复用、调整FIN超时时间,这些优化能减少每个连接的内存占用,提升能支撑的并发数。

第四,不要盲目追求单台高并发:如果并发量持续增长,优先做负载均衡,多台机器水平扩展,比在单台机器上抠优化成本更低,稳定性更高。单台机器就算能扛百万并发,出问题就是全盘崩溃,不如分散到多台机器风险更低。

结语

Linux没有固定的“最大并发数”,它是一个由文件描述符、内存、CPU、网络协议、场景共同决定的动态值:小到1核1G机器的几千并发,大到64G内存机器的百万并发,都是合理的。理解并发数背后的限制因素,比记一个固定的数字重要得多——当你遇到并发瓶颈的时候,知道该从哪里排查,该调整什么资源,这才是最重要的。

很多人痴迷于追求“百万并发”的技术指标,但实际生产中,绝大多数业务根本不需要这么高的并发,合理规划资源,保障服务稳定,比追求极限参数更有价值。搞清楚并发限制的本质,我们就能根据自己的业务需求,算出合理的并发上限,配置出最适合自己的服务,这才是理解这个问题的真正意义。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
换一批
延伸阅读

当我们在代码里调用read读取文件,调用malloc分配内存,调用socket创建网络连接的时候,最终都会落到系统调用上。但很多开发者只知道系统调用是用户程序请求内核服务的接口,却说不清系统调用到底是怎么实现的:为什么用...

关键字: Linux 系统调用

在工业自动化现场,我们时常听到这样的抱怨:"明明 Linux 上跑个 EtherCAT 主站协议栈很简单,可一到多轴联动、精密组装这类场景,周期一不小心就'飘'了,轨迹抖得让人心慌。" 问题就出在"硬实时"三个字上。要在...

关键字: 工业自动化 Linux 半导体

在云原生技术蓬勃发展的今天,容器凭借轻量、高效、可移植的特性,成为构建现代应用的核心载体。然而,容器并非绝对安全的“隔离堡垒”——当内核存在漏洞时,攻击者可通过容器逃逸突破隔离限制,直接获取宿主机的控制权,进而威胁整个集...

关键字: 内核 Linux

在高并发场景下,Linux系统的并发处理能力直接决定了应用的性能和稳定性。无论是Web服务器、数据库还是消息队列,都需要面对大量并发连接的挑战。那么,Linux系统的最大并发数究竟是多少?它受到哪些因素的限制?又该如何优...

关键字: Linux系统 并发数

在探讨Linux可执行文件如何装载进虚拟内存之前,我们首先需要理解虚拟内存这一核心概念。虚拟内存是计算机系统内存管理的一种关键技术,它为应用程序构建了一个看似连续完整的地址空间,让程序认为自己拥有一块独立且连续的内存区域...

关键字: Linux 虚拟内存

在软件开发过程中,调试是定位和解决问题的关键环节。GDB(GNU Debugger)作为Linux平台下最常用的调试工具,支持对C、C++等多种语言程序的调试,能够帮助开发者监控程序执行、检查变量值、定位崩溃原因。然而,...

关键字: GDB Linux

随着嵌入式Linux系统的复杂度不断增加,设备驱动开发面临着新的挑战。传统的内核编码方式已难以满足现代SoC平台硬件配置的灵活性和可维护性需求,而设备树(Device Tree)技术的引入,彻底改变了Linux内核与硬件...

关键字: Linux 驱动开发

在Zynq/SoC异构计算平台开发中,PS(Processing System)端运行Linux系统与PL(Programmable Logic)端自定义IP核之间的高速数据交互是核心挑战之一。DMA(直接内存访问)技术...

关键字: Zynq/SoC开发 Linux

在Linux环境下的C/C++开发中,程序调试是排查问题、优化性能的核心环节。GDB(GNU Debugger)作为一款功能强大的命令行调试工具,凭借其精细的控制能力和丰富的功能,成为开发者不可或缺的利器。然而,GDB的...

关键字: GDB Linux

在Linux程序开发与运行的链条中,链接是衔接编译与执行的关键环节。它将编译器生成的目标代码、系统库函数等资源整合为可执行程序,直接决定了程序的资源占用、维护成本与运行效率。静态链接曾是早期系统的主流选择,但随着软件规模...

关键字: Linux 静态链接
关闭