中间件中禁止阻塞操作,应仅做轻量判断并用context.context安全传递数据,避免全局变量;日志和监控需采样,不全量记录。

中间件里别做阻塞操作
HTTP中间件运行在请求处理链上,任何同步阻塞行为(比如直接调用数据库、远程API、或耗时计算)都会拖慢整个链路,把并发能力拉到零。Gin或net/http的中间件默认是串行执行的,一个卡住,后面全等。
- 常见错误:
db.QueryRow()、http.Get()、time.Sleep()直接写在中间件函数体里 - 正确做法:只做轻量判断(如解析Header、校验Token签名)、设置Context值;重逻辑下沉到Handler或异步队列
- 例外情况:若必须调用外部服务,用带超时的
context.WithTimeout()包裹,并设短超时(如300ms),失败快速返回
用 context.Context 传递数据,别用全局变量或闭包捕获
中间件之间共享信息(如用户ID、请求ID、租户标识)必须通过context.Context,否则会引发数据污染或竞态——尤其在高并发下,goroutine复用导致闭包变量被多个请求共用。
- 错误示范:
var userID string; func(mw *AuthMiddleware) ServeHTTP(...) { userID = parseToken(...); next.ServeHTTP(...) }——userID是包级变量,所有请求共享 - 正确写法:用
context.WithValue(r.Context(), key, value),且key必须是自定义类型(避免字符串冲突),例如type ctxKey string; const userIDKey ctxKey = "user_id" - 注意:不要往Context里塞大对象(如整个
*User结构体),只放ID或必要字段;大对象建议存在sync.Map或缓存中,用ID查
日志和监控中间件要控制采样率
全量打日志或上报指标会让中间件本身成为性能瓶颈,尤其在QPS过万时,I/O和序列化开销远超业务逻辑。
- 默认不开全量日志:只记录错误、P95+延迟、异常状态码(如5xx)
- 采样策略示例:对
/health这类探针接口不打日志;对普通接口按rand.Intn(100) 做1%采样 - 指标上报避免每请求都
prometheus.CounterVec.Inc(),改用本地计数器+定时flush(如每秒汇总一次) - 结构化日志字段精简:只保留
method、path、status、latency_ms、request_id,去掉user_agent等非必要字段
避免中间件重复初始化依赖
每个中间件实例如果都自己初始化DB连接、Redis客户端或配置解析器,会导致资源浪费和启动变慢,还可能触发连接数上限。
- 错误:在中间件构造函数里调
redis.NewClient()或viper.ReadInConfig() - 正确:依赖对象(如
*redis.Client、*sql.DB)应在main()中初始化并传入中间件,或通过依赖注入容器统一管理 - 轻量对象可复用:比如
bytes.Buffer用sync.Pool池化,但不要在中间件里每次new(bytes.Buffer) - 特别注意:JWT密钥、证书等敏感配置,应在启动时加载进内存,中间件只读取,绝不重新解析文件或环境变量
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











