一、高并发基础:从一次线上事故说起
我记得那是几年前的一个双十一晚上。监控大屏上,某服务的连接数曲线突然从 2 万飙到了 12 万。我盯着 Wireshark 的抓包界面,满屏的 SYN 包、RST 包像瀑布一样往下刷。服务器 CPU 才 30%,但新连接就是建不起来。
那是我第一次真正意识到:高并发不是简单的「请求多」,而是连接模型、操作系统、网络协议栈三者之间的博弈。今天咱们就从最基础的概念开始,把这些底层的逻辑理清楚。
1.1 什么是高并发?
高并发,说白了就是单位时间内系统能同时处理的请求量。但这里有个坑——很多人把「并发」和「吞吐」混为一谈。
核心指标:
- QPS(每秒查询数):1 秒内能处理多少请求
- 并发连接数:同一时刻系统保持的连接总量
- 响应时间:从发请求到收到响应的时间差
举个例子。你开了一家奶茶店。QPS 是「每分钟能做几杯」,并发连接数是「店里同时站了多少人」。如果店里挤了 100 个人,但店员只能 1 分钟做 1 杯——这就是高并发低吞吐,典型的「看起来热闹,实际效率低」。
我在项目中遇到过最典型的场景:某网关 QPS 只有 5000,但并发连接数到了 8 万。为什么?因为每个请求处理时间太长,连接一直不释放。Wireshark 里一看,全是 TIME_WAIT 状态的连接。
1.2 TCP 连接模型:三次握手与四次挥手
要分析高并发,TCP 连接模型是绕不开的基础。你想想看,每次 HTTP 请求背后,都藏着一套完整的 TCP 生命周期。
三次握手(建立连接)
客户端和服务端建立连接时,需要交换三个报文:
客户端 服务端
| |
|-------- SYN (seq=x) -------->| 第一次:客户端说「我要连你」
| |
|<---- SYN+ACK (seq=y,ack=x+1)-| 第二次:服务端说「收到,我也准备好了」
| |
|-------- ACK (seq=x+1,ack=y+1)->| 第三次:客户端说「好,开始传数据」
| |
嗯,这里要注意:第三次握手是可以携带数据的。很多高性能服务器会利用这个特性做「TCP 快速打开」,减少一次 RTT 的延迟。
四次挥手(关闭连接)
客户端 服务端
| |
|-------- FIN (seq=u) -------->| 第一次:客户端说「我发完了」
| |
|<---- ACK (ack=u+1) ---------| 第二次:服务端说「知道了,但我还有数据」
| |
|<---- FIN (seq=v) -----------| 第三次:服务端说「我也发完了」
| |
|-------- ACK (ack=v+1) ------>| 第四次:客户端说「好,关闭」
| |
我曾经在排查一个连接泄漏问题时,发现服务端有大量 CLOSE_WAIT 状态的连接。原因很简单——服务端代码里忘了调用 close()。Wireshark 里看,服务端只回了 ACK,没发 FIN。这种问题在高并发下特别隐蔽,因为连接数一多,你根本看不出是哪个 socket 出了问题。
1.3 并发与并行的区别
这两个词经常被混用,但本质完全不同。我习惯用一个比喻来解释:
| 概念 | 比喻 | 本质 |
|---|---|---|
| 并发 | 一个人同时做三件事(来回切换) | 逻辑上的「同时」 |
| 并行 | 三个人各做一件事(真正同时) | 物理上的「同时」 |
单核 CPU 只能并行执行一个线程,但可以通过时间片轮转实现「并发」。多核 CPU 才能做到真正的「并行」。
实战经验: 我在调优一个 Nginx 网关时,发现即使把 worker_processes 设成 8(8 核),QPS 也只提升了 30%。Wireshark 抓包发现,瓶颈不在 CPU,而在网卡中断亲和性——所有中断都打在一个核上。这就是典型的「并发模型设计不合理」。
1.4 高并发场景下的网络特征
高并发下,网络行为会和低负载时完全不同。我总结了几个最明显的特征:
1. 连接数暴涨,端口耗尽
每个 TCP 连接都需要一个本地端口。默认情况下,Linux 的临时端口范围是 32768-60999,总共也就 2.8 万个。如果客户端发起的连接超过这个数,就会报 Cannot assign requested address。
避坑指南: 我曾经在生产环境遇到过这个问题。当时 Wireshark 里看到大量 SYN 包发出去后没有 SYN+ACK 回来,客户端一直在重试。排查了半天,发现是客户端端口用完了。解决方案:调大 net.ipv4.ip_local_port_range,或者开启 tcp_tw_reuse 复用 TIME_WAIT 状态的端口。
2. 小包风暴,CPU 飙升
高并发下,网络包的平均大小会变小。因为很多连接只发几个字节的请求就关闭了。这些小包的处理开销(中断、上下文切换)远大于大包。Wireshark 里看,如果 平均包长小于 200 字节,基本可以断定存在小包风暴。
3. 丢包与重传的恶性循环
当网络或服务器处理不过来时,开始丢包。TCP 检测到丢包后会启动重传,重传又增加了网络负载,导致更多丢包。Wireshark 的「Expert Info」里,如果看到大量 Retransmission 和 Fast Retransmission,说明系统已经进入拥塞状态。
4. 连接建立延迟(SYN 队列溢出)
服务端维护两个队列:SYN 队列(半连接)和 Accept 队列(全连接)。高并发下,如果 Accept 队列满了,新来的 SYN 包会被直接丢弃。客户端收不到 SYN+ACK,就会不断重试。Wireshark 里表现为:客户端发了 SYN,服务端没回包,几秒后客户端重发 SYN。
1.5 高并发下的关键网络参数
最后,我整理了一份高并发场景下最常用的内核参数清单。这些参数我在每次排查问题时都会先检查一遍:
| 参数 | 默认值 | 高并发建议值 | 说明 |
|---|---|---|---|
net.ipv4.tcp_tw_reuse |
0 | 1 | 复用 TIME_WAIT 连接 |
net.ipv4.tcp_tw_recycle |
0 | 0(已废弃) | NAT 环境下有坑,别开 |
net.ipv4.tcp_fin_timeout |
60 | 15-30 | 减少 FIN_WAIT2 等待时间 |
net.core.somaxconn |
128 | 1024-65535 | Accept 队列最大长度 |
net.ipv4.tcp_max_syn_backlog |
256 | 1024-65535 | SYN 队列最大长度 |
net.ipv4.ip_local_port_range |
32768 60999 | 1024 65535 | 扩大临时端口范围 |
我的习惯: 每次接手一个新系统,我都会先用 Wireshark 抓 5 分钟的流量,然后看三个指标:SYN 重传率、TIME_WAIT 占比、平均包长。这三个数据基本能告诉你系统的「健康状态」。如果 SYN 重传率超过 1%,说明服务端已经扛不住了。