Go 超时引发大量 Fin-Wait2

开发 后端
通过grafana监控面板,发现了几个高频的业务缓存节点出现了大量的fin-wait2,而且fin-wait2状态持续了不短的时间。通过连接的ip地址和抓包数据判断出对端的业务。

[[410689]]

本文转载自微信公众号「码农桃花源」,作者峰云就她了。转载本文请联系码农桃花源公众号。

通过grafana监控面板,发现了几个高频的业务缓存节点出现了大量的fin-wait2,而且fin-wait2状态持续了不短的时间。通过连接的ip地址和抓包数据判断出对端的业务。除此之外,频繁地去创建新连接,我们对golang net/http transport的连接池已优化过,但established已建连的连接没有得到复用。

另外,随之带来的问题是大量time-wait的出现,毕竟fin-wait2在拿到对端fin后会转变为time-wait状态。但该状态是正常的。

分析问题

通过分析业务日志发现了大量的接口超时问题,连接的地址跟netstat中fin-wait2目的地址是一致的。那么问题已经明确了,当http的请求触发超时,定时器对连接对象进行了关闭。这边都close了,那么连接自然无法复用,所以就需要创建新连接,但由于对端的API接口出现逻辑阻塞,自然就又触发了超时,continue。

  1. // xiaorui.cc 
  2.  
  3. Get "http://xxxx": context deadline exceeded (Client.Timeout exceeded while awaiting headers) 
  4.  
  5. Get "http://xxxx": context deadline exceeded (Client.Timeout exceeded while awaiting headers) 
  6.  
  7. Get "http://xxxx": context deadline exceeded (Client.Timeout exceeded while awaiting headers) 

通过strace追踪socket的系统调用,发现golang的socket读写超时没有使用setsockopt so_sndtimeo so_revtimeo参数。

  1. [pid 34262] epoll_ctl(3, EPOLL_CTL_ADD, 6, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=1310076696, u64=140244877192984}}) = 0 
  2. [pid 34265] epoll_pwait(3,  <unfinished ...> 
  3. [pid 34262] <... getsockname resumed>{sa_family=AF_INET, sin_port=htons(45242), sin_addr=inet_addr("127.0.0.1")}, [112->16]) = 0 
  4. [pid 34264] epoll_pwait(3,  <unfinished ...> 
  5. [pid 34262] setsockopt(6, SOL_TCP, TCP_NODELAY, [1], 4 <unfinished ...> 
  6. [pid 34262] setsockopt(6, SOL_SOCKET, SO_KEEPALIVE, [1], 4 <unfinished ...> 
  7. [pid 34264] read(4,  <unfinished ...> 
  8. [pid 34262] setsockopt(6, SOL_TCP, TCP_KEEPINTVL, [30], 4 <unfinished ...> 

代码分析

通过net/http源码可以看到socket的超时控制是通过定时器来实现的,在连接的roundTrip方法里有超时引发关闭连接的逻辑。由于http的语义不支持多路复用,所以为了规避超时后再回来的数据造成混乱,索性直接关闭连接。

当触发超时会主动关闭连接,这里涉及到了四次挥手,作为关闭方会发送fin,对端内核会回应ack,这时候客户端从fin-wait1到fin-wait2,而服务端在close-wait状态,等待触发close syscall系统调用。服务端什么时候触发close动作?需要等待net/http handler业务逻辑执行完毕。

  1. // xiaorui.cc 
  2.  
  3. var errTimeout error = &httpError{err: "net/http: timeout awaiting response headers", timeout: true
  4.  
  5. func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) { 
  6.     for { 
  7.         testHookWaitResLoop() 
  8.         select { 
  9.         case err := <-writeErrCh: 
  10.             if debugRoundTrip { 
  11.                 req.logf("writeErrCh resv: %T/%#v", err, err) 
  12.             } 
  13.             if err != nil { 
  14.                 pc.close(fmt.Errorf("write error: %v", err)) 
  15.                 return nil, pc.mapRoundTripError(req, startBytesWritten, err) 
  16.             } 
  17.             if d := pc.t.ResponseHeaderTimeout; d > 0 { 
  18.                 if debugRoundTrip { 
  19.                     req.logf("starting timer for %v", d) 
  20.                 } 
  21.                 timer := time.NewTimer(d) 
  22.                 defer timer.Stop() // prevent leaks 
  23.                 respHeaderTimer = timer.C 
  24.             } 
  25.         case <-pc.closech: 
  26.             ... 
  27.         case <-respHeaderTimer: 
  28.             if debugRoundTrip { 
  29.                 req.logf("timeout waiting for response headers."
  30.             } 
  31.             pc.close(errTimeout) 
  32.             return nil, errTimeout 

如何解决

要么加大客户端的超时时间,要么优化对端的获取数据的逻辑,总之减少超时的触发。这个问题其实跟 Go 没有关系,换成openresyt和python同样有这个问题。

 

责任编辑:武晓燕 来源: 码农桃花源
相关推荐

2022-02-25 14:12:10

熔断Sentinel微服务

2020-05-07 11:00:24

Go乱码框架

2021-04-26 09:40:46

QPS数据库Redis

2023-04-11 22:01:57

2013-07-25 10:30:02

亚马逊数据中心

2021-01-20 10:16:26

高并发数据服务

2020-04-02 07:31:53

RPC超时服务端

2017-06-09 10:16:40

2015-04-02 09:43:17

2021-09-30 14:23:23

服务器开发工具

2021-08-02 22:31:24

Go语言Append

2021-10-28 19:35:48

Go 控制超时

2022-01-09 17:38:32

goHttpClient 集群

2012-05-15 09:49:03

TIME_WAITMySQL

2020-08-06 10:12:20

TCP连接网络协议

2020-10-15 12:26:28

黑客勒索软件攻击

2021-07-22 16:19:11

Android 12谷歌用户

2021-09-05 11:20:04

带宽网络排查

2021-05-20 06:03:20

比特币区块链加密货币

2017-07-02 16:50:21

点赞
收藏

51CTO技术栈公众号