Go语言无内置负载均衡器,需自行实现或选第三方库;标准库httputil需手动处理Director、健康检查与路径前缀;gorilla/reverseproxy有KeepAlive、权重生效时机等坑;复杂场景应直接使用Traefik或Envoy。

Go 语言里没有内置的负载均衡器
Go 标准库不提供像 Nginx 或 Envoy 那样的反向代理级负载均衡能力。你写的 http.Server 本身只是单点服务,要实现客户端请求分发到多个后端,得自己构造转发逻辑,或者借助第三方库——不是“怎么配”,而是“怎么写”或“怎么选”。
用 net/http/httputil + RoundRobin 实现最简轮询
这是最常被搜到的方案,但容易漏掉关键细节:默认的 httputil.NewSingleHostReverseProxy 只支持单后端;多后端必须自己实现 http.RoundTripper 或重写 Director 函数。
-
Director必须重设req.URL.Host和req.URL.Scheme,否则请求会发到本机(常见错误现象:connection refused或一直 502) - 后端健康检查得自己加,标准库不自动踢掉宕机节点
- 如果后端路径带 prefix(比如
http://s1:8080/api/v1),Director里要手动截掉原始路径前缀,否则会变成/api/v1/api/v1/xxx
proxy := httputil.NewSingleHostReverseProxy(nil)
proxy.Transport = &http.Transport{...}
// 每次请求选一个 backend
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)
}
gorilla/reverseproxy 比标准库更省心,但仍有坑
这个库封装了多后端、权重、超时等,看起来开箱即用,实际用起来要注意:
- 它默认不开启连接复用(
KeepAlive),高并发下容易耗尽文件描述符,得显式配置http.Transport - 权重(
Weight)只在初始化时读取一次,运行时改权重不生效 - 它的
Director是函数类型,但签名和标准库不一致,直接套用会编译失败:参数是*http.Request,不是*httputil.ProxyRequest - 错误日志默认输出到
os.Stderr,没做 hook 的话,线上出问题很难定位
别硬扛,该上专用组件时就上
如果你需要熔断、限流、一致性哈希、gRPC 负载均衡、动态上游发现(比如从 Consul 同步服务列表),Go 自己拼的 proxy 很快会变成技术债。
立即学习“go语言免费学习笔记(深入)”;
- 轻量场景用
traefik(Go 写的,配置即代码,支持自动 TLS 和服务发现) - 中大型系统直接上
envoy,用 xDS API 动态管理集群,Go 进程只负责业务逻辑 - 哪怕只是本地开发联调,也比手写
Director加sync.Mutex更可靠
真正难的不是“怎么实现轮询”,而是“怎么让失败请求不卡住、不堆积、不污染下游指标”。这些细节藏在 transport 超时、idle 连接回收、context cancel 传播里,一不留神就在线上吃大亏。


















