模拟一下,服务端:
- // gohttpserver.go
-
- package main
-
- import (
- "fmt"
- "log"
- "net/http"
- "time"
- )
-
- func main() {
- http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintf(w, "Hi there, say I love you! current time %v", time.Now())
- })
-
- log.Fatal(http.ListenAndServe(":9901", nil))
- }
客户端:
- // gohttpclient.go
- package main
-
- import (
- "fmt"
- "io/ioutil"
- "net"
- "net/http"
- "time"
- )
-
- func main() {
- url := "http://127.0.0.1:9901"
- for i := 0; i < 10000; i++ {
- func() {
- trans := &http.Transport{
- DialContext: (&net.Dialer{
- Timeout: 3 * time.Second,
- KeepAlive: 30 * time.Second,
- }).DialContext,
- ForceAttemptHTTP2: true,
- MaxIdleConns: 100,
- IdleConnTimeout: 90 * time.Second,
- TLSHandshakeTimeout: 10 * time.Second,
- ExpectContinueTimeout: 1 * time.Second,
- }
- //defer trans.CloseIdleConnections()
-
- client := &http.Client{
- Timeout: time.Duration(100) * time.Millisecond,
- Transport: trans,
- }
-
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- return
- }
-
- //req.Close = true // fd leak without setting this
- resp, err := client.Do(req)
- if err != nil {
- // handle error
- }
- defer resp.Body.Close()
- haha, _ := ioutil.ReadAll(resp.Body)
- fmt.Println(string(haha))
- }()
-
- time.Sleep(500 * time.Millisecond)
- }
- }
运行服务端和客户端
- $ go run gohttpserver.go &
- $ go run gohttpclient.go &
查看连接数:
- $ netstat -an |grep 9901 | grep ESTABLISHED
- tcp4 0 0 127.0.0.1.9901 127.0.0.1.54403 ESTABLISHED
- tcp4 0 0 127.0.0.1.54403 127.0.0.1.9901 ESTABLISHED
- tcp4 0 0 127.0.0.1.9901 127.0.0.1.54402 ESTABLISHED
- tcp4 0 0 127.0.0.1.54402 127.0.0.1.9901 ESTABLISHED
- tcp4 0 0 127.0.0.1.9901 127.0.0.1.54398 ESTABLISHED
- tcp4 0 0 127.0.0.1.54398 127.0.0.1.9901 ESTABLISHED
- tcp4 0 0 127.0.0.1.9901 127.0.0.1.54397 ESTABLISHED
- tcp4 0 0 127.0.0.1.54397 127.0.0.1.9901 ESTABLISHED
然后放开客户端的defer trans.CloseIdleConnections()或者req.Close = true,重新运行,都可以修复此问题。
查看源代码 net/http/client.go,可以找到
- func (c *Client) transport() RoundTripper {
- if c.Transport != nil {
- return c.Transport
- }
- return DefaultTransport
- }
然后在 net/http/transport.go 中,继续看,连接池是挂在 Transport 上,所以每次新建 Transport,如果不关闭,就会导致连接泄露。
- // DefaultTransport is the default implementation of Transport and is
- // used by DefaultClient. It establishes network connections as needed
- // and caches them for reuse by subsequent calls. It uses HTTP proxies
- // as directed by the $HTTP_PROXY and $NO_PROXY (or $http_proxy and
- // $no_proxy) environment variables.
- var DefaultTransport RoundTripper = &Transport{
- Proxy: ProxyFromEnvironment,
- DialContext: (&net.Dialer{
- Timeout: 30 * time.Second,
- KeepAlive: 30 * time.Second,
- DualStack: true,
- }).DialContext,
- ForceAttemptHTTP2: true,
- MaxIdleConns: 100,
- IdleConnTimeout: 90 * time.Second,
- TLSHandshakeTimeout: 10 * time.Second,
- ExpectContinueTimeout: 1 * time.Second,
- }
-
- // ...
- 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[*Request]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
- // ...
- }
感谢:
查看指定进程的连接信息
- # ps -ef|grep rig
- root 4865 4258 0 11:30 pts/0 00:00:00 grep --color=auto rig
- footsto+ 14995 1 0 11月12 ? 00:07:29 ./rig_linux_amd64 -u
- footsto+ 15459 1 0 10月18 ? 00:45:26 java -server -Xmx768m -Xms768m -Xmn384m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -Dlogpath.base=/home/footstone/logs/bjca-app-rig-metrics-server -cp /home/footstone/YUQI_TEST/config:/home/footstone/YUQI_TEST/lib/* -jar /home/footstone/YUQI_TEST/lib/rig-metrics-server-1.0.0-SNAPSHOT.jar bjca-app-rig-metrics-server
- rigaga 20730 1 0 9月25 ? 03:30:40 /usr/bin/rigaga -config /etc/rigaga/rigaga.conf -config-directory /etc/rigaga/rigaga.d
- # lsof -p 14995
- COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
- rig_linux 14995 footstone cwd DIR 253,1 4096 2818080 /home/footstone/go-opads/ops-rig/20191112-1573526761
- rig_linux 14995 footstone rtd DIR 253,1 4096 2 /
- rig_linux 14995 footstone txt REG 253,1 22682823 2818082 /home/footstone/go-opads/ops-rig/20191112-1573526761/rig_linux_amd64
- rig_linux 14995 footstone mem REG 253,1 2173512 328343 /usr/lib64/libc-2.17.so
- rig_linux 14995 footstone mem REG 253,1 144792 328369 /usr/lib64/libpthread-2.17.so
- rig_linux 14995 footstone mem REG 253,1 164240 328336 /usr/lib64/ld-2.17.so
- rig_linux 14995 footstone 0r CHR 1,3 0t0 1028 /dev/null
- rig_linux 14995 footstone 1w REG 253,1 1179657 2818128 /home/footstone/go-opads/ops-rig/20191112-1573526761/nohup.out
- rig_linux 14995 footstone 2w REG 253,1 1179657 2818128 /home/footstone/go-opads/ops-rig/20191112-1573526761/nohup.out
- rig_linux 14995 footstone 3r a_inode 0,9 0 5937 inotify
- rig_linux 14995 footstone 4u a_inode 0,9 0 5937 [eventpoll]
- rig_linux 14995 footstone 5u a_inode 0,9 0 5937 [eventpoll]
- rig_linux 14995 footstone 6r FIFO 0,8 0t0 1441054004 pipe
- rig_linux 14995 footstone 7w FIFO 0,8 0t0 1441054004 pipe
- rig_linux 14995 footstone 8w CHR 1,3 0t0 1028 /dev/null
- rig_linux 14995 footstone 10u IPv6 1441050975 0t0 TCP *:10099 (LISTEN)
- rig_linux 14995 footstone 11w REG 253,1 29608321 2818151 /home/footstone/go-opads/ops-rig/20191112-1573526761