HiHuo
首页
博客
手册
工具
首页
博客
手册
工具
  • 系统底层修炼

    • 操作系统核心知识学习指南
    • CPU调度与上下文切换
    • CFS调度器原理与源码
    • 内存管理与虚拟内存
    • PageCache与内存回收
    • 文件系统与IO优化
    • 零拷贝与Direct I/O
    • 网络子系统架构
    • TCP协议深度解析
    • TCP问题排查实战
    • 网络性能优化
    • epoll与IO多路复用
    • 进程与线程管理
    • Go Runtime调度器GMP模型
    • 系统性能分析方法论
    • DPDK与用户态网络栈
    • eBPF与内核可观测性
    • 综合实战案例

TCP问题排查实战

章节概述

在生产环境中,TCP相关的问题是最常见的网络故障之一。本章将通过实际案例,教你如何系统地排查和解决各类TCP问题,包括连接失败、TIME_WAIT堆积、延迟抖动、重传等常见场景。

学习目标:

  • 掌握TCP问题的系统排查方法
  • 学会使用工具快速定位问题
  • 能够解决常见的TCP连接和性能问题
  • 建立TCP故障排查的知识体系

核心概念

1. TCP问题分类

┌─────────────────────────────────┐
│     连接建立问题                 │
│  - 无法连接                      │
│  - 连接超时                      │
│  - 连接拒绝                      │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│     连接状态问题                 │
│  - TIME_WAIT堆积                │
│  - CLOSE_WAIT泄漏               │
│  - SYN_RECV攻击                 │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│     性能问题                     │
│  - 延迟高                        │
│  - 吞吐量低                      │
│  - 重传频繁                      │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│     数据传输问题                 │
│  - 丢包                          │
│  - 乱序                          │
│  - 窗口阻塞                      │
└─────────────────────────────────┘

2. 排查工具链

快速诊断工具:
- ping: 测试连通性
- telnet/nc: 测试端口
- ss/netstat: 查看连接状态

深入分析工具:
- tcpdump: 抓包分析
- wireshark: 可视化分析
- ss -i: 查看TCP详细信息
- netstat -s: 查看统计信息

内核追踪工具:
- strace: 系统调用追踪
- perf: 性能分析
- bpftrace: eBPF追踪

案例分析

案例1:连接拒绝(Connection Refused)

症状:

$ telnet server_ip 8080
telnet: Unable to connect to remote host: Connection refused

排查流程:

# 步骤1:确认服务是否在监听
ss -ltn | grep 8080
# 或
netstat -ltn | grep 8080

# 预期输出:
# LISTEN  0  128  *:8080  *:*

# 如果没有输出 → 服务未启动或监听错误端口/地址
# 步骤2:检查防火墙
# Ubuntu/Debian
sudo ufw status

# CentOS/RHEL
sudo firewall-cmd --list-all

# iptables
sudo iptables -L -n | grep 8080
# 步骤3:检查SELinux(CentOS/RHEL)
getenforce
# 如果是Enforcing,临时关闭测试
sudo setenforce 0
# 步骤4:检查服务日志
# 查看应用日志
tail -f /var/log/app.log

# 查看系统日志
journalctl -u service_name -f

常见原因和解决方案:

原因解决方案
服务未启动启动服务
监听地址错误修改为0.0.0.0或具体IP
防火墙阻止添加防火墙规则
SELinux阻止配置SELinux策略
端口被占用更换端口或停止占用进程

案例2:TIME_WAIT堆积

症状:

$ ss -s
TCP: 15000 (estab 1000, timewait 12000, closed 12000)

影响:

  • 端口耗尽(客户端)
  • 连接表满
  • QPS下降

排查流程:

# 步骤1:确认TIME_WAIT数量
ss -tan | grep TIME-WAIT | wc -l

# 步骤2:查看端口范围
sysctl net.ipv4.ip_local_port_range
# 输出:net.ipv4.ip_local_port_range = 32768    60999

# 步骤3:计算可用端口
# 可用端口 = 60999 - 32768 = 28231
# 如果TIME_WAIT接近此值,会端口耗尽
# 步骤4:查看连接来源
ss -tan | grep TIME-WAIT | awk '{print $4}' | cut -d: -f1 | sort | uniq -c | sort -rn | head
# 找出产生最多TIME_WAIT的源IP

解决方案:

# 方案1:启用TIME_WAIT重用
sudo sysctl -w net.ipv4.tcp_tw_reuse=1

# 方案2:减少TIME_WAIT等待时间
sudo sysctl -w net.ipv4.tcp_fin_timeout=30

# 方案3:扩大端口范围
sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535"

# 方案4:使用连接池(应用层)
# - HTTP Keep-Alive
# - 数据库连接池
# - gRPC连接复用

永久生效:

sudo tee -a /etc/sysctl.conf << EOF
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.ip_local_port_range = 1024 65535
EOF

sudo sysctl -p

案例3:CLOSE_WAIT泄漏

症状:

$ ss -tan | grep CLOSE-WAIT | wc -l
5000

# 连接数持续增长,不会减少

原因分析:

CLOSE_WAIT表示:
- 对端已发送FIN
- 本端收到并回复ACK
- 但本端未调用close()

这通常是应用程序bug!

排查流程:

# 步骤1:定位进程
ss -tanp | grep CLOSE-WAIT | awk '{print $NF}' | sort | uniq -c
# 输出:users:(("app_name",pid=12345,...))

# 步骤2:查看进程打开的fd
lsof -p 12345 | grep -i close
ls -l /proc/12345/fd | wc -l

# 步骤3:追踪系统调用
sudo strace -p 12345 -e trace=close,read,write -T
# 观察是否有close()调用

代码检查:

// 错误示例(Go)
func handleConnection(conn net.Conn) {
    // 忘记close
    data, _ := ioutil.ReadAll(conn)
    process(data)
    // 缺少: defer conn.Close()
}

// 正确示例
func handleConnection(conn net.Conn) {
    defer conn.Close()  // 确保关闭
    data, _ := ioutil.ReadAll(conn)
    process(data)
}

应急措施:

# 找出并重启问题进程
kill -HUP <pid>  # 优雅重启
# 或
kill -9 <pid>    # 强制重启(最后手段)

案例4:高延迟和抖动

症状:

# 延迟突然升高
# p50: 10ms
# p99: 500ms (异常)
# p999: 2000ms

排查流程:

# 步骤1:网络层延迟
ping server_ip
# 观察RTT是否稳定

mtr -rwzbc100 server_ip
# 查看路由上的丢包和延迟

# 步骤2:TCP层统计
ss -ti dst server_ip
# 输出:
# cubic wscale:7,7 rto:240 rtt:31.5/2.4 ato:40 
# cwnd:10 bytes_acked:12345 segs_out:100 
# send 22.8Mbps lastsnd:1000 lastrcv:1000

关键指标:

rtt: 往返时延(越小越好)
rto: 重传超时(过大说明网络不稳定)
cwnd: 拥塞窗口(太小限制吞吐)
retrans: 重传次数(应该为0或很小)
# 步骤3:检查重传
netstat -s | grep -i retrans

# 步骤4:抓包分析
sudo tcpdump -i eth0 -nn host server_ip -w delay.pcap
# 用wireshark分析:
# 1. Statistics -> TCP Stream Graph -> Time Sequence
# 2. 查找DUP ACK、Retransmission

常见原因和解决方案:

原因表现解决方案
网络拥塞RTT高,重传多启用BBR算法
缓冲区小窗口小调大tcp_rmem/wmem
应用阻塞Recv-Q积压优化应用处理速度
CPU高负载处理慢增加worker或优化代码
跨机房基线RTT高考虑CDN或就近部署

优化示例:

# 启用BBR
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr

# 调大缓冲区
sudo sysctl -w net.ipv4.tcp_rmem="4096 87380 6291456"
sudo sysctl -w net.ipv4.tcp_wmem="4096 16384 4194304"

# 启用TCP fast open
sudo sysctl -w net.ipv4.tcp_fastopen=3

案例5:高重传率

症状:

$ netstat -s | grep retrans
    1234 segments retransmited
    
# 重传率 > 1% 需要关注

排查流程:

# 步骤1:实时监控重传
sudo /usr/share/bcc/tools/tcpretrans

# 输出:
# TIME     PID    LADDR:LPORT  RADDR:RPORT  STATE
# 10:20:15 12345  10.0.0.1:80  10.0.0.2:54321  ESTABLISHED

# 步骤2:查看连接详情
ss -ti | grep retrans
# 输出:
# retrans:1/5 表示当前重传1个包,总共重传5个
# 步骤3:抓包分析重传原因
sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-syn != 0' -w syn.pcap

# wireshark分析:
# 过滤器: tcp.analysis.retransmission
# 查看:
# 1. 是超时重传还是快速重传
# 2. RTT变化趋势
# 3. 窗口大小变化

常见重传原因:

  1. 网络丢包
# 检查网卡丢包
ethtool -S eth0 | grep -i drop

# 检查队列溢出
netstat -s | grep -i overflow
  1. 缓冲区不足
# 检查缓冲区溢出
netstat -s | grep -i "buffer full"
  1. 拥塞窗口太小
ss -ti | grep cwnd
# cwnd持续很小 → 切换拥塞控制算法

解决方案:

# 1. 调整网卡队列
sudo ethtool -G eth0 rx 4096 tx 4096

# 2. 增大socket缓冲区
sudo sysctl -w net.core.rmem_max=16777216
sudo sysctl -w net.core.wmem_max=16777216

# 3. 切换拥塞控制算法
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr

# 4. 调整队列长度
sudo sysctl -w net.core.netdev_max_backlog=16384

️ 排查工具箱

1. 快速诊断脚本

#!/bin/bash
# tcp_quick_check.sh - TCP快速诊断

echo "===== TCP连接状态统计 ====="
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn

echo ""
echo "===== TOP 10 TIME_WAIT 连接 ====="
ss -tan | grep TIME-WAIT | awk '{print $4" "$5}' | sort | uniq -c | sort -rn | head -10

echo ""
echo "===== TOP 10 CLOSE_WAIT 连接 ====="
ss -tan | grep CLOSE-WAIT | awk '{print $4" "$5}' | sort | uniq -c | sort -rn | head -10

echo ""
echo "===== TCP统计信息 ====="
netstat -s | grep -E "segments|retrans|failed|reset"

echo ""
echo "===== 当前TCP参数 ====="
sysctl -a | grep -E "tcp_tw_reuse|tcp_fin_timeout|tcp_max_syn_backlog|ip_local_port_range"

2. 连接追踪脚本

#!/bin/bash
# connection_tracker.sh - 追踪指定端口的连接

PORT=$1
if [ -z "$PORT" ]; then
    echo "用法: $0 <port>"
    exit 1
fi

echo "追踪端口 $PORT 的连接..."

while true; do
    TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
    TOTAL=$(ss -tan | grep ":$PORT" | wc -l)
    ESTAB=$(ss -tan | grep ":$PORT" | grep ESTAB | wc -l)
    TIMEWAIT=$(ss -tan | grep ":$PORT" | grep TIME-WAIT | wc -l)
    CLOSEWAIT=$(ss -tan | grep ":$PORT" | grep CLOSE-WAIT | wc -l)
    
    printf "%s | Total: %4d | ESTAB: %4d | TIME-WAIT: %4d | CLOSE-WAIT: %4d\n" \
           "$TIMESTAMP" "$TOTAL" "$ESTAB" "$TIMEWAIT" "$CLOSEWAIT"
    
    sleep 1
done

3. 性能测试脚本

#!/bin/bash
# tcp_perf_test.sh - TCP性能测试

SERVER=$1
PORT=$2

if [ -z "$SERVER" ] || [ -z "$PORT" ]; then
    echo "用法: $0 <server> <port>"
    exit 1
fi

echo "测试服务器: $SERVER:$PORT"

# 1. 连通性测试
echo "1. 连通性测试..."
nc -zv $SERVER $PORT 2>&1 | grep succeeded && echo " 端口可达" || echo " 端口不可达"

# 2. RTT测试
echo ""
echo "2. RTT测试..."
ping -c 10 $SERVER | tail -1

# 3. 带宽测试(需要iperf3)
echo ""
echo "3. 带宽测试..."
if command -v iperf3 &> /dev/null; then
    iperf3 -c $SERVER -p $PORT -t 10 2>/dev/null | grep sender
else
    echo "未安装iperf3,跳过"
fi

# 4. TCP握手时间
echo ""
echo "4. TCP握手时间..."
curl -o /dev/null -s -w "Connect: %{time_connect}s\nTotal: %{time_total}s\n" http://$SERVER:$PORT/

实战演练

演练1:模拟TIME_WAIT堆积

# 创建大量短连接
for i in {1..1000}; do
    (echo "test" | nc localhost 8080 &)
done

# 观察TIME_WAIT
watch -n 1 'ss -tan | grep TIME-WAIT | wc -l'

演练2:模拟CLOSE_WAIT泄漏

# server_leak.py - 模拟CLOSE_WAIT泄漏
import socket
import time

sock = socket.socket()
sock.bind(('0.0.0.0', 9999))
sock.listen(100)

while True:
    conn, addr = sock.accept()
    # 故意不关闭连接
    print(f"Accept from {addr}")
    time.sleep(0.1)
# 客户端:创建连接后立即关闭
for i in {1..100}; do
    echo "test" | nc localhost 9999
    sleep 0.1
done

# 观察CLOSE_WAIT
ss -tan | grep CLOSE-WAIT

演练3:抓包分析重传

# 启动抓包
sudo tcpdump -i lo -nn port 8080 -w retrans.pcap &

# 使用tc模拟丢包
sudo tc qdisc add dev lo root netem loss 10%

# 发送数据
echo "test data" | nc localhost 8080

# 停止模拟
sudo tc qdisc del dev lo root

# 分析抓包
wireshark retrans.pcap
# 过滤器: tcp.analysis.retransmission

常见问题

Q1: 如何判断TIME_WAIT是否过多?

A: 判断标准:

  • 绝对数量:超过10000个需要关注
  • 相对比例:超过可用端口的50%
  • 增长趋势:持续增长不回落
  • 业务影响:出现端口耗尽错误

Q2: CLOSE_WAIT和TIME_WAIT有什么区别?

A:

  • TIME_WAIT: 主动关闭方的正常状态,会自动消失
  • CLOSE_WAIT: 被动关闭方,需要应用调用close()才会消失
  • TIME_WAIT是正常的,CLOSE_WAIT堆积是bug

Q3: 如何快速定位是哪个服务产生的连接问题?

A: 使用命令:

# 查看连接最多的进程
ss -tanp | awk '/ESTAB/{print $NF}' | cut -d'"' -f2 | sort | uniq -c | sort -rn

# 查看TIME_WAIT最多的目标IP
ss -tan | grep TIME-WAIT | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn

Q4: 生产环境如何安全地调整TCP参数?

A: 安全步骤:

  1. 在测试环境先验证
  2. 使用sysctl临时调整
  3. 观察监控指标
  4. 确认无问题后写入sysctl.conf
  5. 保留回退方案

复习题

选择题

  1. TIME_WAIT状态出现在哪一方?

    • A. 主动关闭方
    • B. 被动关闭方
    • C. 双方都有
    • D. 随机
  2. CLOSE_WAIT堆积通常是什么原因?

    • A. 网络问题
    • B. 应用未调用close()
    • C. 内核bug
    • D. 配置错误
  3. 以下哪个工具不能查看TCP连接状态?

    • A. ss
    • B. netstat
    • C. ifconfig
    • D. lsof
  4. tcp_tw_reuse参数的作用是?

    • A. 删除TIME_WAIT
    • B. 重用TIME_WAIT端口
    • C. 增加连接数
    • D. 提高速度
  5. 查看TCP重传次数使用什么命令?

    • A. ping
    • B. netstat -s
    • C. ifconfig
    • D. route

简答题

  1. 解释TIME_WAIT堆积的原因和解决方法。

  2. 如何排查CLOSE_WAIT泄漏问题?给出完整的步骤。

  3. 描述如何使用tcpdump和wireshark分析TCP重传问题。

  4. 在什么情况下应该调整tcp_rmem和tcp_wmem参数?

  5. 如何判断TCP性能问题是网络层还是应用层导致的?

实战题

  1. 故障排查题: 线上服务突然无法建立新连接,ss显示大量TIME_WAIT。请给出完整的排查和解决方案。

  2. 性能优化题: 一个高并发HTTP服务,客户端经常报连接超时。通过分析发现服务端有大量CLOSE_WAIT。请分析原因并给出修复方案。

  3. 综合分析题: 使用tcpdump抓包发现大量TCP重传,如何进一步分析确定是网络问题还是配置问题?给出详细步骤。

扩展阅读

推荐资源

  • TCP/IP详解 卷1:协议
  • Wireshark网络分析的艺术
  • Linux TCP源码

深入方向

  • TCP性能调优高级技巧
  • TCP拥塞控制算法对比
  • 内核TCP参数详解
  • eBPF在TCP调试中的应用

下一章预告: 我们将深入探讨网络性能优化,包括BBR算法、内核参数调优、以及各种网络优化技术的对比和选择。

Prev
TCP协议深度解析
Next
网络性能优化