go语言需自行实现负载均衡:用httputil.newsinglehostreverseproxy配合原子索引轮询,重写director设req.url.scheme/host,自定义roundtripper控连接池,滑动窗口健康检查。

Go 语言没有内置负载均衡器,你写的 http.Server 默认就是单点;要分发请求到多个后端,必须自己实现调度逻辑或选对第三方库——不是“配”,而是“写”或“选”。
用 httputil.NewSingleHostReverseProxy 实现轮询时,Director 必须重设 req.URL.Host 和 req.URL.Scheme
标准库的 httputil.NewSingleHostReverseProxy 只支持单后端,多后端得靠自己写 Director。常见错误是只改 req.URL.Path,漏掉 Host 和 Scheme:
- 不设
req.URL.Host→ 请求发到本机,报connection refused或一直502 - 不设
req.URL.Scheme→http.Transport无法识别协议,panic 或静默失败 - 后端带路径前缀(如
https://s1:8443/api/v1)时,req.URL.Path要手动截掉原始前缀,否则变成/api/v1/api/v1/xxx
正确写法示例:
proxy := httputil.NewSingleHostReverseProxy(nil)
proxy.Transport = &http.Transport{ /* 自定义配置 */ }
proxy.Director = func(req *http.Request) {
u := backends[rrIndex%len(backends)]
req.URL.Scheme = u.Scheme
req.URL.Host = u.Host
req.URL.Path = singleJoiningSlash(u.Path, req.URL.Path)
req.Header.Set("Host", u.Host) // 关键:显式设 Host 头
}
RoundRobin 索引必须用原子操作,不能用普通变量++
并发下 index++ 是非原子的,多个 goroutine 同时读到相同值,导致流量打偏到同一台后端。错误现象包括监控里流量倾斜、日志中重复出现某台 IP 的 5xx。
- Go 1.19+ 推荐用
atomic.Uint64:idx := b.index.Add(1) % uint64(len(b.backends)) - 老版本用
sync/atomic.AddUint64,注意参数是*uint64类型 - 别在循环里直接写
(curIndex++) % n,顺序必须是:先Load,再取模,最后Add - 如果后端列表动态更新(如健康检查剔除),索引要重置,否则可能长期跳过有效节点
健康检查不能只靠 time.Tick,得有滑动窗口 + 惰性恢复
高频无节制探测会压垮脆弱后端;简单布尔开关又会导致抖动放大——刚恢复就又被误判下线。
- 用
time.Ticker控制频率(如 10 秒一次),别用time.AfterFunc递归调用 - 每次探测必须带独立超时:
ctx, cancel := context.WithTimeout(ctx, 2*time.Second) - 状态缓存用
sync.Map(key 是后端地址),避免 map + mutex 锁竞争 - 推荐滑动窗口:最近 10 次检查失败 ≥ 3 次才标记 unhealthy;恢复用惰性策略——最后一次成功时间 > 30s 才重新纳入轮询
- 检查路径统一用
/healthz,响应码必须是200 OK,别掺业务逻辑
http.RoundTripper 必须自定义,不能复用 http.DefaultTransport
默认传输层不控制连接数、空闲超时和 TLS 复用,线上容易触发 too many open files 或卡在 CLOSE_WAIT。
- 必须显式配置
MaxIdleConns(建议 1000)、MaxIdleConnsPerHost(建议 200)、IdleConnTimeout(建议 30s) - 若后端是 HTTPS,需确保
TLSClientConfig.NextProtos包含"h2",否则 HTTP/2 握手失败 - 自签名证书场景,不能设
InsecureSkipVerify: true,应加载可信RootCAs并设ServerName - 健康检查和代理转发要用两个独立的
http.Client,避免短连接干扰主流量连接池
最易被忽略的是:后端地址必须用完整 URL(如 "https://10.0.1.10:8443")初始化,不能只传 "10.0.1.10:8443";否则 url.Parse 后 u.Scheme 为空,RoundTrip 直接 panic。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











