Go 健壮 HTTP 客户端:超时、重试、连接复用与中间件式 RoundTrip

test12026-10-063 次阅读

为什么不能直接用默认 http.Get

net/http 的默认 Client 没有总超时,遇到后端挂起会一直占着 goroutine;默认也不复用连接、不做重试。生产环境的对外调用应当显式配置 Transport、超时与重试策略。

配置带超时与连接池的 Client

package main

import (
	"net/http"
	"time"
)

func newClient() *http.Client {
	transport := &http.Transport{
		MaxIdleConns:        100,
		MaxIdleConnsPerHost: 10,
		IdleConnTimeout:     90 * time.Second,
	}
	return &http.Client{
		Transport: transport,
		Timeout:   5 * time.Second, // 整个请求的上限
	}
}

用自定义 RoundTripper 实现重试

RoundTrip 是每次请求的必经之处。包一层即可在失败时按次数重试,并用 http.Request.Clone 重建请求体。

type retryer struct {
	next     http.RoundTripper
	maxTries int
}

func (r *retryer) RoundTrip(req *http.Request) (*http.Response, error) {
	var resp *http.Response
	var err error
	for i := 0; i < r.maxTries; i++ {
		resp, err = r.next.RoundTrip(req.Clone(req.Context()))
		if err == nil && resp.StatusCode < 500 {
			return resp, nil
		}
		if resp != nil {
			resp.Body.Close()
		}
	}
	return resp, err
}

把重试接入 Client

client := &http.Client{
	Transport: &retryer{next: transport, maxTries: 3},
	Timeout:   5 * time.Second,
}

要点

  • 超时永远要设:连接、读写、总超时分别用 DialContext、ResponseHeaderTimeout 与 Client.Timeout 控制。
  • 重试只对幂等请求(GET、PUT)做,POST 等写操作需业务层确认。
  • 5xx 与网络错误才重试,4xx 通常是参数问题,重试无意义。
  • Transport 复用同一个实例,连接池才生效;不要每次请求 new 一个 Transport。
T

test1

文章作者

为什么不能直接用默认 http.Get net/http 的默认 Client 没有总超时,遇到后端挂起会一直占着 g...

分类
技术
发布时间
2026-10-06
字数
约 1256 字
阅读
3 次