最近工作的时候一个接入服务需要测性能测试,万万没想到测出了一个把 linux 句柄打满的问题
具体是什么问题呢,我们一起来看看
正常操作
项目中,有一些 http 请求是这样写的:
- 请求 https 的地址,为了绕过 tls ,加上了
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}
配置 - 正常访问我们需要的请求的地址
- 正常获取我们的期望的数据,正常解析
func main() {
client := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
resp, err := client.Get("https://www.xxxxxx.com")
if err != nil {
fmt.Println("Get err : ", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
例如如下是访问百度的结果,没有什么毛病
t# go run main.go
<html>
<head>
<script>
location.replace(location.href.replace("https://","http://"));
</script>
</head>
<body>
<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>
发现问题
可是例如这样的请求代码拿去做性能测试的话,我们实际遇到的问题是,linux 句柄数被打满了
句柄数被打满了,简单的思考有如下 2 个初步的可能:
- linux 句柄数设置过小
- http 代码没有释放连接
我知道的有如下 3 种方式,可以修改 linux 的句柄数:
1、修改 /etc/profile
直接修改 /etc/profile
, 在 该文件最后加上如下语句
ulimit -n 65535
这里举个例子,设置 65535 个句柄数
修改后 执行
source /etc/profile
查看效果
ulimit -a
2、修改 limits.conf 文件
直接修改 limits.conf,让其生效
vim /etc/security/limits.conf
3、修改 login 文件
我们可以在 /etc/pam.d/login 文件中 添加最下面一行
session required pam_limits.so
例如上面这样添加
上述 第2 和 第3 种方式,需要重新 ssh 进入到服务器,或者重启服务器才可生效
虽然我增大了 linux 句柄数,发现在性能测试中,只是测得可以稍微久一点了,可是最终还是连接数被打满,这是为什么呢?
仔细查看了代码,代码中也有关闭 http 的连接
那么问题会是处在哪里呢?
找到问题解决问题
仔细查看了代码,只有一个怀疑点了,那就是下面这句话
client := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
最初开始使用这句话的时候,目的也只是为了绕过 tls ,并没有考虑太多,现在仔细看看 http.Transport
结构体里面的功能
type Transport struct {
idleMu sync.Mutex
closeIdle bool // user has requested to close all idle conns
idleConn map[connectMethodKey][]*persistConn // most recently used at end
idleConnWait map[connectMethodKey]wantConnQueue // waiting getConns
idleLRU connLRU
reqMu sync.Mutex
reqCanceler map[cancelKey]func(error)
altMu sync.Mutex // guards changing altProto only
altProto atomic.Value // of nil or map[string]RoundTripper, key is URI scheme
connsPerHostMu sync.Mutex
connsPerHost map[connectMethodKey]int
connsPerHostWait map[connectMethodKey]wantConnQueue // waiting getConns
...........省略多行 ...........
// TLSClientConfig specifies the TLS configuration to use with
// tls.Client.
// If nil, the default configuration is used.
// If non-nil, HTTP/2 support may not be enabled by default.
TLSClientConfig *tls.Config
// TLSHandshakeTimeout specifies the maximum amount of time waiting to
// wait for a TLS handshake. Zero means no timeout.
TLSHandshakeTimeout time.Duration
// DisableKeepAlives, if true, disables HTTP keep-alives and
// will only use the connection to the server for a single
// HTTP request.
//
// This is unrelated to the similarly named TCP keep-alives.
DisableKeepAlives bool
...........省略多行 ...........
}
仔细查看了上述结构之后,发现 DisableKeepAlives
可以禁用长连接,,每个请求都会创建一个连接,切请求完就会马上关闭连接
正确设置 Transport 后问题得以解决
client := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DisableKeepAlives: true,
},
}
该问题表象看上去是没有设置好 http.Transport
实际上是 go http 包对于连接的管理 我们还没有去熟悉他,对于他关于连接的具体实现和细节代码,日后有机会再分享
代码修改完毕,性能测试果然正常通过,对技术对代码一定要有敬畏之心
欢迎点赞,关注,收藏
朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力
好了,本次就到这里
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
我是阿兵云原生,欢迎点赞关注收藏,下次见~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。