api请求频次统计应根据粒度需求分层实现:粗粒度用中间件,细粒度(用户/ip/token/条件过滤)需在业务handler或context中打点;须过滤无关路径、保障并发安全(sync.map+atomic)、规范prometheus指标label(路径归一化)、避免统计拖慢主流程。

API请求频次统计该挂在哪一层
频次统计不能只靠中间件一锤定音,得看你要统计什么粒度。如果只是粗略统计总请求数或按路径分组,gin.HandlerFunc 中间件足够;但如果要区分用户、IP、Token 或带条件过滤(比如只统计 401 错误),就得在业务 handler 里手动打点,或者用 context 透传标识后由统一 collector 汇总。
常见错误是把统计逻辑写在 router.Use() 后面的全局中间件里,结果连健康检查 /healthz、静态资源、OPTIONS 预检都计入——实际要排除这些路径。
- 统计前先做路径白名单/黑名单:用
strings.HasPrefix(c.Request.URL.Path, "/api/")过滤 - 避免在中间件里直接操作共享 map —— 并发写 panic,必须加
sync.RWMutex或用sync.Map - 不要在中间件里调用
c.Next()后再读取c.Writer.Status()来判断是否成功——Gin 的 writer 是 wrapper,状态可能被后续中间件覆盖
用 sync.Map 实现线程安全的路径计数器
sync.Map 比 map[string]int + 全局锁更轻量,适合读多写少的频次场景(比如每秒几千次请求,但统计维度只有几十个路径)。但它不支持遍历计数,所以需要额外维护一个原子变量或定期 snapshot。
示例结构:
var pathCounter = sync.Map{} // key: string (path), value: *int64
func incPathCount(path string) {
v, _ := pathCounter.LoadOrStore(path, new(int64))
atomic.AddInt64(v.(*int64), 1)
}
注意:sync.Map 的 LoadOrStore 返回的是 interface{},必须类型断言;别用 int 存,高并发下 atomic.AddInt64 才安全。
- 别用
pathCounter.Store(path, count+1)—— 竞态,丢失更新 - 如果需按时间窗口(如每分钟重置),得配合
time.Ticker单独起 goroutine 清空,不能依赖 HTTP 请求触发 - 调试时用
pathCounter.Range(func(key, value interface{}) bool { ... })取快照,但生产环境慎用——Range 是阻塞操作
如何导出统计结果为 Prometheus metrics
Gin 本身不内置指标暴露,得靠 promhttp + 自定义 prometheus.CounterVec。关键不是“怎么注册”,而是“怎么 label 匹配真实需求”:比如按 path、method、status 维度聚合,就别只用一个 flat counter。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
推荐初始化方式:
var (
apiRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "api_requests_total",
Help: "Total number of API requests.",
},
[]string{"path", "method", "status"},
)
)
func init() {
prometheus.MustRegister(apiRequestsTotal)
}
在中间件中打点:
apiRequestsTotal.WithLabelValues(
c.Request.URL.Path,
c.Request.Method,
strconv.Itoa(c.Writer.Status()),
).Inc()
- label 值别直接填
c.FullPath()—— 动态路由如/user/:id会导致 label 爆炸,应提前 normalize 成/user/{id} - 别漏掉
status为 0 的情况(比如 panic 导致未写 response),这类请求需在 recover 中补打 - 如果用了 Gin 的
c.AbortWithStatusJSON(),它内部会设 status,但c.Writer.Status()要在c.Next()后才能读准
高频请求下如何避免统计拖慢主流程
统计本身不该成为性能瓶颈。实测显示,单次 atomic.AddInt64 几乎无开销,但频繁调用 prometheus.CounterVec.WithLabelValues(含 map 查找 + 字符串拼接)在 QPS > 5k 时可观测到 0.1ms 级延迟增长。
优化手段有限但明确:
- 用固定 label 组合预先缓存
prometheus.Counter实例,比如counterMap["/api/v1/users_GET_200"],避免每次动态构造 - 对非核心路径(如文档页、favicon)直接跳过统计,用
if !shouldTrack(c.Request)快速返回 - 别在统计逻辑里做任何网络调用(如上报到 Redis)、日志 I/O 或 JSON 序列化——这些必须异步化或批量 flush
- 如果用 Redis 计数,务必用
INCR而非GET+SET,并设置合理超时(如EXPIRE key 3600)防止内存泄漏
真正容易被忽略的,是统计维度膨胀带来的 cardinality 问题:一个未 normalize 的路径模板,加上用户 ID、trace ID 等 label,可能让 Prometheus series 数量失控——这不是 Gin 的问题,但你得在埋点前想清楚。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










