做网络相关的工作,无论你是后端开发、运维、测试还是刚入门的学生,TCP/IP 这套协议族躲不开也绕不过。平时排查个线上超时、抓个包看下请求为什么慢、调个 Nginx 超时参数,底层全是这些协议在工作。这篇是网络基础知识系列的第三篇,重点把 TCP/IP 协议族里的核心协议逐个拆开讲:从最底层的以太网帧、ARP,到 IP、ICMP,再到传输层的 TCP、UDP,最后到应用层的 HTTP、DNS。目标就一个——把每层协议到底在干什么、报文字段意味着什么、遇到问题怎么排查,讲清楚,讲透,让你看完再去抓包看报文时不再两眼一抹黑。
这篇内容适合三类人:第一类是刚入行一两年的开发或运维,需要系统补齐网络基础;第二类是准备面试的,网络协议是高频考点,这篇可以直接当复习提纲用;第三类是已经在写代码但遇到线上问题只能求人的,搞懂这些协议,你排查问题的思路会清晰很多。
1. 内容整体设计与思路拆解:为什么必须从分层模型开始理解
1.1 抓包看到的是完整的报文,但理解必须分层
很多初学者在第一次打开 Wireshark 抓包时都很懵,屏幕上全是密密麻麻的十六进制和一行行看不懂的协议缩写。其实你看到的每一行,完整的叫法是"一个数据包",而每个数据包,本质上是好几层协议的数据叠在一起。这就是为什么先讲分层:你不是在看一个东西,你是在看五六个不同层级的协议头套在一起。
这里说的分层,最常用的是 TCP/IP 四层模型:链路层、网络层、传输层、应用层。面试题里常问的 OSI 七层模型,在实际工程中已经被 TCP/IP 四层模型取代了,因为四层模型更贴近真实实现。比如你在浏览器里输入一个网址,请求从应用层一路向下封装,到链路层把完整的数据帧发出去,对端收到后再从链路层一路向上拆解,各层只处理自己那一层关心的信息。打比方说,就像寄快递:应用层是你写的信,传输层是信封上写的收件人姓名(端口),网络层是快递单上的地址(IP),链路层是快递车在路上跑的实际路径(MAC 地址寻址)。
理解了这个封装与解封装的过程,你以后再看到"TCP 重传""TTL 超时""HTTP 404"这些词,就能立刻反应出来——这说的是哪一层的问题,影响范围有多大。这是整个网络排查能力的地基。
1.2 学习路径规划:从数据流动的视角串联所有协议
把协议逐个单独背,是最低效的学习方式。我的经验是反过来,顺着一个数据包从发送到接收的完整旅程来看每个协议。这个过程就串起了所有核心协议:你在浏览器输入网址,先通过 DNS 把域名解析成 IP(应用层),然后发起 TCP 连接(传输层),把你的请求数据切分成一个个 TCP 段,每个段带上源端口和目标端口;这些 TCP 段被交到 IP 层,封装成 IP 数据报,带上源 IP 和目标 IP;IP 数据报在链路层被打包成以太网帧,通过 ARP 协议查到下一跳设备的 MAC 地址,最终从网卡发出去。
顺着这条路走一遍,你就会发现:每一层协议其实只解决一个问题。链路层解决"在同一个网络里怎么把数据传给隔壁设备",网络层解决"跨网络怎么找到对方",传输层解决"数据到了之后怎么交给正确的应用程序,并且确保完整有序",应用层解决"应用程序之间用什么格式交换数据"。带着这个框架再看下面的协议细节,就不容易迷路。
2. 核心细节解析:逐层拆解每个协议的工作方式
2.1 链路层:MAC 寻址、以太网帧与 ARP
2.1.1 以太网帧结构
链路层最典型的协议是以太网。一个以太网帧的头部主要包含:目标 MAC 地址(6 字节)、源 MAC 地址(6 字节)、类型字段(2 字节),中间是数据部分,最后是 4 字节的帧校验序列 FCS。类型字段告诉你上层是什么协议,比如 0x0800 表示 IP,0x0806 表示 ARP。
这里有个容易被忽略的点:MAC 地址是在数据帧经过每一跳时不断变化的。源 MAC 和目标 MAC 描述的是"当前这一段链路的两端设备",而不是整个通信旅程的起点和终点。IP 地址才是从端到端不变的。也就是说,数据从你的电脑经过路由器再到服务器,每过一个路由器,MAC 地址就会被改写一次,而 IP 地址保持不变。很多人在理解"代理"和"网关"的区别时绕不过来,其实就是没搞懂 MAC 地址逐跳寻址、IP 地址端到端寻址这个区别。
2.1.2 ARP 协议的工作流程
光有 IP 地址还不够,数据帧在链路上传输时依靠的是 MAC 地址。那怎么通过 IP 找到 MAC 呢?这就是 ARP(Address Resolution Protocol)要做的事。
ARP 的工作流程很朴实:当主机 A 要给同网段的主机 B 发数据时,先查自己的 ARP 缓存表,如果没有 B 的 MAC 地址,就广播一个 ARP 请求:"谁的 IP 是 192.168.1.10?请把你的 MAC 地址告诉我。" 同一网段所有设备都能收到这个广播,但只有 IP 匹配的那台设备会回复单播的 ARP 应答。A 收到应答后,把 IP 和 MAC 的映射关系写入 ARP 缓存,再开始正式发送数据。
实际排查中 ARP 相关的问题主要是两类。一类是 ARP 缓存表老化或冲突导致通信异常,你 ping 不通某台机器但配置看起来完全正确,先arp -a看看缓存条目有没有异常;另一类是出现大量 ARP 广播,整个局域网内设备 CPU 升高、网络明显变慢,这往往是有人中了 ARP 欺骗或者网络里有环路。真实的排查思路不是死记命令,而是理解"每一次跨设备通信都离不开 ARP 寻址"这个前提,自然就知道该查什么。
2.2 网络层:IP 寻址、路由与 ICMP
2.2.1 IPv4 地址与报头关键字段
IP 协议是整个网络层的绝对核心。我们最常见的 IPv4 地址是 32 位,平时写成点分十进制的四段。除了地址本身,IP 报文头里几个字段你在排查时必须能看懂:
- 版本(4 位):IPv4 还是 IPv6。
- 首部长度(4 位):IP 头有多长,因为可选字段存在,头部长度不固定。
- 总长度(16 位):整个 IP 数据报的长度。这个字段关系到分片和重组。
- TTL(8 位):数据报在网络中可以经过的最大跳数。每经过一个路由器减 1,减到 0 就丢弃并返回 ICMP 超时消息。这就是 traceroute 命令的工作原理。
- 协议号(8 位):上层用的传输层协议。1 表示 ICMP,6 表示 TCP,17 表示 UDP。
- 源 IP 地址和目标 IP 地址(各 32 位):端到端通信的标识。
还有标志字段和分片偏移字段。当数据报大小超过链路 MTU 时,IP 层会进行分片。实际工作中我踩过一个大坑:某个业务传输的 UDP 报文数据偏大,但没有在应用层做 MTU 感知,导致大量 IP 分片,其中一片在中间链路丢失后,整个数据报被丢弃,对端反而认为"数据完全没收到"。后来把应用层发送缓冲调整为不超过 1400 字节,问题就消失了。
2.2.2 路由的核心思路与 ICMP 的排查价值
路由器做的事情,本质上就是"查表转发"。每台路由器维护一张路由表,表里记录了去往不同目标网段应该走哪个下一跳。数据报到达路由器后,路由器用目标 IP 逐位匹配路由表,找到一个最长的匹配项,然后从对应的接口转发出去。这个"最长前缀匹配"是路由协议里的核心原则。
ICMP(Internet Control Message Protocol)虽说是网络层的辅助协议,但在日常排查中却极其常用。ping 命令发送的是 ICMP Echo Request,对端回 ICMP Echo Reply;traceroute 利用的是 ICMP 超时消息来探测路径。有一次线上反馈"某台数据库服务器从应用服务器 ping 不通",但应用服务器到数据库的 3306 端口却能正常连接。这就说明网络层链路是通的,但 ICMP 报文可能被防火墙策略拦截了。所以排查时千万别拿"ping 得通"当作网络全通的唯一标准,要结合端口连通性一起判断。
2.3 传输层:TCP 与 UDP
2.3.1 TCP 报文头的关键字段
TCP 是传输层的主角,也是面试和排查的重中之重。TCP 报文头里必须掌握这些字段:
- 源端口、目标端口(各 16 位):标识通信的应用程序。
- 序列号(32 位):本端发送数据的字节流编号。用来保证数据有序、去重。
- 确认号(32 位):期望对端下一次发送的序列号。表示"在这个编号之前的数据我都收到了"。
- 数据偏移(4 位):TCP 头有多长。
- 标志位(各 1 位):URG、ACK、PSH、RST、SYN、FIN。抓包时重点看 SYN、ACK、FIN、RST。
- 窗口大小(16 位):本端接收窗口,用于流量控制,表示"我还能收多少数据"。
- 校验和(16 位):覆盖 TCP 头部和数据部分。
其中四层对应关系很多人容易乱:MAC 地址标识一台设备,IP 地址标识一台主机,端口号标识主机上的一个进程,TCP 连接的元组是(源 IP、源端口、目标 IP、目标端口)。只要这四个元素唯一,就能唯一确定一条连接。
2.3.2 三次握手与四次挥手:为什么是这个次数
TCP 连接建立的三次握手,过程是:客户端发送 SYN 报文(seq=x),服务端收到后回复 SYN+ACK(seq=y, ack=x+1),客户端再回复 ACK(ack=y+1)。三次之后,双方都确认了"我能发,你能收;你能发,我能收"。
为什么要三次而不是两次?核心问题在于要防止已失效的连接请求突然又传到服务端。设想一个场景:客户端第一次发起的 SYN 在网络中滞留了很久,客户端超时后重新发起连接,成功建立了会话并关闭。这时那个滞留的 SYN 才到达服务端,如果只有两次握手,服务端会以为客户端又要建连,直接分配资源傻等。有了第三次握手,客户端发现这个连接根本不是自己想要的,不会回复 ACK,服务端自然超时释放资源。
四次挥手的过程:主动关闭方发送 FIN,对端回复 ACK,然后对端再发送自己的 FIN,主动方再回 ACK。为什么是四次而不是三次?因为 TCP 是双工通信,两个方向的数据流是独立的。收到对方的 FIN 只表示对方不再发数据了,但自己这边的数据可能还没发完,所以要先回 ACK 告诉对方"我收到你的关闭请求了",然后等自己的数据发完,再发送自己的 FIN。主动关闭方在发出最后的 ACK 后会进入 TIME_WAIT 状态,持续 2MSL(最大报文段生存时间),确保最后一个 ACK 如果丢失,对端重发的 FIN 还能被响应。
实操中 Time_Wait 状态多到影响新连接,这是 Altas Pouch 或者高并发短连接服务常见的场景。TIME_WAIT 本身不是问题,但量太大且本机端口不够用时,新连接就会报"Address already in use"。常见的优化方向是调整net.ipv4.tcp_tw_reuse开启复用、调大端口范围、或者应用层改成连接池。
2.3.3 传输层的可靠性机制与拥塞控制
TCP 的可靠性靠的是三个机制支撑:确认与重传、滑动窗口、拥塞控制。
确认与重传:发送方发送数据后启动超时定时器,如果在时间内没有收到 ACK,就重传该数据。超时时间不是固定的,TCP 会根据 RTT(往返时间)动态计算 RTO。抓包中如果看到大量重复 ACK 和快速重传,说明网络里发生了丢包。
滑动窗口:解决的是"发太快对方来不及收"的问题。接收方在 ACK 里带着自己的窗口大小,告诉发送方还能收多少字节,发送方据此调整发送量。这就是流量控制。
拥塞控制解决的是"数据发太快把中间网络挤爆"的问题。TCP 用拥塞窗口 cwnd 来限制发送速度,慢启动阶段 cwnd 从 1 个 MSS 开始,每收到一个 ACK 翻倍增长;到慢启动阈值后转为拥塞避免,线性增长。出现丢包后,算法会大幅降低 cwnd,重新开始探测。这个机制解释了一个常见现象:新建连接的前几个 RTT 传输速率上不去,等 cwnd 涨上来速度才稳定,这也是为什么长连接比短连接效率高。
对比之下,UDP 就简单粗暴得多。UDP 头只有 8 个字节:源端口、目标端口、长度、校验和。没有序列号、没有确认、没有重传、没有窗口、没有连接状态。它只做一件事:把数据报尽量发出去。所以 UDP 天然低延迟、开销小,适合音视频通话、游戏实时交互、DNS 查询这类能容忍偶发丢包但无法容忍延迟的场景。但是在可靠性要求高的业务里用它,就必须自己在应用层实现确认与重传。
| 对比项 | TCP | UDP |
|---|---|---|
| 连接状态 | 面向连接,三次握手 | 无连接 |
| 可靠性 | 确认、重传、排序 | 尽力交付,不保证到达 |
| 传输模式 | 字节流 | 数据报 |
| 头部开销 | 20 字节起 | 8 字节 |
| 速度 | 慢,受拥塞控制影响 | 快,无拥塞控制 |
| 典型应用 | HTTP、FTP、SSH、数据库 | DNS、VoIP、视频直播、游戏 |
2.4 应用层:HTTP 协议与 DNS 解析
2.4.1 HTTP 请求与响应结构
应用层协议里出场率最高的就是 HTTP。一个 HTTP 请求报文由请求行、请求头、空行、请求体组成。请求行包含请求方法、URL、协议版本。响应报文由状态行、响应头、空行、响应体组成。
HTTP 版本演进值得梳理一遍:HTTP/1.0 每个请求建立新的 TCP 连接,效率低;HTTP/1.1 增加了 Connection: keep-alive 支持持久连接,还引入了 Host 字段、管线化和块传输编码;HTTP/2 增加了多路复用,在一个 TCP 连接上并发多个请求,通过二进制分帧层解决了队头阻塞;HTTP/3 使用了基于 UDP 的 QUIC 协议,进一步减少了连接建立时间和队头阻塞。
在真实项目里有一个经典问题:为什么 HTTP/1.1 时代浏览器对同一域名默认开 6 个并发连接?因为 HTTP/1.1 有队头阻塞问题,单个 TCP 连接上请求必须按顺序返回,浏览器只能开多个连接并行下载。HTTP/2 多路复用后,这个 6 连接限制依然保留,但目的变成了多个连接分带宽。这些细节在调优前端性能时非常关键。
2.4.2 状态码与常见 Header
状态码按首位数字分五类。1xx 信息性,2xx 成功,3xx 重定向,4xx 客户端错误,5xx 服务端错误。实际排查中最常打交道的是这些:
- 301 永久重定向:域名换了,旧地址需要指到新地址。
- 302 临时重定向:请求的路径暂时移到别的地址。
- 304 未修改:命中浏览器本地缓存,响应体为空。
- 400 错误请求:请求报文格式有问题,如 JSON 解析失败。
- 401 未认证:没带有效的凭证。
- 403 禁止访问:认证了但没权限。
- 404 未找到:路由不存在或资源被删除。
- 429 请求过多:触发限流。
- 500 内部错误:代码抛异常。
- 502 错误网关:代理服务器后面的服务没响应。
- 503 服务不可用:服务过载或正在维护。
- 504 网关超时:代理等待上游响应超时。
504 和 502 的区别是排查思路的分水岭。502 是上游根本没有有效响应,可能是进程挂掉、端口没监听;504 是上游响应太慢,超过了代理等待时间。前者查服务是否存活,后者查业务处理耗时和网络链路。
2.4.3 DNS 解析的过程与常见坑
DNS 是应用层的基础设施,它的工作流程可以简化成这样:你在浏览器输入某个域名,先查操作系统缓存,没有就查本地 hosts 文件,再没有就向配置的 DNS 服务器发起递归查询。DNS 服务器如果也没有缓存,会从根域名服务器开始,依次查询顶级域服务器、权威服务器,最终拿到域名对应的 IP 并返回。
DNS 排查中最大的坑是缓存不一致。有一次我们上线了一个新域名的解析记录,改完权威服务器后等了几小时,线上部分机器还是解析到旧 IP。原因是有些机器本地起了 DNS 缓存服务,或者上层递归服务器还没刷新 TTL。后来养成了习惯:改 DNS 解析前先看原来的 TTL,提前把 TTL 调小,等同步完成后再改回来。这个技巧在正式环境做域名切换时非常实用。
3. 实操环节:用抓包和命令行工具验证协议行为
3.1 Wireshark 抓包看三次握手与 HTTP 请求
纸上谈兵再多,不如实际抓一次包。以访问一个 HTTP 网站为例,打开 Wireshark 选择对应网卡,设置过滤条件tcp.port == 80,刷新网页,就能看到完整的交互过程。
第一条是客户端发往服务器的 SYN 报文,Flags 列显示 SYN 置位,seq 是一个随机初始序列号。第二条是服务器回复的 SYN+ACK,同时带有自己的随机序列号和对客户端序列号+1 的确认号。第三条是客户端的 ACK。整个三次握手到这里就结束了。接着就是一条 PSH+ACK 的请求报文,然后是服务器回的一长串 ACK 数据包。不用管具体内容,光是观察这个顺序,你就能把"连接建立、请求发送、数据返回"这几个阶段对应起来。
想更清楚地看 TCP 交互,可以右键某条 TCP 报文选择 Follow -> TCP Stream,Wireshark 会把这条连接的全部 TCP 段按顺序重组出来。这时候你应该能感受到什么叫字节流——TCP 就是把这些段按序列号顺序拼起来,交给应用层的就是一段连续的数据,而不是一个又一个孤立的报文。
3.2 tcpdump 命令行抓包
服务器上没有图形界面,排查问题靠的就是 tcpdump。抓包前先想清楚目标:抓哪个端口、哪个主机、哪个协议。不要一开始就tcpdump -i eth0裸抓,数据量会大到刷屏。
常用命令示例:
# 抓取和指定 IP 的 eth0 网卡流量,保存到文件 tcpdump -i eth0 host 10.10.10.10 -w /tmp/trace.pcap # 抓取 80 端口的 HTTP 流量,并打印 ASCII 可读内容 tcpdump -i eth0 tcp port 80 -A # 抓取指定协议的流量,比如只看 ICMP tcpdump -i eth0 icmp一个比较实用的排查场景:应用反馈连数据库超时,怀疑中间网络丢包。抓包看 TCP 重传即可,命令是:
tcpdump -i eth0 tcp and host 数据库IP and port 3306 -w db.pcap抓到包后用 Wireshark 打开,菜单 Statistics -> TCP Stream Graph -> Time-Sequence Graph,看序列号是否出现回退和重复发送。序列号反复回退就说明重传频繁,网络链路质量堪忧。
3.3 命令行网络工具组合使用
排查网络问题靠的不是单个命令,而是一套组合拳。我常用的顺序是这样:
第一步ping -c 10 目标IP,看丢包率和 RTT 波动。注意 ping 只能说明网络层通不通,不能说明端口通不通。
第二步traceroute或mtr看路径。mtr 比 traceroute 更好用,它会持续监测每一跳的丢包率,能定位是出口路由器丢包还是中间链路丢包。
第三步telnet 目标IP 端口或nc -zv 目标IP 端口,测端口连通性。能通说明端口在监听且防火墙放行。
第四步curl -v http://...看应用层的请求响应细节。-v参数会打印出请求头和响应头,还能看到 TLS 握手过程、重定向链路。
第五步ss -tnp或netstat -tnp看本机连接状态。如果大量连接卡在 SYN_SENT,多半是出方向被封或对端不收;大量 ESTABLISHED 且数量逼近上限,要调文件描述符或内核参数。
这套组合从下往上逐层验证,哪里断了立刻就能定位是哪一层的问题。很多新手上来就怀疑代码,其实跑一遍这套流程,大概率能在网络层和传输层找到答案。
3.4 抓包实验:观察一次完整的 HTTP 请求过程
推荐一个适合自己练手的实验,不用搭建任何环境。打开终端执行下面这条命令,它会启动一个临时的 HTTP 服务:
python3 -m http.server 8080然后在浏览器访问http://localhost:8080,同时用 tcpdump 或 Wireshark 抓 lo 网卡(回环接口)的 8080 端口流量。你会发现回环接口上同样有完整的 TCP 三次握手和 HTTP 请求响应,因为即使是本机到本机,TCP/IP 协议栈也会走完整流程,只不过没有真实的物理链路。
观察点有三个:第一,HTTP/1.1 默认的 Connection: keep-alive 是怎么体现在抓包里的——多个 HTTP 请求复用在同一条 TCP 连接上,中间没有断开;第二,浏览器请求的路径下,响应里会有 Server、Content-Length、Content-Type 这些头;第三,请求结束后的 RST 报文——某些客户端行为会导致连接被重置,用来模拟异常断开再合适不过。
4. 常见问题与排查技巧实录
4.1 TCP 连接超时排查:从现象到根因的思路
现象:应用日志里上报"connect timeout",客户端连不上某台服务器的某个端口。
排查从下往上:先ping目标 IP,如果不通,问题在网络层或者目标主机防火墙拦截 ICMP。如果 ping 通但端口连不上,下一步telnet 目标IP 端口,端口不通有可能是服务没监听、本机防火墙 drop、公有云安全组没放行、中间网络设备策略拦截。如果 telnet 能通但应用层超时,那就是业务代码的问题了,比如数据库连接池满、线程池阻塞、SQL 慢查询。
一次真实的排查印象深刻。某服务偶发连接超时,丢包率几乎为零,telnet 也基本能通,但一压测就大面积超时。后来抓包发现,大量连接卡在 TCP 三次握手的第二步,客户端发出 SYN 后迟迟收不到 SYN+ACK。定位到服务端 accept 队列满了,新的 SYN 到达后因为全连接队列和半连接队列都满了而被丢弃。调大内核参数net.core.somaxconn、net.ipv4.tcp_max_syn_backlog,同时应用层把 listen 的 backlog 调大,问题解决。
4.2 大量 TIME_WAIT 导致新连接失败
现象:高并发短连接服务,ss -s里看到 TIME_WAIT 数量上万,日志出现 "Cannot assign requested address"。
TIME_WAIT 出现在主动关闭方。查看本机连接状态:
ss -tn state time-wait | wc -l如果是主动关闭方且量过大,调整思路是优先从架构层面解决:用 keep-alive 连接池复用连接,让短连接变成持久连接,这是最彻底的办法。如果临时缓解,可以调大本地端口范围:
sysctl -w net.ipv4.ip_local_port_range="1024 65000"以及允许复用 TIME_WAIT 状态的端口:
sysctl -w net.ipv4.tcp_tw_reuse=1注意tcp_tw_reuse只对发起连接的一方有效,而且必须确保时间戳选项开启,否则复用会导致序列号冲突。更严谨的做法是深入业务层优化连接模型,而不是一味依赖内核参数。
4.3 HTTP 504 与 502 的区分与处理
线上网关报 504 是常见事故。首先看是哪个组件返回的,如果是 Nginx 返回的 504 Gateway Timeout,说明 Nginx 等 upstream 响应超时,此时看的是proxy_read_timeout配置和上游实际耗时。如果上游是 PHP-FPM,看request_terminate_timeout和 PHP 执行时间;如果上游是 Java 服务,看 GC 耗时长不长、线程池有没有耗尽。
502 则说明 Nginx 连 upstream 的连接本身都没建立成功。可能是上游端口没监听、进程刚挂、或者触发负载均衡健康检查踢掉了故障节点。排查命令很简单:在 Nginx 机器上直接 curl 上游地址,看有没有响应。没有响应就登录上游机器,看进程状态和端口监听。整个排查链路不超过五分钟就能出结论。
4.4 常见问题速查表
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
| ping 通则端口不通 | 服务未监听、防火墙拦截、安全组未放行 | telnet 目标 IP 端口 |
| 应用偶发超时 | TCP 重传、半连接队列满 | tcpdump 抓包看重传与 SYN |
| 响应慢但 ping RTT 正常 | 应用本身耗时、DNS 解析慢、连接排队 | curl -w 计时统计各阶段耗时 |
| 间歇性不可用 | DNS 缓存、负载均衡后端实例异常 | 更换 DNS 测试、查看 LB 后端健康状态 |
| 连接数打满 | 文件描述符限制、连接泄漏 | ss -tnp 看连接归属、lsof 查句柄 |
4.5 独家避坑经验
抓包文件打开慢,可能是抓到了巨型流量文件,尽量在抓包时用过滤条件缩小范围,或者用-c限制包数量。还有,抓 HTTPS 流量如果不做 TLS 解密设置,只能看到加密后的数据,第一步先别急着解密,先看 TCP 层有没有重传和乱序,确认传输层稳定了再去看应用层内容。
改网络配置和生产环境调优,永远不要批量改所有机器再验证。先拿一台机器改,观察指标,确认没问题,再逐步推广。我见过太多因为一个 sysctl 参数改错导致整个集群出现大规模连接异常的案例,回滚时间和损失都远超"逐台灰度"的那点时间成本。
5. 从协议栈角度看业务优化
5.1 为什么长连接比短连接快
理解 TCP 的慢启动机制后,你就明白为什么服务间调用一定要用连接池。每一次新建连接都要经过三次握手,然后进入慢启动,cwnd 从很小开始涨。短连接意味着每一次请求都要重新经历一个从低速到高速的过程,长期来看传输效率非常低。连接池把连接保活并复用,再加上 HTTP/1.1 的 keep-alive,既省掉了握手和挥手开销,又绕开了慢启动带来的初始低速率期。实测中同样一份数据,从每次新建连接到连接池复用,响应时间能降到原来的三分之一左右,在高 RTT 跨机房环境下收益更明显。
5.2 超大报文与分片问题
应用层一次发送 4MB 的 TCP 数据,TCP 会按 MSS 分片发送,对端重组成连续字节流,这些都是协议栈自动完成的。但 UDP 不一样,如果应用层一次发送超过 MTU 的 UDP 数据报,IP 层会分片,一旦某个分片丢了,整个数据报就没了,对端不会自动重组重传。这在视频推流、自定义 UDP 协议中是非常典型的坑。解决思路是应用层自行控制报文大小不超过路径 MTU,通常保守起见控制在 1400 字节以内,或者在应用层实现可靠传输协议来应对丢包。
5.3 全链路追踪时如何对应各层信息
联调排查问题时,经常需要把客户端报错、网关日志、后端日志串起来。这里面每一层都有自己独立的标识:网络层看 IP,传输层看端口和 TCP 连接四元组,应用层看重试、超时和业务状态码,HTTP 层看请求 ID 或 trace ID。实践中的经验是,工单里最好同时记录客户端的出口 IP、目标域名、目标端口、报错时间、HTTP 状态码和耗时分布。没有这些基础信息,排查效率会非常低下。设计良好的日志系统会把各层信息聚合到一个 trace ID 下,这样一次请求从头到尾发生了什么、在哪一层耗时大、在哪一步超时,一眼就能定位。
最后再分享一点个人的实操心得
网络协议这东西,看十遍书不如亲手抓一次包。我建议你把 Wireshark 打开,就在本地回环接口抓一次访问任意网站的包,自己找出来三次握手、HTTP 请求行、响应头、四次挥手分别对应哪些报文。这个实验五分钟就能做完,但对协议的理解比读三章书都扎实。
我还有一个习惯,遇到线上网络问题,每次排查完都会在笔记里记录一个问题-现象-根因-解决方式四段式的内容。积累一段时间之后,你会发现大多数网络故障翻来覆去就是那么几类:连接数耗尽、队列满、丢包重传、防火墙误拦、DNS 缓存过期。你提前见过的坑越多,真正遇到事故时定位就越快。这些知识最终会变成你的本能反应——看到现象,立刻知道查哪一层,这才是学习网络协议最大的收益。