写阻塞是怎么来的
应用调用 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 本身是正常反馈机制,问题在于应用如何应对:如果重试逻辑没有退避、或把未写完的数据直接丢弃,就会表现为数据丢失或连接异常,而不是单纯的慢。