golang长连接的误用

误用一:忘记读取响应的body

由于忘记读取响应的body导致创建大量处于TIME_WAIT状态的连接(同时产生大量处于transport.go的readLoop和writeLoop的协程)

在linux下运行下面的代码:

package main

import (
  "fmt"
  "html"
  "log"
  "net"
  "net/http"
  "time"
)

func startWebserver() {

  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
      fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
  })

  go http.ListenAndServe(":8080", nil)

}

func startLoadTest() {
  count := 0
  for {
      resp, err := http.Get("http://localhost:8080/")
      if err != nil {
          panic(fmt.Sprintf("Got error: %v", err))
      }
      resp.Body.Close()
      log.Printf("Finished GET request #%v", count)
      count += 1
  }

}

func main() {


  startWebserver()

  startLoadTest()

}

在程序运行时另外开一个终端运行下面的命令:

netstat -n | grep -i 8080 | grep -i time_wait | wc -l

你会看到TIME_WAIT数量在持续增长

root@myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
166
root@myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
231
root@myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
293
root@myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
349

解决办法: 读取响应的body
更改startLoadTest()函数,添加下面的代码:

func startLoadTest() {
  for {
          ...
      if err != nil {
          panic(fmt.Sprintf("Got error: %v", err))
      }
      io.Copy(ioutil.Discard, resp.Body)  // <-- add this line
      resp.Body.Close()
                ...
  }

}

现在再次运行netstat -n | grep -i 8080 | grep -i time_wait | wc -l,你会发现TIME_WAIT状态的连接数为0

误用二:空闲连接最大数量设置太小,实际连接数量超过连接池的限制

连接的数量超过连接池的限制导致出现大量TIME_WAIT状态的连接

这种情况时由于持续超过连接池导致许多短连接被打开。
请看下面的代码:

package main

import (
  "fmt"
  "html"
  "io"
  "io/ioutil"
  "log"
  "net/http"
  "time"
)

func startWebserver() {

  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

      time.Sleep(time.Millisecond * 50)

      fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
  })

  go http.ListenAndServe(":8080", nil)

}

func startLoadTest() {
  count := 0
  for {
      resp, err := http.Get("http://localhost:8080/")
      if err != nil {
          panic(fmt.Sprintf("Got error: %v", err))
      }
      io.Copy(ioutil.Discard, resp.Body)
      resp.Body.Close()
      log.Printf("Finished GET request #%v", count)
      count += 1
  }

}

func main() {

  // start a webserver in a goroutine
  startWebserver()

  for i := 0; i < 100; i++ {
      go startLoadTest()
  }

  time.Sleep(time.Second * 2400)

}

在另外一个终端运行netstat,尽管响应已经被读取,TIME_WAIT的连接数还是持续增加

root@ myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
166
root@ myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
231
root@ myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
293
root@ myhost:/# netstat -n | grep -i 8080 | grep -i time_wait | wc -l
349

什么是TIME_WAIT状态呢?

就是当我们创建大量短连接时,linux内核的网络栈保持连接处于TIME_WAIT状态,以避免某些问题。
例如:避免来自一个关闭的连接延迟的包被后来的连接所接收。并发连接被用地址,端口,序列号等其他机制所隔离开。

为什么这么多的TIME_WAIT端口?

默认情况下,Golang的http client会做连接池。他会在完成一个连接请求后把连接加到一个空闲的连接池中。如果你想在这个连接空闲超时前发起另外一个http请求,它会复用现有的连接。
这会把总socket连接数保持的低一些,直到连接池满。如果连接池满了,它会创建一个新的连接来发起http请求。
那这个连接池有多大呢?看看transport.go:

var DefaultTransport RoundTr
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值