目录

程序因为 tcp window full hang 住的一次排查过程

文章简介:程序因为 tcp window full hang 住的一次排查过程

背景

生产出了问题,被拉来救场。

业务流程是 server1 会从 kafka1 中消费数据,重新整理数据后通过 tcp 发送给 logstash,logstash 再将数据发送给下游的 kafka2;为了确保不丢数据,logstach 开了某种幂等插件。

问题现象是,kafka1 中有 1000 条数据,tcp 确认发送成功 500 条,剩下的 500 条数据未发送或者发送失败了;kafka2 中未收到数据;

debug 过程

首先从源头看 server 1 中 tcp 是未发送还是发送失败了。从日志中未发现发送失败的日志。

/why-golang-prog-hang-tcp-window-full/images/server1-goroutine-stacktrace.png

从程序中的 goroutine 调用栈可以确认,程序卡在 syscall write 27 minutes,怀疑 tcp 连接出了问题;

在 logstash 这个机器上 ss 查看连接状态,可以看到,logstash 没有将 socket 中的数据 copy 到用户空间。

/why-golang-prog-hang-tcp-window-full/images/logstash-ss.png

Recv-Q: The count of bytes not copied by the user program connected to this socket. man 8 netstat

查看 logstash 发现发送到 kafka 失败了:

/why-golang-prog-hang-tcp-window-full/images/logstash-err-log.png

看下抓包,可以看到再 keepalive + zerowindow,确认为 tcp read buf 已经满了,tcp 无法继续发送新的数据,golang 的 socket.write 只能死等。

/why-golang-prog-hang-tcp-window-full/images/logstash-tcpdump.png

解决

修复 kafka TopicAuthorizationException: not authorized to aceess topics, 业务即恢复。

综上

  1. tcp write 的成功,不意味着数据真的被接收到,如果业务需要确保下游被接收到,需要有业务 ack 机制;
  2. 改进方案可以考虑添加一个 kafka topic 或者中间添加一个 mq,整体数据流变更为 kafka1 -> server1 -> mq -> kafka2