go语言无内置负载均衡器,需用httputil.reverseproxy手动实现:director必须显式设置req.url.scheme、req.url.host和req.host,轮询需线程安全控制,健康检查与后端动态更新须自行编码。

用 Go 标准库的 httputil.ReverseProxy 就能跑起来,不需要额外依赖,但直接照抄示例会转发失败或 502。
为什么 httputil.NewSingleHostReverseProxy 不能直接用于多后端
这个函数只接受一个 *url.URL,内部硬编码为单目标转发。它没有后端池、不支持健康检查、也不带负载策略——本质是“单点代理”,不是“负载均衡器”。你把它当 LB 用,等于把轮询逻辑写在了 Director 外面,而 NewSingleHostReverseProxy 的 Director 是私有实现,没法替换。
- 常见错误现象:
panic: runtime error: invalid memory address(改了Director却没重置req.URLScheme/Host) - 正确做法:必须用
&httputil.ReverseProxy{Director: ...}手动构造,自己控制 URL 重写逻辑 - 关键点:每次请求进来,
Director函数必须显式设置req.URL.Scheme、req.URL.Host和req.Host,否则下游服务收不到有效 Host header
Director 函数里最容易漏掉的三件事
它不只是改 req.URL,还影响请求能否被后端正常识别和路由。漏掉任何一项,轻则 404,重则连接被拒绝。
-
req.URL.Scheme必须设为"http"或"https",不能留空或用"" -
req.URL.Host必须设为后端地址(如"10.0.0.1:8080"),不能只写 IP 不带端口(HTTP 默认 80,HTTPS 默认 443,但显式写出更安全) -
req.Host必须同步设成和req.URL.Host一致,否则某些后端(比如用 Host 做虚拟主机路由的 Nginx)会返回 400
轮询策略要自己加锁,别信网上“原子计数器”示例
Go 的 sync/atomic 对 int32 是安全的,但轮询需要读-改-写(read-modify-write)操作,单纯 atomic.AddInt32 不足以保证线程安全——多个 goroutine 并发调用 Director 时可能跳过某个后端。
- 推荐用
sync.RWMutex+ 普通int:读多写少,锁开销可忽略 - 避免在
Director里做耗时操作(如 HTTP 健康检查),它在请求路径上,延迟直接叠加到用户响应时间 - 如果后端地址变更多,不要热更新切片,而是重建整个
LoadBalancer实例并原子替换 handler,否则可能 panic
ModifyResponse 不只是打印日志,它决定你能不能读响应体
标准 ReverseProxy 默认把响应体流式转发,一旦开始 copy 就无法再读取。想记录、修改或审计响应内容,必须提前接管 res.Body。
- 必须先
ioutil.ReadAll(res.Body)(Go 1.19+ 改用io.ReadAll),再res.Body.Close() - 之后用
io.NopCloser(bytes.NewBuffer(bodyBytes))重新赋值给res.Body,否则下游 handler 会报body closed - 注意内存:大响应体(如文件下载)会导致 OOM,生产环境要加 size 限制或流式处理
真正麻烦的从来不是写几行 ReverseProxy,而是确保每个请求的 URL、Header、Body、TLS 配置、错误传播都按预期流转。很多问题只在高并发或特定后端(比如 gRPC-gateway、Spring Cloud Gateway)下才暴露,本地跑通不等于线上可用。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











