golang的http连接池

1,033 阅读3分钟

studygolang.com/pkgdoc

在Go语言中,tryPutIdleConn方法是在http.Transport中用于将空闲连接放回连接池的方法。当一个HTTP请求完成后,如果连接池中没有达到最大限制,那么该连接就会被放回连接池中,以备下一次使用。 tryPutIdleConn方法的实现逻辑如下: 1. 首先,它会检查连接是否已经关闭,如果已经关闭,则直接返回。 2. 然后,它会检查连接池是否已经满了,如果已经满了,则直接关闭连接。 3. 接着,它会检查连接是否已经在连接池中,如果已经在连接池中,则直接返回。 4. 最后,如果连接没有被关闭,连接池没有满,连接也不在连接池中,那么就将连接放回连接池中。 总的来说,tryPutIdleConn方法的作用是将空闲连接放回连接池中,以便下一次使用。

设置最大空闲连接数和最大空闲时间

在Golang中,可以通过设置http.Transport的MaxIdleConns和IdleConnTimeout字段来控制空闲连接数和空闲时间。 MaxIdleConns表示最大空闲连接数,即在连接池中保持的最大空闲连接数。如果超过这个数目,将会关闭一些空闲连接。可以根据实际情况设置这个值,一般建议设置为CPU核心数的2倍。 IdleConnTimeout表示空闲连接的最大空闲时间,即连接在连接池中保持的最长时间。如果超过这个时间,连接将会被关闭。可以根据实际情况设置这个值,一般建议设置为几分钟。 例如,以下代码设置了最大空闲连接数为100,最大空闲时间为5分钟: transport := &http.Transport{ MaxIdleConns: 100, IdleConnTimeout: 5 * time.Minute, } client := &http.Client{Transport: transport} 通过合理设置这两个参数,可以提高HTTP请求的效率和性能。

http请求示例

// 创建一个自定义的Transport结构体
tr := &http.Transport{
   // 最大空闲连接数
   MaxIdleConns: 10,

   // 空闲连接超时时间
   IdleConnTimeout: 30 * time.Second,

   // 禁用压缩
   DisableCompression: true,
}
// 创建一个http.Client结构体,并设置其Transport字段为自定义的Transport结构体
client := &http.Client{Transport: tr}
// 发送HTTP请求
resp, err := client.Get("https://www.baidu.com")
if err != nil {
   fmt.Println(err)
   return
}
defer resp.Body.Close()
// 处理响应
fmt.Println(resp.StatusCode)

系统默认的空闲时间和最大空闲连接数

var DefaultTransport RoundTripper = &Transport{
   Proxy: ProxyFromEnvironment,
   DialContext: defaultTransportDialContext(&net.Dialer{
      Timeout:   30 * time.Second,
      KeepAlive: 30 * time.Second,
   }),
   ForceAttemptHTTP2:     true,
   MaxIdleConns:          100,
   IdleConnTimeout:       90 * time.Second,
   TLSHandshakeTimeout:   10 * time.Second,
   ExpectContinueTimeout: 1 * time.Second,
}

http包

func (c *Client) Get(url string) (resp *Response, err error) 
func (c *Client) do(req *Request) (retres *Response, reterr error)
func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) 

transport包

    func (t *Transport) roundTrip(req *Request) (*Response, error)
    func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) 
    func (t *Transport) queueForDial(w *wantConn)
    func (t *Transport) dialConnFor(w *wantConn) 
    func (t *Transport) putOrCloseIdleConn(pconn *persistConn)
    func (t *Transport) tryPutIdleConn(pconn *persistConn) error

详情函数tryPutIdleConn

func (t *Transport) tryPutIdleConn(pconn *persistConn) error {
   if t.DisableKeepAlives || t.MaxIdleConnsPerHost < 0 {
      return errKeepAlivesDisabled
   }
   if pconn.isBroken() {
      return errConnBroken
   }
   pconn.markReused()

   t.idleMu.Lock()
   defer t.idleMu.Unlock()

   // HTTP/2 (pconn.alt != nil) connections do not come out of the idle list,
   // because multiple goroutines can use them simultaneously.
   // If this is an HTTP/2 connection being “returned,” we're done.
   if pconn.alt != nil && t.idleLRU.m[pconn] != nil {
      return nil
   }

   // Deliver pconn to goroutine waiting for idle connection, if any.
   // (They may be actively dialing, but this conn is ready first.
   // Chrome calls this socket late binding.
   // See https://www.chromium.org/developers/design-documents/network-stack#TOC-Connection-Management.)
   key := pconn.cacheKey
   // 判断等待空闲连接的队列是否为空
   if q, ok := t.idleConnWait[key]; ok {
      done := false
      if pconn.alt == nil {
         // HTTP/1.
         // Loop over the waiting list until we find a w that isn't done already, and hand it pconn.
         for q.len() > 0 {
         // 从头部取出来一个
            w := q.popFront()
            if w.tryDeliver(pconn, nil) {
               done = true
               break
            }
         }
      } else {
         // HTTP/2.
         // Can hand the same pconn to everyone in the waiting list,
         // and we still won't be done: we want to put it in the idle
         // list unconditionally, for any future clients too.
         for q.len() > 0 {
            w := q.popFront()
            w.tryDeliver(pconn, nil)
         }
      }
      // 如果队列空了
      if q.len() == 0 {
         // 删除队列的map
         delete(t.idleConnWait, key)
      } else {
         // 将空闲连接的map指向了新的队列
         t.idleConnWait[key] = q
      }
      if done {
         return nil
      }
   }

   if t.closeIdle {
      return errCloseIdle
   }
   if t.idleConn == nil {
      t.idleConn = make(map[connectMethodKey][]*persistConn)
   }
   idles := t.idleConn[key]
   // 空闲连接已经是最大了,则抛弃此连接
   if len(idles) >= t.maxIdleConnsPerHost() {
      return errTooManyIdleHost
   }
   // 判断此连接是否存在,不存在抛出错误
   for _, exist := range idles {
      if exist == pconn {
         log.Fatalf("dup idle pconn %p in freelist", pconn)
      }
   }
   // 连接放入到连接池中
   t.idleConn[key] = append(idles, pconn)
   t.idleLRU.add(pconn)
   if t.MaxIdleConns != 0 && t.idleLRU.len() > t.MaxIdleConns {
      oldest := t.idleLRU.removeOldest()
      oldest.close(errTooManyIdle)
      t.removeIdleConnLocked(oldest)
   }

   // Set idle timer, but only for HTTP/1 (pconn.alt == nil).
   // The HTTP/2 implementation manages the idle timer itself
   // (see idleConnTimeout in h2_bundle.go).
   if t.IdleConnTimeout > 0 && pconn.alt == nil {
      if pconn.idleTimer != nil {
         pconn.idleTimer.Reset(t.IdleConnTimeout)
      } else {
         // 空闲连接时间到了之后,将会被关闭
         pconn.idleTimer = time.AfterFunc(t.IdleConnTimeout, pconn.closeConnIfStillIdle)
      }
   }
   pconn.idleAt = time.Now()
   return nil
}

连接队列的key

type connectMethodKey struct {
   proxy, scheme, addr string
   onlyH1              bool
}