接上篇,面试被问处理过哪些故障的备选。此问题是由于 Golang
未关闭 HTTP
连接,产生大量 Close-Wait
状态用尽端口。
问题背景
故障程序是一个 Telegraf
插件,名为 url_monitor,修改自 http_response 插件,用于监控 URL
的状态码,响应内容及响应时间,本站之前也发过相关文章,见 基于Telegraf和InfluxDB的url监控方案。
故障发生时,首先是收到大量 URL
报警,检查后发现是误报,立即查看 Telegraf
日志,发现大量 cannot assign requested address
日志。通过 Zabbix
查看 TCP
状态监控,发现 Close-Wait
状态持续增长,经过近一个月时间超过 6
万,导致端口用尽。
故障处理
首先立即暂停报警发送程序,同时通过微信群通知研发同事,忽略误报。之后重启 Telegraf
,释放被占用的端口。
接下来是分析代码,调用 resp.Body.Close()
,确保不堆积 Close-Wait
状态,见 commit/d230b。
// 当请求失败,resp为nil时,直接defer会导致panic,因此需要先判断
if resp != nil {
// 保证关闭连接. 不关闭连接将导致close-wait累积,最终占满端口。监控将报错:cannot assign requested address
defer resp.Body.Close()
}
原理分析
TCP四次挥手
- 客户端(主动端)发送
FIN
包通知服务端(被动端)断开连接,并进入FIN-WAIT-1
状态 - 服务端(被动端)响应
ACK
,并进入CLOSE-WAIT
状态 - 服务端(被动端)处理完成之后,发送
FIN+ACK
,并进入LAST-ACK
状态 - 客户端(主动端)响应
ACK
,进入TIME-WAIT
状态,然后等待2MSL
之后关闭连接,服务端(被动端)收到ACK
后关闭连接
示意图如下
HTTP请求,谁先关闭连接?
HTTP 1.0协议
- 如果响应头中有
content-length
头,则客户端在接收body
时,可以依照这个长度来接收数据,接收完后,就表示这个请求完成了。
–>服务端和客户端在明确自己数据处理完成后,都可以主动断开连接 - 如果没有
content-length
头,则客户端会一直接收数据,直到服务端主动断开连接,才表示body
接收完了。
–>客户端必须等待服务端断开连接后,才能断开自己的连接。
HTTP 1.1协议
- 如果响应头中的
Transfer-encoding
为chunked
传输,则表示body
是流式输出,body
会被分成多个块,每块的开始会标识出当前块的长度,此时,body
不需要通过长度来指定。
–>服务端和客户端在明确自己数据处理完成后,都可以主动断开连接 - 如果响应头中的
Transfer-encoding
为非chunked
传输,但有content-length
,则按照content-length
来接收数据。
–>服务端和客户端在明确自己数据处理完成后,都可以主动断开连接 - 如果响应头中的
Transfer-encoding
为非chunked
传输,但没有content-length
,则客户端接收数据,直到服务端主动断开连接。
–>客户端必须等待服务端断开连接后,才能断开自己的连接。
resp.Body.Close()
If Response.Body
won't be closed with Close()
method than a resources associated with a fd won't be freed. This is a resource leak.
Closing Response.Body
From response source:
It is the caller's responsibility to close Body.
So there is no finalizers bound to the object and it must be closed explicitly.
Error handling and deferred cleanups
On error, any Response can be ignored. A non-nil Response with a non-nil error only occurs when CheckRedirect fails, and even then the returned Response.Body is already closed.
resp, err := http.Get("http://example.com/")
if err != nil {
// Handle error if error is non-nil
}
defer resp.Body.Close() // Close body only if response non-nil
参考连接
1. https://blog.csdn.net/weixin_39366864/article/details/104552012
2. https://stackoverflow.com/a/41512208
发表回复