服务端应使用令牌桶或原子计数器限流而非防抖,golang.org/x/time/rate 适合单机低流量场景,需按用户ID或IP隔离限流桶并缓存+过期清理,多实例必须用Redis+Lua原子脚本,同时须限制请求体大小防内存耗尽。

服务端没有“防抖”,只有限流。高频刷接口的本质是单位时间请求数超标,必须用令牌桶或原子计数器控制放行节奏,而不是等“最后一次触发”——HTTP 请求之间没有状态关联。
用 golang.org/x/time/rate 做单机令牌桶最简可行
适合低流量内部接口或压测兜底,不依赖外部组件:
-
rate.NewLimiter(rate.Limit(5), 10)表示每秒最多 5 次请求,桶容量 10(允许突发) - 别写
rate.Every(time.Second/5),易误解;rate.Limit(5)更直白 - 每个请求调
limiter.Allow()判断,返回false就立刻返回429 - 全局共用一个实例会误伤用户,必须按维度隔离(见下一条)
按 IP 或用户 ID 隔离限流桶,避免共享配额
NAT 环境下多个用户共用一个出口 IP,仅靠 c.ClientIP() 不够准,必须结合认证态:
- 认证中间件必须在限流中间件之前注册,否则
c.GetString("user_id")为空,退化成 IP 限流 - 用
sync.Map缓存每个userID或标准化后的 IP 对应的*rate.Limiter实例 - IPv6 地址含冒号,拼 key 前先用
net.ParseIP标准化,再转字符串 - 务必加过期清理:5 分钟无访问就
delete对应 entry,否则内存泄漏
多实例部署必须用 Redis + Lua 原子脚本
单机 rate.Limiter 在负载均衡后完全失效,所有实例各自统计,总量不可控:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- Redis key 示例:
rate:uid:<code>uid>:api/v1/submit或rate:ip:<code>c.ClientIP():api/v1/submit - 用 Lua 脚本把
INCR、判断、EXPIRE三步合并,避免竞态导致超限漏判 - 别手写
redis.Incr+ if 判断 —— 中间插入请求时,计数可能已超但未被拦截 -
redis.NewScript必须初始化阶段复用,不要每次请求都新建,否则解析开销大
别忽略请求体大小限制,它和限流一样是防刷第一道门
恶意构造超大 Body 可绕过频率限制直接耗尽内存:
- Gin 的
BodySizeLimit配置只在绑定前生效;若中间件提前调了r.ParseForm()或io.ReadAll(r.Body),该配置就失效 - 推荐用
http.MaxBytesHandler全局包裹,设统一宽松上限(如 10MB),在路由分发前就拦截 - 上传类接口需差异化:用
http.MaxBytesReader在 handler 开头动态赋值,配合r.ParseMultipartForm(8 控制内存缓存大小 - 总上传体积限制必须前置,
ParseMultipartForm的maxMemory参数不限制总大小
真正难的是维度选择和过期策略:IP 易伪造、用户 ID 需认证前置、Redis key 设计要带业务上下文、sync.Map 的清理时机稍有偏差就会内存持续增长——这些细节不跑线上压测根本看不出问题。

















