当前位置: 首页 > 科技观察

Go超时触发大量Fin-Wait2

时间:2023-03-11 23:44:59 科技观察

本文转载自微信公众号《码农桃花源》,作者风云是她。转载本文请联系码农桃花源公众号。通过grafana监控面板,发现几个高频业务缓存节点出现大量fin-wait2,且fin-wait2状态持续时间较长。通过连接的ip地址和抓包数据判断对端的业务。此外,我们经常创建新连接。我们优化了golangnet/httptransport的连接池,但是已经建立的连接并没有被复用。另外,由此带来的问题是大量时间等待的出现。毕竟fin-wait2收到peerfin后会转为time-wait状态。但是状态是正常的。问题分析通过分析业务日志,发现大量的接口超时问题。连接地址与netstat中fin-wait2的目的地址一致。那么问题已经搞清楚了。当http请求触发超时时,定时器关闭连接对象。两边都是关闭的,所以连接自然不能复用,所以需要新建一个连接,但是因为对端的API接口在逻辑上被阻塞了,所以又触发了超时,继续。//xiaoui.c??cGet"http://xxxx":contextdeadlineexceeded(Client.Timeoutexceededwhileawaitingheaders)Get"http://xxxx":contextdeadlineexceeded(Client.Timeoutexceedwhileawaitingheaders)Get"http://xxxx":contextdeadlineexceeded(Client.Timeoutexceededwhileawaitingheaders)strace跟踪socket系统调用,发现golang的socket读写超时没有使用setsockoptso_sndtimeoso_revtimeo参数。[pid34262]epoll_ctl(3,EPOLL_CTL_ADD,6,{EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET,{u32=1310076696,u64=140244877192984}})=0[pid34265]epoll_pwait(3,<未完成...>[pid]<34262...getsocknameresumed>{sa_family=AF_INET,sin_port=htons(45242),sin_addr=inet_addr("127.0.0.1")},[112->16])=0[pid34264]epoll_pwait(3,<未完成...>[pid34262]setsockopt(6,SOL_TCP,TCP_NODELAY,[1],4<未完成...>[pid34262]setsockopt(6,SOL_SOCKET,SO_KEEPALIVE,[1],4<未完成...>[pid34264]读取(4,[pid34262]setsockopt(6,SOL_TCP,TCP_KEEPINTVL,[30],4代码分析通过net/http源码可以看到socket是通过timer来实现的,在连接的roundTrip方法中,有超时导致关闭连接的逻辑,由于http的语义不支持多路复用,为了避免之后返回的数据造成混淆超时,连接就干脆直接closures了,当超时触发的时候,就会closed自动连接,这里涉及挥手四次,作为关闭方,会发送fin,对端内核回复ack。此时client从fin-wait1到fin-wait2,server处于close-wait状态,等待触发closesyscall系统调用。服务器什么时候触发关闭动作?您需要等待net/http处理程序业务逻辑执行完成。//xiaoui.c??cvarerrTimeouterror=&httpError{err:"net/http:timeoutawaitingresponseheaders",超时:true}func(pc*persistConn)roundTrip(req*transportRequest)(resp*Response,errerror){for{testHookWaitResLoop()select{caseerr:=<-writeErrCh:ifdebugRoundTrip{req.logf("writeErrChresv:%T/%#v",err,err)}iferr!=nil{pc.close(fmt.Errorf("writererror:%v",err))returnnil,pc.mapRoundTripError(req,startBytesWritten,err)}ifd:=pc.t.ResponseHeaderTimeout;d>0{ifdebugRoundTrip{req.logf("startingtimerfor%v",d)}timer:=time.NewTimer(d)defertimer.Stop()//preventleaksrespHeaderTimer=timer.C}case<-pc.closech:...case<-respHeaderTimer:ifdebugRoundTrip{req.logf("timeoutwaitingforresponseheaders.")}pc.close(errTimeout)returnnil,errTimeout解决方法:要么增加客户端的超时时间,要么优化对端获取数据的逻辑,总之减少超时的触发。这个问题和Go无关,openresyt和python是同一个问题。