一、并发问题概述

大家好,我是老张。做后端十几年,抓包也抓了上万次。今天咱们聊聊并发问题——这玩意儿说白了就是系统扛不住同时来的请求了。

我记得刚入行那会儿,带我的师傅跟我说过一句话,到现在我都觉得特别对:「并发问题不是 bug,是系统在极限状态下的真实反应。」你想想看,一个接口平时响应 10ms,一旦并发上来变成 10 秒,这中间发生了什么?

什么是服务并发问题

并发问题,就是当多个请求同时访问共享资源时,系统表现出来的异常行为。我习惯把它分成三类:

  • 资源竞争:多个线程/进程同时读写同一份数据
  • 资源耗尽:连接池、线程池、内存被占满
  • 协调失效:锁、事务、分布式协议出问题

举个例子。你有个数据库连接池,配置了 10 个连接。平时 100 个请求慢慢来,没问题。突然来了 1000 个请求,10 个连接瞬间被占满,后面的请求全在排队。这就是典型的资源耗尽型并发问题。

核心观点:并发问题的本质是「有限资源」与「无限请求」之间的矛盾。抓包定位的关键,就是找到这个矛盾在网络上留下的痕迹。

并发问题的典型表现

我在项目中遇到过各种各样的并发问题。总结下来,无非就这三种表现:

表现 现象 用户感知
延迟 请求处理时间变长,从 10ms 变成 1s 甚至 10s 页面加载慢、操作卡顿
超时 请求在规定时间内没得到响应,客户端主动断开 请求失败、报错、白屏
资源耗尽 CPU 100%、内存 OOM、连接数打满 服务完全不可用

这三种表现往往是递进关系。延迟不解决,就会超时;超时多了,资源就耗尽了。嗯,这里要注意——很多团队只盯着业务代码看,忽略了网络层面的表现,结果排查了好几天才发现是连接池配置问题。

避坑指南:我曾经遇到过一个案例,线上服务偶尔超时,开发团队查了三天代码没找到问题。最后我抓了个包一看——TCP 连接建立阶段就卡住了,三次握手迟迟没完成。原来是后端服务的 backlog 队列满了,新连接被内核直接丢弃。这种问题,看代码是看不出来的。

并发问题在 TCP/IP 协议栈中的体现

为什么我们要从 TCP/IP 层面看并发问题?因为所有的服务通信,最终都会落到协议栈上。你想想看,一个 HTTP 请求从客户端发出,要经过多少层?

  1. 应用层:HTTP 请求/响应
  2. 传输层:TCP 连接管理、流量控制、拥塞控制
  3. 网络层:IP 路由、分片
  4. 链路层:MAC 地址、帧传输

并发问题在每一层都有体现。我挑几个典型的说说:

1. TCP 连接队列溢出

服务端 listen 的时候,内核维护了两个队列:半连接队列(SYN Queue)和全连接队列(Accept Queue)。并发高的时候,这两个队列很容易满。满了怎么办?内核直接丢包。客户端那边看到的就是连接超时。

2. TCP 窗口太小

接收窗口(rwnd)告诉对方「你一次能发多少数据」。如果接收端处理不过来,窗口会越来越小,最终变成 0。这就是窗口关闭。并发高的时候,接收端 buffer 被占满,窗口缩到 0,发送端只能干等着。

3. 拥塞控制触发

网络拥堵时,TCP 会主动降低发送速度。并发请求一多,网络很容易拥堵,拥塞窗口(cwnd)被不断缩小。结果就是——明明带宽够用,但传输速度上不去。

4. 连接复用与竞争

HTTP/1.1 的 keep-alive 让多个请求复用同一个 TCP 连接。但并发高的时候,多个请求在同一个连接上排队,后面的请求被前面的「慢请求」阻塞。这就是著名的 HOL(Head-of-Line)阻塞问题。

我的经验:抓包定位并发问题,我一般先看三个指标:TCP 连接建立时间、首字节时间(TTFB)、以及是否有大量重传。这三个指标能快速告诉你问题出在「连不上」「响应慢」还是「网络丢包」。

下面这张图,是我自己总结的并发问题在 TCP/IP 协议栈中的映射关系。每次排查问题,我都会先对照这张图,看看问题可能出在哪一层。

并发问题在 TCP/IP 协议栈中的映射 应用层(HTTP / RPC / gRPC) 并发表现:请求排队、超时、连接复用冲突(HOL 阻塞) 传输层(TCP / UDP) 并发表现:连接队列溢出、窗口关闭、拥塞控制触发、大量重传 关键指标:SYN 超时、零窗口、cwnd 下降、RTO 指数退避 网络层(IP) 并发表现:IP 分片丢失、路由震荡、TTL 耗尽 链路层(以太网 / MAC) 并发表现:冲突、帧丢失、带宽争用 抓包重点:重点关注传输层(TCP),这是并发问题最集中的体现层

你看这张图,传输层是并发问题的「重灾区」。为什么?因为 TCP 协议本身就有很多机制来应对网络压力——流量控制、拥塞控制、连接管理。这些机制在低并发时是保护,在高并发时反而可能成为瓶颈。

我举个例子你就明白了。假设你的服务突然来了 10000 个并发连接。服务端 listen 的 backlog 默认是 128,瞬间就被打满了。新来的连接请求,内核直接发 RST 拒绝。客户端那边看到的就是「Connection refused」。这就是典型的传输层并发问题。

总结一下:并发问题不是玄学,它在网络上是有迹可循的。延迟对应 TCP 重传和窗口变化,超时对应连接队列溢出和 RTO 超时,资源耗尽对应连接数打满和端口耗尽。我们后面几章会一个一个拆开讲,用 Wireshark 抓包给你看这些现象长什么样。

好了,这一章先到这里。记住一句话:并发问题的根在资源,但表现在网络。学会看网络,你就掌握了定位并发问题的钥匙。