首页/系统/Linux 内核与性能调优速查/发送缓冲写阻塞定位
网络栈调优98 条

发送缓冲写阻塞定位

应用 write/send 偶发卡住不返回, 常见原因是发送缓冲已满且对端读取慢; 用 ss -m 观察 socket 排队量与缓冲上限。

写阻塞是怎么来的

应用调用 write/send 偶发长时间不返回,最常见的原因是发送缓冲已经写满而对端读取偏慢:阻塞式 socket 会挂起等待缓冲腾出空间,非阻塞式则返回 EAGAIN。判断要看 socket 队列的实际占用——ss -m 输出的 skmem 中,r/t 是收发队列中排队的字节数,rb/tb 是对应的缓冲上限;ss -ti 还能给出拥塞窗口、RTT 与未发送字节数,帮助区分是网络受限还是对端消费慢。

基本信息

观察命令ss -m(skmem 明细)、ss -ti(拥塞窗口与时延)
阻塞式 socket缓冲满则挂起等待,直到有空间
非阻塞式 socket返回 EAGAIN,由应用重试或排队
发送缓冲上限net.core.wmem_default/wmem_max、SO_SNDBUF
相关参数net.ipv4.tcp_notsent_lowat(可写通知阈值)
进程侧线索strace -T 观察单次 write 耗时,线程栈确认阻塞点

典型使用场景

  • 应用出现「偶发卡在写操作」的延迟尖峰,需要定位阻塞位置
  • 消息推送、日志上报类服务因写阻塞拖高尾延迟
  • 区分是网络慢、对端消费慢还是应用发送过快
  • 评估发送缓冲配置是否与业务写入速度匹配
  • 云主机或容器中确认 socket 内存上限是否被系统参数压低

定位步骤

定位步骤
ss -m dst :8080  # 看 skmem 中的收发排队量(r/t)与缓冲上限(rb/tb)
ss -tin dst :8080  # 看 cwnd、rtt、notsent 与 retrans,判断受限原因
ss -s  # 全局 socket 汇总,先确认是否整体存在排队
sudo strace -T -p 12345 -e trace=write,sendto 2>&1 | tail -20  # 观察单次写的实际耗时
sysctl net.core.wmem_max net.ipv4.tcp_wmem  # 核对发送缓冲上限与自动调优范围
sudo cat /proc/12345/stack  # 内核侧确认线程是否阻塞在发送路径(需权限)

关键字段读法

字段含义判读要点
skmem r / t接收与发送队列中排队的字节数发送队列接近 tb 时就容易阻塞
skmem rb / tb接收与发送缓冲上限确认是否被系统上限限制
cwnd拥塞窗口偏小说明网络侧受限
notsent未发送字节数反映内核里积压的数据量
retrans重传段数异常重传会加剧发送阻塞
定位顺序建议从网络侧开始:先确认对端接收速度与往返时延(ss -ti 的 cwnd 与 rtt),再考虑应用侧发送节奏与缓冲设置。直接调大发送缓冲往往会掩盖对端处理慢的问题,并抬高内存占用。
非阻塞 socket 出现 EAGAIN 本身是正常反馈机制,问题在于应用如何应对:如果重试逻辑没有退避、或把未写完的数据直接丢弃,就会表现为数据丢失或连接异常,而不是单纯的慢。

易错点

  • 直接调大 SO_SNDBUF:掩盖对端处理慢的根因,同时抬高内存占用
  • 把偶发 EAGAIN 当成连接故障:应先检查应用的重试与背压逻辑是否合理
  • 只看应用日志不看 socket 状态:缺少队列占用与拥塞窗口证据,方向容易跑偏
  • 忽略对端消费能力:根因常在对方,改本机参数无法解决
  • 忽略系统级 socket 内存上限:应用想要的缓冲值可能被 wmem_max 截断,改动看似无效

数据来源与参考

  • man 7 socket
  • man ss
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌