限流中间件必须用 wait() 而非 allow(),因 allow() 粗暴丢弃请求致 429 突增而负载低;wait() 配合 context.withtimeout 实现流量软着陆,且需按路由/用户等级独立配置限流器。

限流中间件必须用 Wait() 而不是 Allow()
直接调 limiter.Allow() 做判断再 http.Error,会在突发流量下瞬间打满 429 错误率,但 CPU 和下游实际负载却很低——因为请求没排队、全被粗暴丢弃。真实网关场景里,用户刷新页面、前端重试、SDK 自动重连都会制造脉冲,Wait() 才是让流量“软着陆”的关键。
使用要点:
-
rate.NewLimiter(10, 5)表示“每秒补 10 个令牌,桶最多存 5 个”,不是“每秒最多 10 次请求”;空闲时桶满,能扛住 5 个瞬时并发 - 中间件中应调
limiter.Wait(r.Context()),配合context.WithTimeout控制最大等待时长(比如 200ms),超时才返回 429 - 别在全局变量里复用同一个
rate.Limiter实例来限所有接口——不同路由路径、不同用户等级需独立限流器
降级逻辑不能写在中间件里统一 fallback
网关层的降级不是“所有请求失败都返回兜底 JSON”,而是有明确触发条件和作用域的主动决策。把降级逻辑塞进通用中间件,会导致 400、401、404 全被吞掉,掩盖真实业务问题。
正确做法:
- 降级只响应 transient error:如
context.DeadlineExceeded、net.ErrClosed、gobreaker.ErrOpenState,不处理参数错误或权限拒绝 - 降级函数必须无外部依赖:不能查 Redis、不能发 HTTP 请求、不能访问数据库;只读本地缓存(如预热好的
map[string][]byte)或配置文件 - 每个后端服务调用点单独封装降级分支,例如
callOrderServiceWithFallback(),而不是在中间件里统一if err != nil { return fallback() }
熔断器必须按下游服务粒度隔离
一个 gobreaker.CircuitBreaker 实例管所有后端,等于订单服务挂了,用户服务也跟着不可用。网关转发时,每个 http.Client 或每个 RPC 方法都该配独立熔断器。
配置关键点:
-
gobreaker.Settings.Name必须唯一可识别,例如"payment-service-create-order",方便 Prometheus 标签聚合 -
MaxRequests别设 3 或 100:低流量下设太小会误熔断,高流量下设太大导致故障发现滞后;建议从 10–20 起调,结合平均 RT 动态调整 -
ReadyToTrip函数里加权判断更稳:比如counts.FailureCount > 5 && float64(counts.FailureCount)/float64(counts.TotalCount) > 0.5
限流 + 熔断 + 降级要分层串联,不能混在同一中间件
三者职责不同,强行揉在一起会让逻辑耦合、调试困难、指标难归因。网关请求链应明确分层:先限流(防过载)→ 再熔断(防级联)→ 最后降级(保主干)。每一层只做一件事,且失败后透传原始错误供上层判断。
典型陷阱:
- 在限流中间件里同时做熔断判断,导致限流统计被熔断逻辑污染
- 降级函数里又调一次
http.Get,主服务挂了,降级路径也雪崩 - 没用
context.WithTimeout包裹Wait()和Execute(),导致排队/熔断探测无限阻塞











