1. 协议栈基础:从物理信号到HTTP请求

做高并发服务,第一关就是协议栈。很多人上来就调线程池、调连接池,结果瓶颈其实在协议栈底层。我见过太多案例——应用层代码写得再漂亮,协议栈一堵,全白搭。

今天咱们先把地基打牢。协议栈这东西,说白了就是一套规则,让数据能从一台机器跑到另一台机器,还能被正确理解。

1.1 OSI七层模型 vs TCP/IP四层模型

先看这张对比图,心里有个全局印象。

OSI七层模型 vs TCP/IP四层模型 OSI 七层模型 7. 应用层 (Application) 6. 表示层 (Presentation) 5. 会话层 (Session) 4. 传输层 (Transport) 3. 网络层 (Network) 2. 数据链路层 (Data Link) 1. 物理层 (Physical) TCP/IP 四层模型 4. 应用层 (HTTP / DNS / FTP / SMTP) 3. 传输层 (TCP / UDP) 2. 网络层 (IP / ICMP / ARP) 1. 网络接口层 (以太网 / WiFi / 物理层) OSI是理论模型,TCP/IP是事实标准 高并发场景下,我们主要关注传输层(TCP/UDP)和应用层(HTTP)

OSI七层是教科书上的理想模型,分得特别细。但实际互联网跑的是TCP/IP四层模型——它把上面三层(应用、表示、会话)合并成一层,下面两层(物理、数据链路)也合并了。

我个人习惯直接拿TCP/IP模型来思考问题。你想想看,做高并发服务,真正需要操心的就三层:应用层、传输层、网络层。网络接口层一般交给内核和网卡驱动去管,我们很少直接碰。

1.2 核心协议的角色定位

三个核心协议,各有各的脾气。我列个表,方便你对比。

协议 层级 核心特点 典型场景 并发瓶颈
TCP 传输层 面向连接、可靠、有序、流量控制、拥塞控制 HTTP、数据库连接、RPC 三次握手开销、队头阻塞、TIME_WAIT
UDP 传输层 无连接、不可靠、低延迟、无拥塞控制 DNS、视频流、游戏、QUIC 无内置拥塞控制,应用层需自行处理
HTTP 应用层 请求-响应模型、无状态、可扩展 Web服务、API、微服务 短连接开销、头部冗余、队头阻塞

TCP:可靠但沉重

TCP是个老实人。它保证你发的每个字节都能按顺序到达。代价呢?三次握手、四次挥手、拥塞窗口、滑动窗口……每一个机制都在消耗时间和资源。

我记得有一次排查线上问题,发现服务端TIME_WAIT连接堆到十几万,新连接就是建不起来。查了半天,原来是客户端频繁创建短连接,服务端被动关闭后TIME_WAIT堆积。后来改成连接池,问题就解决了。

避坑指南:我曾经接手过一个项目,业务方为了省事,每次请求都新建TCP连接。QPS一上2000,服务直接挂掉。后来改成连接池+长连接,同样机器扛到了2万QPS。记住——TCP连接是资源,不是消耗品。

UDP:轻量但需要自己兜底

UDP就简单多了。它只管把数据包扔出去,不管对方收没收到。延迟低,开销小,但丢包、乱序都得应用层自己处理。

为什么QUIC(基于UDP的HTTP/3)越来越火?说白了就是受够了TCP的队头阻塞。我最近在做一个实时推送服务,底层就用了UDP+应用层重传,延迟比TCP方案低了40%。

HTTP:应用层的门面

HTTP是应用层最常用的协议。从HTTP/1.0的短连接,到HTTP/1.1的keep-alive,再到HTTP/2的多路复用,最后到HTTP/3的QUIC——每一次演进都在解决并发场景下的瓶颈。

你想想看,HTTP/1.1虽然支持了长连接,但同一个连接上的请求必须排队(队头阻塞)。HTTP/2引入了多路复用,但TCP层面的队头阻塞依然存在。直到HTTP/3换成UDP+QUIC,才算真正解决了这个问题。

1.3 并发场景下的协议栈瓶颈分析

高并发场景下,协议栈的瓶颈往往不在应用层,而在传输层和网络层。我总结了几类最常见的瓶颈,你看看自己遇到过几个。

1.3.1 连接建立与释放的开销

TCP三次握手需要1.5个RTT(往返时间)。如果客户端和服务端距离远,比如跨机房、跨地域,这个开销就很可观了。

举个例子:北京到上海的网络延迟大约30ms。一次三次握手就是45ms。如果每次请求都新建连接,光握手就占了45ms。QPI一高,连接建立本身就成了瓶颈。

我的建议:高并发服务一定要用连接池。连接池不仅能复用连接,还能控制并发连接数,避免把服务端打爆。我个人习惯把连接池大小设置为CPU核心数的2-4倍,然后根据实际压测结果微调。

1.3.2 队头阻塞(Head-of-Line Blocking)

队头阻塞是TCP协议的一个固有缺陷。TCP保证数据按序到达,但如果一个包丢了,后面的包都得等着。即使后面的包已经到达了接收端,也不能交给应用层处理。

HTTP/2虽然实现了多路复用,但底层还是TCP。一个TCP连接上的所有流共享一个拥塞窗口,一旦丢包,所有流都得等。这就是为什么HTTP/3要换成QUIC——每个流独立拥塞控制,一个流丢包不影响其他流。

1.3.3 缓冲区与内存开销

每个TCP连接都有发送缓冲区和接收缓冲区。默认值通常是16KB-64KB。如果服务端维持10万个连接,光缓冲区就要占掉几GB内存。

我记得有一次帮一个朋友优化网关,发现内存占用高得离谱。一查,TCP读写缓冲区都设成了默认的64KB。10万连接就是6.4GB。后来根据业务包大小,把缓冲区调到了4KB,内存直接降到400MB。

1.3.4 中断与上下文切换

高并发下,网卡中断和上下文切换是隐藏的杀手。每个数据包到达都会触发中断,中断处理程序把数据从网卡拷贝到内核缓冲区,然后通知应用层读取。这个过程涉及多次上下文切换。

现代网卡支持RSS(Receive Side Scaling)和多队列,可以把中断分散到多个CPU核心上。但配置不当的话,所有中断都挤在一个核心上,那个核心的CPU使用率直接飙到100%。

核心思路:协议栈瓶颈的本质是资源竞争。连接数、缓冲区、中断、上下文切换——这些资源在高并发下都会成为瓶颈。解决思路无非两条:

  • 减少开销:连接池、长连接、零拷贝、大页内存
  • 分散压力:多队列网卡、多线程/多进程、IO多路复用(epoll/kqueue)

1.4 一张图总结本章核心

下面这张图把协议栈的层次、核心协议、以及并发瓶颈串起来了。建议你保存下来,后面每章都会用到。

协议栈并发瓶颈全景图 应用层 (HTTP / gRPC / WebSocket) 瓶颈:短连接开销 · 头部冗余 · 序列化/反序列化 · 应用层队头阻塞 传输层 (TCP / UDP) 瓶颈:三次握手 · TIME_WAIT · 队头阻塞 · 缓冲区内存 · 拥塞控制 网络层 (IP / ICMP) 瓶颈:路由延迟 · 分片重组 · NAT · 防火墙规则 网络接口层 (以太网 / 网卡驱动) 瓶颈:中断风暴 · 上下文切换 · 网卡队列 · 带宽上限

好了,第一章就到这里。协议栈基础是后面所有章节的基石。TCP的握手优化、UDP的可靠性改造、HTTP/2的多路复用、epoll的IO模型——这些都会在后续章节逐一展开。

记住一句话:高并发不是靠堆机器堆出来的,是靠把每一层的瓶颈都抠干净抠出来的。