Go 后端接口限流实战:令牌桶、熔断与小程序高峰防刷
Go 后端接口限流实战:令牌桶、熔断与小程序高峰防刷
我的点餐小程序后端跑在 2 核 2G 的华为云 Flexus L 上,带宽只有 2Mbps。饭点一到,几十个用户同时点菜,接口请求瞬间翻倍。有一次我观察到,真正下单的请求没几个,大部分是前端轮询订单状态和重复点击带来的重复请求。小服务器最怕的不是正常流量,而是这种突发又重复的流量。限流就是给接口装一道闸:平时放行无感,高峰时先保住核心请求。
我先用的是最简单的固定窗口计数:每个接口每分钟最多放行 N 次,超了返回 429。这个办法实现简单,用 Redis 的 INCR 加过期时间几行代码就能跑。但它有个硬伤:窗口边界会出毛刺。比如每分钟限 100 次,有人在 59 秒时打了 100 次,下一分钟刚开始又打 100 次,两秒内实际承受了 200 次。对小服务器来说,这种毛刺可能直接把 CPU 打满。所以固定窗口只适合做粗粒度的兜底,不适合做精细限流。
后来我换成了令牌桶。思路是桶里以固定速率补充令牌,每个请求取一枚令牌,没令牌就拒绝。令牌桶的好处是允许短时间小幅突发:桶攒满了可以一次放过去,而长期平均速率仍然受控。Go 里实现很简单,用一个带缓冲的 channel 当桶,后台 goroutine 按速率往里放令牌;接口处理前先尝试取令牌,取不到直接返回 429 和一个友好的提示。我给不同的接口配了不同的桶:下单接口速率低、桶小,菜单查询接口速率高、桶大,静态资源不走限流。这样饭点高峰时,下单仍然能进,查询也不会把服务拖垮。
光有限流还不够,遇到下游变慢还要有熔断。我的订单接口依赖数据库查询,有一次磁盘 IO 抖动,查询变慢,请求在接口里排队,goroutine 越积越多,最后连健康检查都超时了。加了熔断之后,连续失败超过阈值就直接快速失败,不再往下游堆请求;隔一段时间放一个探测请求过去,好了就恢复,坏了继续熔断。熔断和限流的区别是:限流防的是请求太多,熔断防的是下游太慢,两者一起上,小服务器才扛得住异常。
还有一层防刷要放在业务上。点餐场景里最常见的重复请求是用户连点提交按钮和前端轮询。我在下单接口加了幂等键:前端每次提交带一个唯一请求 ID,后端用这个 ID 在 Redis 里做去重,重复提交直接返回第一次的结果,不再重复扣库存、下重复单。订单状态查询则改成长轮询加指数退避,查询间隔从 1 秒逐步拉长,查到终态就停。这样一改,饭点请求量直接少了四成,效果比单纯加机器明显得多。
复盘下来,小服务器做限流的顺序是:先识别真正的核心接口,再按接口分级配令牌桶,兜底用固定窗口,下游不稳加熔断,业务重复用幂等键消化。限流不是为了拒绝用户,而是为了让有限的 2 核 2G 在高峰时先保住能下单、能支付的核心链路。我的体会是:小服务扛高峰,靠的不是硬撑,而是提前把闸装好。
- 点赞
- 收藏
- 关注作者
评论(0)