健康探测需显式配置超时(3–5秒)、禁用重定向、校验状态码与响应体、用带缓冲channel控并发、独立context、参数化配置并校验,避免goroutine泄漏和误判。

用 http.Get 做一次健康探测,但别忽略超时和重定向
直接调用 http.Get 看返回码,是最常见的写法,但线上出问题往往就栽在这一步。默认没有超时,请求卡住会拖垮整个探测协程;而且 http.Get 会自动跟随 3xx 重定向,可能把 200 OK 探测变成对跳转后地址的探测,而你根本没意识到。
- 必须显式设置
http.Client并配置Timeout,建议控制在 3–5 秒内(比如time.Second * 4) - 禁用自动重定向:设置
CheckRedirect返回http.ErrUseLastResponse,确保只看目标 URL 的原始响应 - 检查
resp.StatusCode是否在预期范围内(如200、204),不要只看err == nil - 记得
resp.Body.Close(),否则连接不释放,短时间高频探测会耗尽文件描述符
用 time.Ticker 定时轮询,但别让失败堆积成 goroutine 泄漏
很多人用 for range ticker.C 启动探测,看似简洁,但如果探测函数里有阻塞或 panic,for 循环会卡住或退出,后续探测彻底停摆;更隐蔽的是,如果每次探测都另起 goroutine 却不加管控,失败时没回收,容易积累成千上万僵尸 goroutine。
- 探测逻辑必须包裹
defer func() { recover() }(),防止 panic 中断 ticker - 避免在 ticker 循环里直接
go checkURL(...),改用带缓冲的 channel 控制并发数(比如最多同时 3 个探测) - 每次探测应设独立上下文(
context.WithTimeout),和 ticker 的周期解耦——例如 ticker 每 10 秒触发一次,但单次探测只给 4 秒,超时即弃 - 日志里至少带上 URL 和耗时(
time.Since(start)),方便定位是网络慢还是服务响应慢
HTTP 状态码不是唯一依据,Content-Length 和响应体长度也得看
有些服务在故障时仍返回 200 OK,但响应体为空,或只有错误页 HTML,甚至返回一个固定字符串如 "OK"。光靠状态码会误判“存活”。
- 对关键服务,建议额外校验响应体长度(
resp.ContentLength或读取前 N 字节) - 可配置期望的最小响应长度(如 ≥ 10 字节),或匹配特定响应头(如
Content-Type: application/json) - 若需验证内容,用
io.LimitReader(resp.Body, 1024)限制读取量,防大响应体拖慢探测 - 注意:
ContentLength可能为-1(分块传输),此时只能靠实际读取判断
生产环境别硬编码 URL,用 flag 或 os.Getenv 注入配置
本地测试写死 "http://localhost:8080/health" 没问题,一上生产,URL、超时、间隔全要变,硬编码会导致每次改都要重新编译发布。
- 用
flag.String定义url、interval、timeout参数,启动时传入:./checker -url https://api.example.com/ready -interval 15s - 环境变量更适配容器场景:
URL=https://api.example.com/ready INTERVAL=15s ./checker,用os.Getenv("URL")读取 - 务必做参数校验:空 URL、非法 URL 格式(
url.Parse失败)、非正整数间隔,启动时直接log.Fatal报错退出 - 别把敏感信息(如带 token 的 URL)写进命令行参数,会被
ps看见;改用配置文件或 secret 注入
真正的难点不在发请求,而在让探测行为稳定、可观测、可配置——超时怎么设、失败几次才报警、响应体要不要校验,这些决策点一旦写死,后期调整成本远高于代码本身。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











