必须用http.server.shutdown()配带超时的context.context,因其本身无默认超时,若handler卡在无deadline的db查询或阻塞操作中,shutdown会永久阻塞;传context.background()将导致进程hang死,须用context.withtimeout设合理超时(如15秒)。

为什么 http.Server 的 Shutdown() 必须配超时且不能只靠 context.Background()
服务平滑下线失败,90% 出在 Shutdown() 调用没设超时或用了错误的 context。它本身不带默认超时,一旦某个 handler 卡住(比如没设 deadline 的数据库查询、阻塞 channel 读写),Shutdown() 就会永久阻塞,导致进程 hang 死。
- 必须传入带 deadline 的 context,例如
context.WithTimeout(context.Background(), 15*time.Second) - 调用前确保所有 long-running goroutine 已监听
ctx.Done()并能及时退出(比如用select { case ) - 不要在
Shutdown()后直接调用os.Exit(0)—— 它可能根本没返回;应检查返回 error,非 nil 时才记录日志并退出
net/http 默认 mux 不适合生产,gorilla/mux 或 chi 的路由中间件怎么避免 panic 泄露
原生 http.ServeMux 没有中间件机制,panic 会直接崩掉整个 server。第三方 router 虽好,但中间件里没 recover 就等于裸奔。
- 每个中间件最外层加
defer func() { if r := recover(); r != nil { log.Printf("panic in middleware: %v", r) } }() - 不要在中间件里直接
panic()做业务错误处理;统一用return+ 错误响应(如http.Error(w, "bad request", http.StatusBadRequest)) -
chi的Middleware函数签名是func(http.Handler) http.Handler,recover 必须放在 handler 内部,不是 middleware 函数体里
连接池和超时配置不匹配,http.Client 为什么越跑越慢
常见错配:设置了 Timeout,却没设 Transport 的 IdleConnTimeout 和 MaxIdleConnsPerHost,结果连接复用失效,大量 TIME_WAIT 积压,DNS 解析变慢,最终请求排队。
-
Timeout控制单次请求总耗时,Transport.IdleConnTimeout控制空闲连接保活时间(建议设为 30s) -
MaxIdleConnsPerHost至少设为 100(默认 2),否则高并发下频繁建连;MaxIdleConns建议设为同值 - 若调用的是内部服务,可关掉 TLS 握手验证(
Transport.TLSClientConfig.InsecureSkipVerify = true),但仅限内网
日志输出到文件时,log.SetOutput() 配 os.File 为什么一重启就丢日志
直接 os.OpenFile(..., os.O_APPEND|os.O_CREATE, 0644) 然后传给 log.SetOutput(),看似没问题,但进程重启时文件句柄丢失,新进程无法续写旧文件,还可能因权限/路径问题创建失败。
- 用
lumberjack.Logger替代裸*os.File,它自动轮转、压缩、限制大小,且 reopen 逻辑健壮 - 不要在 init() 里初始化日志 file,而应在 main() 开头做,确保路径存在(
os.MkdirAll(dir, 0755)) - 如果必须用标准库,至少每次写日志前检查
file.Stat(),异常时重新 open —— 但这不如 lumberjack 省心
稳定性不是堆功能堆出来的,是每处超时、每个 goroutine 生命周期、每次连接释放都抠出来的。最容易被忽略的,往往是 shutdown 时那个没被 cancel 的 ticker,或者 log 里那行没 flush 的最后一行。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











