Go语言 连接池相关总结:HTTP、RPC、Redis 和数据库等( 二 )


用户协程在使用 http.Client 发送请求时,一路到 http.Transport.roundTrip -> http.persistConn.roundTrip:
 pc.writech <- writeRequest{req, writeErrCh, continueCh} resc := make(chan responseAndError) pc.reqch <- requestAndChan{  req:        req.Request,  ch:         resc,  addedGzip:  requestedGzip,  continueCh: continueCh,  callerGone: gone, }在该函数中,将 request 和接收请求的 ch 传入到 reqch,把 writeRequest 写入到 writech 。

  • writeloop 从 writech 中收到了写请求,会把内容写入到 conn 上,这个请求也就发给 server 端了
  • readloop 收到 requestAndChan 结果,上面 writeloop 相当于已经把请求数据发送到 server 端,readloop 这时候可以从 conn 上读出 server 发回的 response 数据,所以 readloop 主要做的就是 ReadResponse,然后把 response 的内容写入到 requestAndChan.ch 中 。
  • 主协程只要监听 requestAndChan.ch 来接收相应的 response 即可(用 select 同时监听 err、连接关闭等 chan) 。
这里 http 标准库的做法要参考一下,把接收数据和相应的错误处理代码可以都集中在一起:
 for {  testHookWaitResLoop()  select {  case err := <-writeErrCh: // 往 server 端写数据异常   if debugRoundTrip {    req.logf("writeErrCh resv: %T/%#v", err, err)   }   if err != nil {    pc.close(fmt.Errorf("write error: %v", err))    return nil, pc.mapRoundTripError(req, startBytesWritten, err)   }   if d := pc.t.ResponseHeaderTimeout; d > 0 {    if debugRoundTrip {     req.logf("starting timer for %v", d)    }    timer := time.NewTimer(d)    defer timer.Stop() // prevent leaks    respHeaderTimer = timer.C   }  case <-pc.closech: // 连接关闭异常   if debugRoundTrip {    req.logf("closech recv: %T %#v", pc.closed, pc.closed)   }   return nil, pc.mapRoundTripError(req, startBytesWritten, pc.closed)  case <-respHeaderTimer: // 读请求头超时   if debugRoundTrip {    req.logf("timeout waiting for response headers.")   }   pc.close(errTimeout)   return nil, errTimeout  case re := <-resc: // 正常地从 response 的 channel 里读到了响应数据   if (re.res == nil) == (re.err == nil) {    panic(fmt.Sprintf("internal error: exactly one of res or err should be set; nil=%v", re.res == nil))   }   if debugRoundTrip {    req.logf("resc recv: %p, %T/%#v", re.res, re.err, re.err)   }   if re.err != nil {    return nil, pc.mapRoundTripError(req, startBytesWritten, re.err)   }   return re.res, nil  case <-cancelChan: // 用户侧通过 context 取消了流程   pc.t.CancelRequest(req.Request)   cancelChan = nil  case <-ctxDoneChan: // 这个应该意思差不多   pc.t.cancelRequest(req.Request, req.Context().Err())   cancelChan = nil   ctxDoneChan = nil  } }http2https://tools.ietf.org/html/rfc7540 https://github.com/bagder/http2-explained
Go语言 连接池相关总结:HTTP、RPC、Redis 和数据库等

文章插图
 
4
http2 协议通过 frame 中的 stream id 对请求和响应进行关联 。
http2 可以不等待上一个请求响应后再发下一个请求,因此同一个连接上可以实现 multiplexing 。标准库中对于 http2 连接的处理复用了 http1 的连接池逻辑,只不过从连接池中取连接时,并没有真的从连接池里把这个连接拿走 。获取到的连接依然保留在 connpool 中 。


推荐阅读