go net-http 5 timeout
日志报错:
can't connect to remote host (123.125.14.188): Address not avaibal
根据日志查看对应代码
tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } var netClient = &http.Client{Transport: tr, Timeout: time.Second * 120}
抓包显示tcp层一直在keepalive,
根据之前的文章:https://www.cnblogs.com/codestack/p/18218899 https://www.cnblogs.com/codestack/p/17980027
没有设置tcp层的idle timeout,代码修改这样即可
tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, IdleConnTimeout: 60 * time.Second, } var netClient = &http.Client{Transport: tr, Timeout: time.Second * 120}
- http.Client.Timeout:http.Client.Timeout涵盖了HTTP请求从连接建立,请求发送,接收回应,重定向,以及读取http.Response.Body的整个生命周期的超时时间
- http.Client.IdleConnTimeout:HTTP keep-alives超时时间(http.persistConn在使用完毕放入空闲连接池时,会重置http.Client.IdleConnTimeout的HTTP keep-alives超时时间,如果在这段时间内还没有被复用,则会触发超时,执行关闭逻辑;http.persistConn在从空闲连接池中被捞出来复用时,会设置为非空闲状态,不会触发实际的超时操作)
- net.Dialer.Timeout:底层连接建立的超时时间
- net.Dialer.KeepAlive:设置TCP的keepalive参数(syscall.TCP_KEEPINTVL以及syscall.TCP_KEEPIDLE)
// tryPutIdleConn adds pconn to the list of idle persistent connections awaiting // a new request. // If pconn is no longer needed or not in a good state, tryPutIdleConn returns // an error explaining why it wasn't registered. // tryPutIdleConn does not close pconn. Use putOrCloseIdleConn instead for that. 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://insouciant.org/tech/connection-management-in-chromium/.) 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 { delete(t.idleConnWait, key) } else { 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 }
在一次请求(send http.Request以及read http.Response.Body)之后,http.persistConn.readLoop会执行tryPutIdleConn操作 tryPutIdleConn首先查看idleConnWait(map[connectMethodKey]wantConnQueue)中是否存在需要http.persistConn对应host的连接。
如果存在,则将该连接http.persistConn赋予http.wantConn,同时将http.wantConn从Transport.idleConnWait中移除,并返回
如果不存在,则将http.persistConn添加到http.Transport.idleConn(map[connectMethodKey][]*persistConn)空闲连接池中(不超过MaxIdleConns以及MaxIdleConnsPerHost的情况下)
最后设置http.persistConn空闲超时时间(HTTP keep-alives时间):
// 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() ... // closeConnIfStillIdle closes the connection if it's still sitting idle. // This is what's called by the persistConn's idleTimer, and is run in its // own goroutine. func (pc *persistConn) closeConnIfStillIdle() { t := pc.t t.idleMu.Lock() defer t.idleMu.Unlock() if _, ok := t.idleLRU.m[pc]; !ok { // Not idle. return } t.removeIdleConnLocked(pc) pc.close(errIdleConnTimeout) } // t.idleMu must be held. func (t *Transport) removeIdleConnLocked(pconn *persistConn) { if pconn.idleTimer != nil { pconn.idleTimer.Stop() } t.idleLRU.remove(pconn) key := pconn.cacheKey pconns := t.idleConn[key] switch len(pconns) { case 0: // Nothing case 1: if pconns[0] == pconn { delete(t.idleConn, key) } default: for i, v := range pconns { if v != pconn { continue } // Slide down, keeping most recently-used // conns at the end. copy(pconns[i:], pconns[i+1:]) t.idleConn[key] = pconns[:len(pconns)-1] break } } }
在http.Transport.IdleConnTimeout时间内,如果http.persistConn一直没有使用,则会触发超时逻辑,将连接从http.Transport.idleConn连接池中剔除;同时删除http.persistConn对应的底层TCP连接
这里如果说http.persistConn从连接池中被再次使用了,则不会触发超时逻辑,如下:
// getConn dials and creates a new persistConn to the target as // specified in the connectMethod. This includes doing a proxy CONNECT // and/or setting up TLS. If this doesn't return an error, the persistConn // is ready to write requests to. func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) { req := treq.Request trace := treq.trace ctx := req.Context() if trace != nil && trace.GetConn != nil { trace.GetConn(cm.addr()) } w := &wantConn{ cm: cm, key: cm.key(), ctx: ctx, ready: make(chan struct{}, 1), beforeDial: testHookPrePendingDial, afterDial: testHookPostPendingDial, } defer func() { if err != nil { w.cancel(t, err) } }() // Queue for idle connection. if delivered := t.queueForIdleConn(w); delivered { pc := w.pc // Trace only for HTTP/1. // HTTP/2 calls trace.GotConn itself. if pc.alt == nil && trace != nil && trace.GotConn != nil { trace.GotConn(pc.gotIdleConnTrace(pc.idleAt)) } // set request canceler to some non-nil function so we // can detect whether it was cleared between now and when // we enter roundTrip t.setReqCanceler(req, func(error) {}) return pc, nil } cancelc := make(chan error, 1) t.setReqCanceler(req, func(err error) { cancelc <- err }) // Queue for permission to dial. t.queueForDial(w) // Wait for completion or cancellation. select { case <-w.ready: // Trace success but only for HTTP/1. // HTTP/2 calls trace.GotConn itself. if w.pc != nil && w.pc.alt == nil && trace != nil && trace.GotConn != nil { trace.GotConn(httptrace.GotConnInfo{Conn: w.pc.conn, Reused: w.pc.isReused()}) } if w.err != nil { // If the request has been cancelled, that's probably // what caused w.err; if so, prefer to return the // cancellation error (see golang.org/issue/16049). select { case <-req.Cancel: return nil, errRequestCanceledConn case <-req.Context().Done(): return nil, req.Context().Err() case err := <-cancelc: if err == errRequestCanceled { err = errRequestCanceledConn } return nil, err default: // return below } } return w.pc, w.err case <-req.Cancel: return nil, errRequestCanceledConn case <-req.Context().Done(): return nil, req.Context().Err() case err := <-cancelc: if err == errRequestCanceled { err = errRequestCanceledConn } return nil, err } } // queueForIdleConn queues w to receive the next idle connection for w.cm. // As an optimization hint to the caller, queueForIdleConn reports whether // it successfully delivered an already-idle connection. func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) { if t.DisableKeepAlives { return false } t.idleMu.Lock() defer t.idleMu.Unlock() // Stop closing connections that become idle - we might want one. // (That is, undo the effect of t.CloseIdleConnections.) t.closeIdle = false if w == nil { // Happens in test hook. return false } // Look for most recently-used idle connection. if list, ok := t.idleConn[w.key]; ok { stop := false delivered := false for len(list) > 0 && !stop { pconn := list[len(list)-1] if pconn.isBroken() { // persistConn.readLoop has marked the connection broken, // but Transport.removeIdleConn has not yet removed it from the idle list. // Drop on floor on behalf of Transport.removeIdleConn. list = list[:len(list)-1] continue } delivered = w.tryDeliver(pconn, nil) if delivered { if pconn.alt != nil { // HTTP/2: multiple clients can share pconn. // Leave it in the list. } else { // HTTP/1: only one client can use pconn. // Remove it from the list. 如果http.persistConn从空闲连接池中被捞了出来复用,则会执行:t.idleLRU.remove(pconn),将persistConn从http.Transport.idleLRU中剔除(表示不空闲了)
//这样当timer时间触发(HTTP keep-alives超时)调用closeConnIfStillIdle时就会直接返回,不会进行实际性的操作(空操作) t.idleLRU.remove(pconn) list = list[:len(list)-1] } } stop = true } if len(list) > 0 { t.idleConn[w.key] = list } else { delete(t.idleConn, w.key) } if stop { return delivered } } // Register to receive next connection that becomes idle. if t.idleConnWait == nil { t.idleConnWait = make(map[connectMethodKey]wantConnQueue) } q := t.idleConnWait[w.key] q.cleanFront() q.pushBack(w) t.idleConnWait[w.key] = q return false }
如上所示,如果http.persistConn从空闲连接池中被捞了出来复用,则会执行:t.idleLRU.remove(pconn),将persistConn从http.Transport.idleLRU中剔除(表示不空闲了)
http.persistConn在使用完毕放入空闲连接池时,会重置http.Client.IdleConnTimeout的HTTP keep-alives超时时间,如果在这段时间内还没有被复用,则会触发超时,执行关闭逻辑