性能缓慢故障排除:高效使用 'netstat' 和 'ss'

掌握关键的 Linux 网络工具 `netstat` 和 `ss`,实现高效的性能故障排除。本指南对比了传统的 `netstat` 与现代、更快的 `ss` 工具,并提供了实用的命令示例。学习如何按连接状态过滤结果、识别监听服务,以及使用 Netlink 套接字统计信息快速诊断网络瓶颈。

性能缓慢故障排除:高效使用 'netstat' 和 'ss'

当 Linux 服务感觉缓慢时,套接字表是快速区分“应用过载”和“网络路径混乱”的最佳途径之一。一个无法接受新连接的 Web 服务器、一个卡在打开数据库会话的工作进程、以及一个拥有大量半开 TCP 握手的主机,对于等待的另一端来说,都表现为模糊的缓慢。

netstat 和 ss 帮助您回答一个更具体的问题:这台机器上当前存在哪些网络套接字,它们处于什么状态,以及哪个进程拥有它们?netstat 在旧系统和老旧的操作手册中仍然有用。ss 是我们在现代 Linux 上首选使用的工具,因为它在繁忙的主机上速度更快,并且具有更好的内置过滤功能。

为什么要监控网络套接字?

网络延迟和缓慢通常与连接问题有关,而不是 CPU 或内存耗尽。监控套接字有助于管理员回答以下关键问题:

  • 哪些端口正在主动监听连接?
  • 是否有太多连接卡在 SYN_RECV 或 TIME_WAIT 状态?
  • 哪个进程(PID)正在使用特定端口?
  • 是否存在意外的出站连接?

通过检查套接字统计信息,您可以快速排除网络配置问题,或识别与连接处理相关的资源争用。

传统工具:netstat

几十年来,netstat 一直是显示网络连接、路由表、接口统计信息和伪装连接的标准工具。虽然在许多现代系统上已被弃用,转而支持 ss,但它仍然广泛可用,并且通常为长期管理员所熟悉。

常见的 netstat 示例

netstat 最常用的标志提供了全面的概览:

标志 描述
-a 显示所有套接字(监听和非监听)
-n 显示数字地址,而不是尝试解析主机名和服务名(加快输出速度)
-t 显示 TCP 连接
-u 显示 UDP 连接
-l 仅显示监听套接字
-p 显示与套接字关联的 PID/程序名称(需要 root 权限)

示例:以数字形式查看所有活动的 TCP 连接

sudo netstat -ant

示例:查找正在监听端口 80(HTTP)的内容

sudo netstat -tulpen | grep ':80'

理解连接状态(netstat)

netstat 的输出通常包含一个 State 列。需要关注的关键状态包括:

  • LISTEN:等待传入连接。
  • ESTABLISHED:一个活动的、已打开的连接。
  • TIME_WAIT:一个套接字在关闭后等待一小段时间,以确保延迟的数据包得到处理。
  • SYN_RECV:等待三次握手的最终确认(如果过多,可能表示 SYN 洪水攻击)。

关于 netstat 的警告:netstat 通常依赖于解析 /proc/net/* 文件,这在具有大量活动连接(数千个)的系统上可能会很慢。这是开发 ss 的主要原因。

现代替代品:ss(套接字统计信息)

ss 工具比 netstat 快得多,因为它使用 Netlink 套接字直接从内核空间检索套接字信息,绕过了较慢的文件系统查找。

常见的 ss 示例

ss 的标志结构与 netstat 非常相似,便于轻松过渡:

标志 描述
-a 显示所有套接字
-n 显示数字地址
-t 显示 TCP 套接字
-u 显示 UDP 套接字
-l 显示监听套接字
-p 显示进程信息(PID/程序)

示例:以数字形式查看所有活动的 TCP 连接(等同于 netstat -ant)

ss -ant

示例:查找正在监听端口 443(HTTPS)的内容

sudo ss -tulpen | grep ':443'

使用 ss 进行高级过滤

ss 最大的优势之一是能够对连接状态进行直接过滤,这比将 netstat 的输出通过管道传递给 grep 要高效得多。

按连接状态过滤

您可以在 ss 命令中直接使用 state 选项。这对于诊断连接堆积非常有用。

查找当前处于 TIME-WAIT 状态的所有套接字:

ss -tan state time-wait

查找所有处于 SYN-SENT 状态(客户端等待服务器响应)的套接字:

ss -tan state syn-sent

按端口或地址过滤

按目标或源地址/端口进行过滤非常简单:

显示目标端口为 22(SSH)的已建立连接:

ss -tn state established '( dport = :22 or sport = :22 )'

显示与特定本地 IP 地址相关的连接:

ss -ant '( daddr = 192.168.1.100 or saddr = 192.168.1.100 )'

性能分析:netstat 与 ss 对比

在进行故障排除时,工具的选择通常归结为速度和细节。

特性 netstat ss
速度 较慢(读取文件) 快得多(使用 Netlink 套接字)
语法 成熟,文档齐全 类似标志,更新的特定选项
过滤 需要通过管道传递给 grep 原生支持状态和地址过滤
信息深度 基础信息良好 更详细的套接字缓冲区大小(TCP 信息)
可用性 几乎通用 现代 Linux 发行版的标准配置

诊断连接建立缓慢

如果客户端报告连接缓慢,请检查是否有套接字卡在等待握手。使用 ss 是确定此问题的最快方法:

  1. 检查高 SYN-RECV 计数: 这表明服务器正在接收连接请求,但未完成握手,通常是由于资源耗尽或高流量负载。
    ss -s | grep syn-rec
    
  2. 检查高 SYN-SENT 计数: 如果服务器本身正在发起许多连接(例如,作为客户端连接到数据库或其他 API),这表明它正在等待响应。
    ss -s | grep syn-sent
    

如果您在任何类别中看到持续的高数字,请将其视为线索而非结论。SYN-SENT 可能意味着远程主机宕机、路由错误、防火墙静默丢弃流量,或远程服务过载。SYN-RECV 可能意味着服务器负载过高、数据包丢失,或客户端正在打开连接但未完成。

实用的分类流程

当有人说“应用很慢”时,我通常从一个简短、可重复的步骤开始:

sudo ss -tulpen
ss -s
sudo ss -tan state established '( sport = :443 or dport = :443 )' | head
sudo ss -tan state syn-recv
sudo ss -tan state time-wait | head

第一个命令确认预期的服务确实在监听,并显示拥有它的进程。摘要显示主机是否拥有数量惊人的 TCP 套接字。过滤后的已建立连接命令证明是否有真实的客户端流量连接到该端口。syn-recv 和 time-wait 检查显示连接建立或连接更替是否值得关注。

例如,想象一个 Nginx 反向代理,用户抱怨新请求会挂起几秒钟。sudo ss -tulpen | grep ':443' 确认 Nginx 拥有 HTTPS 监听器。ss -s 显示 TCP 总数很大,而 sudo ss -tan state syn-recv '( sport = :443 )' 不断从相同的源范围返回行。这并不能自动证明是攻击,但它告诉您要检查负载均衡器健康检查、上游数据包丢失、SYN 积压压力、防火墙日志,以及可能的速率限制。

现在想象同一个代理有很少的 SYN_RECV 套接字,但有许多到上游数据库(端口 5432)的已建立连接。这将您的注意力从公共 HTTPS 转移到数据库路径:

sudo ss -tanp '( dport = :5432 or sport = :5432 )'

如果拥有进程是您的应用程序,并且计数不断攀升,那么下一个有用的问题是应用程序是否在泄漏连接、等待慢查询,或者未能将连接返回到连接池。ss 不能回答这个应用层面的问题,但它能带您找到正确的方向。

理性看待 TIME_WAIT

TIME_WAIT 是一种正常的 TCP 状态,本身并非错误。处理大量短生命周期连接的服务器自然会显示 TIME_WAIT 套接字。它们的存在是为了防止旧连接的延迟数据包与新连接混淆。

有用的问题是 TIME_WAIT 是否与工作负载匹配。一个为每个小请求打开新 HTTP 连接的批处理作业可能会产生一波 TIME_WAIT。一个应该使用 keep-alive 但没有使用的服务也可能如此。在调整内核设置之前,请检查应用程序是否可以重用连接、启用 HTTP keep-alive,或使用适当的客户端连接池。

谨慎对待那些建议盲目更改 TCP sysctl 以“修复” TIME_WAIT 的旧建议。某些设置依赖于内核版本,有些已随时间推移被移除或不推荐使用,有些会在 NAT 或负载均衡器后面造成微妙的故障。首先要理解为什么连接是短生命周期的。

检查本地与远程压力

一个可以节省时间的细节是,本地主机主要是接受连接还是发起连接。前端代理通常有许多本地端口为 80 或 443 的连接。与数据库和 API 通信的应用服务器可能有许多远程端口为 5432、3306、6379 或 443 的连接。

对于本地监听器和入站流量:

sudo ss -tan '( sport = :443 )'

对于到依赖项的出站流量:

sudo ss -tan '( dport = :6379 )'

这种区别会改变接下来的对话。如果入站 HTTPS 堆积,您可能需要检查负载均衡器、TLS 终止、工作进程限制或客户端行为。如果出站 Redis 连接堆积,本地应用程序可能创建了太多客户端连接、正在等待 Redis,或者过于激进地重试。

当您需要快速计数而不必读取数百行时,将 ss 与简单的 shell 工具结合使用:

sudo ss -tan state established '( dport = :443 )' | wc -l
sudo ss -tan state established '( dport = :5432 )' | wc -l

计数包括标题行,因此不是一个完美的指标。但对于分类来说,它仍然有用。如果在事件期间数字每分钟翻倍,那么您将获得比单个快照更强的信号。

容器和网络命名空间

在容器化主机上,请注意命令的运行位置。在主机上运行 ss 会显示主机网络命名空间和已发布的端口,但可能不会显示进程在其容器内部看到的相同视图。如果服务在容器中运行,请比较两种视图:

sudo ss -tulpen
docker exec <container> ss -tulpen

对于 Kubernetes,使用节点视图查看主机级监听器,使用 kubectl exec 查看 Pod 的网络命名空间。一个端口可能在容器内部是打开的,但主机、服务、Ingress 或网络策略仍然阻止流量到达它。ss 是一个本地真相工具,而不是端到端的连接性测试。

网络故障排除的最佳实践

  1. 始终使用 -n:在进行性能故障排除或编写脚本时,使用数字标志(-n)以避免 DNS 解析延迟,这可能会使诊断变得缓慢。
  2. 优先使用 ss:将 ss 作为您的默认工具。仅在 ss 不可用的旧系统上保留使用 netstat。
  3. 以 root 身份运行以获取 PID:要查看哪个程序正在使用端口,在使用两个工具的 -p 标志时,通常需要 sudo 或 root 权限。
  4. 检查接口统计信息:不要忘记接口计数器。使用 ip -s link show <interface_name> 检查丢包或错误,这可能表明是物理层问题而不是套接字问题。
  5. 比较快照:一次 ss 输出是一张照片。相隔一分钟拍摄的两张输出会告诉您情况是在增长、缩小还是稳定。
  6. 写下确切的过滤器:在事件期间,像 ss -tan '( dport = :5432 )' 这样保存的命令比半记忆的 grep 管道更容易重复和比较。

值得养成的习惯很简单:从监听器开始,然后检查连接状态,识别拥有进程,然后决定下一步是在应用程序、网络路径、防火墙还是内核中。