最直接的go微服务防刷票限流方案是用net/http中间件结合golang.org/x/time/rate.limiter,按路径独立配置、用allown()返回429,配合可信代理ip提取与access_token绑定用户身份,并放行options预检请求。

用 net/http 中间件做请求频次限制最直接
Go 微服务里防刷票,第一道防线就是限流,别一上来就搞 JWT 鉴权或 IP 黑名单——那些解决不了高频低权重请求。用中间件在 http.Handler 链路里卡住,既轻量又可控。
常见错误是直接在 handler 函数里用 time.Now() + map 计数:没并发安全、不支持分布式、内存无限增长。必须用带过期和原子操作的结构。
- 推荐用
golang.org/x/time/rate的Limiter,每个接口路径配一个独立Limiter实例(比如每秒 5 次) - 不要全局复用同一个
Limiter,否则 /login 和 /vote 共享额度会误伤正常用户 - 注意
Limiter.WaitN()会阻塞,生产环境建议用Limiter.AllowN()配合 429 响应,避免协程堆积 - 如果服务是多实例,单机限流只能防脚本小白;真要防集群刷票,得接 Redis + Lua 原子计数,但延迟和复杂度明显上升
校验 X-Forwarded-For 头时必须信任上游代理
想按客户端 IP 限流?直接读 r.RemoteAddr 会拿到负载均衡器的内网地址。得看 X-Forwarded-For,但这里坑极多。
典型错误是无条件取头里第一个 IP:攻击者随便加个 X-Forwarded-For: 1.1.1.1, 2.2.2.2 就能绕过。你的服务必须明确知道谁是可信代理(比如 Nginx、ALB),只信任它添加的最后一个 IP。
立即学习“go语言免费学习笔记(深入)”;
- 配置可信代理 CIDR,例如
10.0.0.0/8或具体 IP 列表 - 解析
X-Forwarded-For时,从右往左找第一个不在可信网段的 IP,才是真实客户端 - 如果没配可信代理列表,宁可不用这个头,老实用
r.RemoteAddr限流(至少能防住直连扫描) - 某些云厂商(如腾讯云 CLB)用的是
X-Real-IP,不是X-Forwarded-For,得按实际网关行为适配
Vote 接口必须绑定用户身份而非设备指纹
只靠 User-Agent + IP 做去重,刷票成本几乎为零。真正有效的约束,是把投票动作和不可轻易伪造的身份锚定在一起。
设备指纹(如 Canvas/FingerprintJS)在服务端根本拿不到完整数据,前端算完传过来又可被重放——等于没设防。
- 必须要求调用方携带短期有效的
access_token,且 token 绑定用户 ID 和设备唯一标识(由后端生成,不依赖前端) - 投票前查数据库确认该用户今日未投过此选项,而不是只查缓存(缓存可能穿透或未及时失效)
- 避免用手机号或邮箱做唯一键直接查库,高并发下易成瓶颈;改用预生成的
user_id + vote_item_id + date组合键查 Redis - 注意 token 过期时间别设太长(比如 7 天),否则被盗用后风险期太长;2 小时较平衡
上线前漏掉 OPTIONS 预检请求会导致限流误杀
CORS 场景下,浏览器发 POST 投票请求前会先发 OPTIONS 预检。如果中间件没放过这类请求,直接限流拦截,前端永远收不到 200,只会看到 429 或超时。
这不是逻辑漏洞,是典型的网关链路错位:预检请求不该计入业务频次,但默认中间件一视同仁。
- 在限流中间件里加判断:
r.Method == "OPTIONS"就直接next.ServeHTTP() - 别依赖前端加
Access-Control-Allow-Origin头来绕过——那是响应头,不影响预检是否发送 - 如果用了第三方 CORS 库(如
rs/cors),确认它是否已自动跳过 OPTIONS;没跳过就得自己 wrap 一层 - 本地调试常测不到这问题,因为开发服务器一般不走完整 CORS 流程,压测时务必模拟真实浏览器环境
真正的难点从来不在写几行限流代码,而在于你是否清楚每一层网关、每一个 HTTP 头、每一次 token 校验背后,到底是谁在控制流向、谁在伪造身份、谁又悄悄绕过了你以为的防线。










