应自己编写中间件,在c.next()前后用time.now()和time.since()精确计时,通过c.set()/c.get()存取起始时间避免并发问题,并用sync.map聚合统计、/metrics接口暴露摘要。

怎么用 Gin 中间件记录每个请求的耗时
直接在 gin.Engine 上注册中间件是最轻量的方式,Gin 自带的 gin.Logger() 不记录耗时,得自己写。核心是利用 time.Now() 和 time.Since(),再把结果写进上下文或日志。
常见错误是把耗时统计逻辑放在 handler 里——这样漏掉 panic、重定向、中间件提前返回等情况;必须在最外层中间件里做开始/结束时间标记。
- 用
c.Set("start_time", time.Now())存起始时间,比全局变量安全,避免并发覆盖 - 结束时用
c.Get("start_time")取出并计算,注意类型断言:start, ok := c.Get("start_time").(time.Time) - 别用
defer在中间件开头写耗时打印——defer会等整个请求链结束才执行,但中间件可能被后续 handler 提前中断(比如c.Abort()),导致时间不准
如何把耗时数据存到内存 map 并支持简单查询
临时统计不需要数据库,用 sync.Map 是合理选择:它比普通 map + mutex 更适合读多写少的场景(比如 API 耗时汇总),且避免重复加锁开销。
键建议用 method:route 格式(如 "GET:/api/users"),而不是完整 URL,否则带 query 或 path param 的请求会散列太细,失去聚合意义。
- 每次请求结束时,把耗时毫秒值
int64推进一个 slice(比如最多存最近 1000 条),避免无限增长 - 用
sync.Map.LoadOrStore(key, &histogram{})初始化每个路由的统计结构,避免竞态 - 注意 map 的 value 类型不能是普通 struct(
sync.Map要求可寻址),得用指针或自定义类型
怎样暴露 /metrics 接口返回 JSON 格式的统计摘要
这个接口不是 Prometheus 格式,只是给运维或前端看的简易汇总,字段要实用:平均耗时、P95、最大值、总请求数、最近 10 条原始耗时。
别在响应里直接遍历 sync.Map 并计算 P95——高并发下频繁排序会拖慢接口;应该在写入时就维护好滚动统计(比如用 container/heap 维护 top-k,或只存 min/max/sum/count)。
- 返回 JSON 用
c.JSON(200, data),不要手动json.Marshal+c.Data,避免 mime-type 错误 - 对历史数据做限流:比如每分钟最多访问 5 次,用
golang.org/x/time/rate控制,防止被刷爆内存 - 敏感字段如具体路径参数(
/user/:id)要脱敏,避免暴露业务细节
为什么不用第三方库如 prometheus-client-golang
如果你只需要看几个关键接口的分钟级趋势,Prometheus 太重:要配 exporter、pushgateway、grafana,还要处理指标生命周期和存储。而手写统计能控制粒度(比如只存最近 5 分钟)、不依赖外部服务、调试时直接 curl /metrics 就能看到。
但要注意:手写方案无法回溯历史(超过内存保留窗口就丢)、不支持多实例聚合、没有告警能力。如果团队已有监控体系,优先对接 Prometheus;如果只是开发期快速验证性能瓶颈,自己攒更省事。
最易被忽略的是内存泄漏风险:没设 TTL 或清理机制,sync.Map 里的 key 会越积越多——尤其当路由动态生成(如带 UUID 的 webhook 地址),必须定期扫描过期 key 并删除。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











