中间件应按需注册而非全局:认证、限流等绑定到具体路由组,静态资源跳过中间件,健康检查用裸 handler;HTTP 调用须设超时,DB 连接需配置池;重操作异步化并安全复制上下文;高频分配用 sync.Pool 复用;日志监控需异步、脱敏、采样、原子更新。

中间件不该全局注册,只挂到需要的路由组
全局注册中间件(比如 r.Use(Logger(), Recovery()))会让每个请求——包括 /healthz、/static/xxx 这类高频轻量路径——都执行完整链路。哪怕只是多一次函数调用,QPS 也会掉几个百分点。
实际做法:
- 把认证、限流、日志等中间件按需绑定到具体分组:
v1 := r.Group("/api/v1").Use(AuthMiddleware(), RateLimit()) - 静态资源直接跳过所有中间件:
r.StaticFS("/static", http.Dir("./static")) - 健康检查写成裸 handler:
r.GET("/healthz", func(c *gin.Context) { c.Status(200) })
阻塞操作必须加超时和连接池,否则中间件就成瓶颈
中间件里如果调用没设超时的 http.Get(),或没配连接池的 db.QueryRow(),这个 goroutine 就会卡住。它不阻塞其他请求,但会拖慢整体吞吐,并可能耗尽数据库连接。
关键控制点:
- HTTP 调用一律用
context.WithTimeout(c.Request.Context(), 800*time.Millisecond) - 数据库连接必须显式配置:
db.SetMaxOpenConns(100)、db.SetConnMaxLifetime(time.Hour) - 加密、大文件读写等重操作,考虑放进
go func() { }()异步处理,但记得用c.Copy()安全复制上下文
中间件里频繁 new 对象?用 sync.Pool 复用
比如日志中间件每次构造 map[string]interface{},或 JSON 解析中间件反复分配 bytes.Buffer,都会抬高 GC 频率。实测高频接口中,这类分配占内存压力的 60% 以上。
典型复用模式:
- 定义池:
var bufferPool = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) } } - 在中间件中取用:
buf := bufferPool.Get().(*bytes.Buffer); defer bufferPool.Put(buf) - 注意:归还前要
buf.Reset(),避免脏数据污染后续请求
日志和监控中间件别在主线程里干重活
Logger 中间件如果同步写磁盘、打全量 trace 或拼接长字符串,会直接拉高 P99 延迟。尤其在日志量大的时候,单次写入可能耗时几十毫秒。
更合理的做法:
- 日志内容异步投递到 channel,由单独 goroutine 批量刷盘
- 敏感字段脱敏、采样开关(如只记录 1% 的慢请求)在中间件入口就做判断
- 监控指标(如请求耗时)用原子变量或
sync/atomic更新,避免锁竞争
中间件本身不慢,慢的是你让它干了不该干的事。最常被忽略的一点是:中间件里的任何一次未受控的内存分配、任意一个没设 timeout 的外部调用、任何一处忘记归还的 sync.Pool 对象,都会在高并发下被指数级放大。











