为什么使用 golang http包 会把 linux 句柄打满?

简介: 最近工作的时候一个接入服务需要测性能测试,万万没想到测出了一个把 linux 句柄打满的问题

最近工作的时候一个接入服务需要测性能测试,万万没想到测出了一个把 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

image.png

2、修改 limits.conf 文件

直接修改 limits.conf,让其生效

vim /etc/security/limits.conf

image.png

3、修改 login 文件

我们可以在 /etc/pam.d/login 文件中 添加最下面一行

 session   required   pam_limits.so

image.png

例如上面这样添加

上述 第2 和 第3 种方式,需要重新 ssh 进入到服务器,或者重启服务器才可生效

虽然我增大了 linux 句柄数,发现在性能测试中,只是测得可以稍微久一点了,可是最终还是连接数被打满,这是为什么呢?

仔细查看了代码,代码中也有关闭 http 的连接

image.png

那么问题会是处在哪里呢?

找到问题解决问题

仔细查看了代码,只有一个怀疑点了,那就是下面这句话

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 包对于连接的管理 我们还没有去熟悉他,对于他关于连接的具体实现和细节代码,日后有机会再分享

代码修改完毕,性能测试果然正常通过,对技术对代码一定要有敬畏之心


欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

image.png

好了,本次就到这里

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是阿兵云原生,欢迎点赞关注收藏,下次见~

相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
相关文章
|
2月前
|
安全 关系型数据库 MySQL
Linux下安装mysql8.0(以tar.xz包安装--编译安装)
通过上述步骤,您完成了从下载、编译、安装到配置MySQL 8.0的全过程。此过程虽然较为复杂,但提供了对MySQL安装环境的完全控制,有助于满足特定的部署需求。在实际操作中,根据具体的系统环境,可能还需调整部分步骤或解决未预见的依赖问题。始终参考官方文档和社区资源,保持安装过程与最新版本的兼容性。
831 67
|
3月前
|
Go
Golang的math包常用方法
这篇文章介绍了Golang的math包中的常量和常用方法,并通过示例代码展示了如何使用这些常量和方法。
179 87
Golang的math包常用方法
|
2月前
|
Linux Docker 容器
Centos安装docker(linux安装docker)——超详细小白可操作手把手教程,包好用!!!
本篇博客重在讲解Centos安装docker,经博主多次在不同服务器上测试,极其的稳定,尤其是阿里的服务器,一路复制命令畅通无阻。
1055 4
Centos安装docker(linux安装docker)——超详细小白可操作手把手教程,包好用!!!
|
3月前
|
存储 Go
Golang语言基于go module方式管理包(package)
这篇文章详细介绍了Golang语言中基于go module方式管理包(package)的方法,包括Go Modules的发展历史、go module的介绍、常用命令和操作步骤,并通过代码示例展示了如何初始化项目、引入第三方包、组织代码结构以及运行测试。
55 3
|
3月前
|
Go
Golang语言基于GOPATH方式管理包(package)
这篇文章详细介绍了Golang语言中基于GOPATH方式管理包(package)的方法,包括包的概述、定义、引入格式、别名使用、匿名引入,以及如何快速入门自定义包,并通过具体代码案例展示了包的环境准备、代码编写、细节说明和程序运行。
41 3
|
3月前
|
Go
Golang语言之包依赖管理
这篇文章详细介绍了Go语言的包依赖管理工具,包括godep和go module的使用,以及如何在项目中使用go module进行依赖管理,还探讨了如何导入本地包和第三方库下载的软件包存放位置。
41 3
|
4月前
|
Web App开发 存储
常见抓包工具配置抓取HTTPS
常见抓包工具配置抓取HTTPS
|
4月前
|
移动开发 监控 网络协议
在Linux中,如何查看 http 的并发请求数与其 TCP 连接状态?
在Linux中,如何查看 http 的并发请求数与其 TCP 连接状态?
|
4月前
|
机器学习/深度学习 Ubuntu Linux
在Linux中,如何按照该要求抓包:只过滤出访问http服务的,目标ip为192.168.0.111,一共抓1000个包,并且保存到1.cap文件中?
在Linux中,如何按照该要求抓包:只过滤出访问http服务的,目标ip为192.168.0.111,一共抓1000个包,并且保存到1.cap文件中?
|
4月前
|
Ubuntu Linux
在Linux中,如何升级系统内所有已安装软件包?
在Linux中,如何升级系统内所有已安装软件包?