在这种情况下,我使用的是所有标准的Go库-最重要的是net/http
。
该应用程序由两层组成。第一层是基本的Web应用程序。该网络应用程序为UI提供服务,并根据用户名代理一堆API回调到第二层-因此,它实际上是具有一致哈希的负载平衡器-每个用户都分配给这些第二层节点之一,并且与该用户有关的所有请求都必须发送到该特定节点。
快速详细信息
第一层中的这些API端点有效地读取JSON主体,检查用户名,使用该用户名确定将JSON主体发送到哪个第2层节点,然后将其发送到该节点。这可以通过在全局http.Client
上设置适当的超时时间来完成。
确保未从解封JSON的defer request.Body.Close()
调用返回error
后,服务器端在每个处理程序中执行decoder.Decode(&obj)
。如果存在任何可能发生的代码路径,则不是经常会遵循的代码路径。
症状
在第二层的节点(应用程序服务器)上,我得到这样的日志行,因为它大概是在泄漏套接字并占用所有FD:
2019/07/15 16:16:59 http: Accept error: accept tcp [::]:8100: accept4: too many open files; retrying in 1s
2019/07/15 16:17:00 http: Accept error: accept tcp [::]:8100: accept4: too many open files; retrying in 1s
然后,当我执行lsof
时,将输出14k行,其中11200个是TCP套接字。当我查看lsof
的内容时,我发现几乎所有这些TCP套接字都处于连接状态CLOSE_WAIT
,并且位于我的应用程序服务器(第二层节点)和Web服务器(第一层)之间节点)。
有趣的是,在这段时间内,Web应用程序服务器(第1层)似乎没有出错。
为什么会这样?
我已经看到了很多解释,但是大多数要么指出您需要在自定义http.Client
上指定自定义默认值,而不使用默认值,否则它们会告诉您确保在之后关闭请求正文从第2层处理程序中读取它们。
鉴于所有这些信息,有谁知道我至少可以一劳永逸地解决这个问题?我在互联网上搜索的所有内容都是用户错误,尽管我当然希望是这种情况,但我担心自己无法确定Go标准库的每一个怪癖。
由于无法确切确定发生这种情况需要多长时间-上一次发生是在我开始看到此错误之前已经花了3天的时间,在这一点上显然没有任何恢复,直到我杀死并重新启动过程。
任何帮助将不胜感激!
编辑:客户端代码示例
这是我在Web应用程序(第1层)中调用第2层节点的示例:
var webHttpClient = &http.Client{
Transport: &http.Transport{
MaxIdleConnsPerHost: MaxIdleConnections,
},
Timeout: time.Second * 20,
}
// ...
uri := fmt.Sprintf("http://%s/%s", tsUri, "pms/all-venue-balances")
req, e := http.NewRequest("POST", uri, bytes.NewBuffer(b))
resp, err := webHttpClient.Do(req)
if err != nil {
log.Printf("Submit rebal error 3: %v\n", err)
w.WriteHeader(500)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
w.WriteHeader(200)
w.Write(body)