httpclient 并发 导致 goroutine 泄露 报错 socket too many files

2019-12-23 15:02:03 +08:00
 tim0991

代码背景

使用 golang 验证代理 Ip,代码主要作用如下

问题

ip 文件内容一般是 100W 行以上,程序运行一段时间之后会出现socket: too many files open

我的尝试

最开始以为是持久连接的问题,就设置了keep-alive: false,设置之后发现还是有问题 使用 pprof 调试发现很多 goroutine 卡在这里,但是此时 channel 长度是比设定值要小的,代表是可以接收数据,等于是老的 goroutine 没有释放,新的 goroutine 一直在创建

internal/poll.runtime_pollWait(0x7f004f1ca2f8, 0x72, 0xffffffffffffffff)
	/usr/local/go/src/runtime/netpoll.go:184 +0x55
internal/poll.(*pollDesc).wait(0xc0029e6f18, 0x72, 0x1000, 0x1000, 0xffffffffffffffff)
	/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x45
internal/poll.(*pollDesc).waitRead(...)
	/usr/local/go/src/internal/poll/fd_poll_runtime.go:92
internal/poll.(*FD).Read(0xc0029e6f00, 0xc002938000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
	/usr/local/go/src/internal/poll/fd_unix.go:169 +0x1cf
net.(*netFD).Read(0xc0029e6f00, 0xc002938000, 0x1000, 0x1000, 0x0, 0x0, 0xc001f21f18)
	/usr/local/go/src/net/fd_unix.go:202 +0x4f
net.(*conn).Read(0xc0017ae198, 0xc002938000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
	/usr/local/go/src/net/net.go:184 +0x68
bufio.(*Reader).fill(0xc00180ca20)
	/usr/local/go/src/bufio/bufio.go:100 +0x103
bufio.(*Reader).ReadSlice(0xc00180ca20, 0xa, 0xc001f21840, 0xc001f21888, 0x40c0c6, 0xc00087e120, 0x90)
	/usr/local/go/src/bufio/bufio.go:359 +0x3d
bufio.(*Reader).ReadLine(0xc00180ca20, 0x8, 0xc0006c6a80, 0x7f0051656460, 0x0, 0x2, 0xc329f8)
	/usr/local/go/src/bufio/bufio.go:388 +0x34
net/textproto.(*Reader).readLineSlice(0xc001f21960, 0xc00087e120, 0xc002938000, 0x7f004f3698c8, 0xc0027bdd01, 0x101000000950280)
	/usr/local/go/src/net/textproto/reader.go:57 +0x6c
net/textproto.(*Reader).ReadLine(...)
	/usr/local/go/src/net/textproto/reader.go:38
net/http.ReadResponse(0xc00180ca20, 0xc00106b400, 0x1000, 0xc002938000, 0xc0017ae198)
	/usr/local/go/src/net/http/response.go:161 +0xd1
net/http.(*Transport).dialConn(0xc002945a40, 0x94cd60, 0xc000024100, 0xc0029e6d80, 0x8b4508, 0x5, 0xc002685940, 0x11, 0x0, 0xc000288fa8, ...)
	/usr/local/go/src/net/http/transport.go:1544 +0x85a
net/http.(*Transport).dialConnFor(0xc002945a40, 0xc000ec1ce0)
	/usr/local/go/src/net/http/transport.go:1308 +0xdc
created by net/http.(*Transport).queueForDial
	/usr/local/go/src/net/http/transport.go:1277 +0x41d

因为阅读 golang http 源码太过于吃力,所以只大概跟了一下代码,我理解这段代码是创建 connection 请求并返回, 想请教一下各位这个 connection 不释放的 具体原因到底是为什么

代码和测试文件

测试文件 golang 代码

7366 次点击
所在节点    Go 编程语言
48 条回复
guonaihong
2019-12-23 15:08:40 +08:00
用一个全局的 http.Client 就行。不需要每次 new 个新的。
opengps
2019-12-23 15:09:12 +08:00
文件跟数据库是两回事,你这并行验证的需求要用的不是文件数据,而是数据库
tim0991
2019-12-23 15:11:51 +08:00
@guonaihong 难道不是代理 ip 不同 transport 不同吗? transport 不同还能用同一个 client 吗?
tim0991
2019-12-23 15:13:02 +08:00
@opengps 谢谢指点 但就事论事 想了解一下 为什么以及如何解决文中的 goroutine 泄露问题
EthanDon
2019-12-23 15:14:13 +08:00
这个坑我踩过。。。

你看下你的 http 请求的 response 的 body 是不是没有关闭。这个 body 不管请求发送过程有没有出错,都要调用 body.Close()的。可以看下 go 的文档: https://golang.org/pkg/net/http/
“The client must close the response body when finished with it:”

还有个操作是给所有 http 请求加上超时时间。

https://stackoverflow.com/questions/37454236/net-http-server-too-many-open-files-error/48342086#48342086?newreg=e8bd30ac66d443138486653353d0c59a
https://sanyuesha.com/2019/09/10/go-http-request-goroutine-leak/
tim0991
2019-12-23 15:17:10 +08:00
@EthanDon 你好 谢谢你的回复 首先 body 我关了,其次我给 http.client 设置了超时, 最后 我在问题的结尾留了代码地址 如果你有空可以看看 帮忙指点一下的话感激不尽
lishunan246
2019-12-23 15:25:34 +08:00
这跟 goroutine 和 http 应该没有关系。
单纯是 TIME_WAIT 的连接太多了。
guonaihong
2019-12-23 15:26:42 +08:00
虽然没有调试代码,但是,起的 go 程数是 ulimit -n 的,会不会太多?可以把控制 go 程的代码 queueCh <- true 放到 go 程外面。
tim0991
2019-12-23 15:27:57 +08:00
@lishunan246 那请问 1. 如何控制 time wait 数量? 2 如何主动关闭 time wait?
guonaihong
2019-12-23 15:28:52 +08:00
7 楼说的也是一种可能,可以打开快速回收优化下。
tim0991
2019-12-23 15:30:04 +08:00
@guonaihong 谢谢 我生成环境 channel 是在 goroutine 外的,这个是临时准备用来测试的,然后 ulimit -n 我设置的是 10000W 然后 channel 长度就是 9000 这应该不算长吧,现在只有单个进程
index90
2019-12-23 15:31:53 +08:00
试一下在关闭之前:
io.Copy(ioutil.Discard, resp.Body)
monsterxx03
2019-12-23 15:34:11 +08:00
queueCh <- true 这行要放在 go func() 之前, 不然你希望的阻塞不会生效的.

还有你 wg.Add(1) 放在 continue 的判断之后, 不然假如有空行, 最后 Wait 就永远结束不了
tim0991
2019-12-23 15:35:57 +08:00
@index90 还是有问题
tim0991
2019-12-23 15:37:58 +08:00
@monsterxx03 感谢你的意见 代码是早上在地铁上面写的 有点匆忙不好意思,然后我按照你的建议改过之后 任然是同样的错误
rimutuyuan
2019-12-23 15:45:04 +08:00
ulimit -n 只对单次会话有效
持久化要设置 sysctl

而且 9000 并发是不是太高了,有这么大的带宽吗
tim0991
2019-12-23 15:48:36 +08:00
@rimutuyuan 问题只针对单次回话,带宽是另外的问题了,假设有吧。。。
index90
2019-12-23 15:56:37 +08:00
在本机测试了一下,结论 TIMEWAIT 太多,TIMEWAIT 都会占用 fd
tim0991
2019-12-23 16:10:30 +08:00
@index90 那请问应该怎么解决呢。。。。。我用 time wait 关键字搜索了一下 都说加 disable keep alive 就好了。。。。 能不能麻烦指点一下方向
index90
2019-12-23 16:14:36 +08:00
Google 一下 too many time wait"

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/631536

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX