rate.limiter 不能用于集群限流,因各实例状态不共享;必须分单机兜底(rate.limiter)和集群级(redis/lua 或 sentinel)两层;限流与熔断需联动,先熔断判断再限流,且 burst 与 maxrequests 需协调。

rate.Limiter 不能直接用于集群限流
你写 rate.NewLimiter(100, 20),本地跑压测没问题,一上 K8s 五副本就变成 500 QPS —— 因为每个实例的限流器完全独立,状态不共享。重启后 burst 归零,瞬间涌进一堆请求,下游直接被打挂。
真实生产中必须分两层:单机用 rate.Limiter 做兜底和快速拒绝;集群级限流得靠 Redis + Lua(比如 redis-cell 模块)或专用限流服务(如 Sentinel)。别指望一个内存变量能管住整个服务网格。
- 开发/测试环境可直接用
rate.Limiter模拟,但上线前必须替换或叠加分布式方案 - 按用户、租户、API Key 等维度限流时,
rate.Limiter无法存 key,必须自己维护 map + sync.RWMutex,但会带来 GC 和锁竞争问题——不如直接走 Redis - burst 值别设超过 QPS × 1.5,例如 QPS=100 时,
burst=150已偏高;若下游 DB 连接池只有 50,burst 再大也白搭
gobreaker.Timeout 和 Interval 容易配反
Timeout 不是熔断持续时间,而是单次调用允许的最大耗时;真正控制 “Open 状态保持多久” 的是 Interval。很多人把 Timeout 设成 30 秒,结果发现熔断器刚打开就立刻切 Half-Open —— 其实是 Interval 被误设成了 1 秒。
典型配置应类似:
st := gobreaker.Settings{
Name: "payment-service",
Timeout: 3 * time.Second, // 调用超 3 秒即失败,计入 ConsecutiveFailures
Interval: 60 * time.Second, // Open 状态至少维持 60 秒,之后才尝试 Half-Open
MaxRequests: 3, // Half-Open 时最多放行 3 个探测请求
ReadyToTrip: func(counts gobreaker.Counts) bool {
return counts.TotalFailures > 5 &&
float64(counts.TotalFailures)/float64(counts.Requests) > 0.4
},
}
-
Interval太短( -
Interval太长(>5min)→ 下游已恢复,你还在拒绝流量 -
MaxRequests别设 1:试探不足;也别设 10:可能压垮刚恢复的服务;3–5 是较稳妥范围
限流和熔断的执行顺序不能错
必须先检查熔断状态,再决定是否进入限流逻辑。如果反过来,在 HTTP 中间件里先 limiter.Wait(ctx),等令牌等到一半才发现下游已熔断,既浪费资源,又拖慢响应。
正确姿势是在发起外部调用前做联合判断:
func CanCall() error {
if cb.State() == gobreaker.StateOpen {
return errors.New("service unavailable")
}
if !limiter.Allow() {
return errors.New("rate limited")
}
return nil
}
- 所有对外依赖(HTTP、gRPC、Redis、MySQL)都必须走同一套
CanCall()封装,避免有的路径绕过熔断 - 限流中间件(如
limitRequests)只该加在网关或 client 层,不该加在 handler 内部业务逻辑上 - health check、metrics、debug 接口必须排除在限流/熔断之外,否则监控自身会失效
状态联动比单独配库更重要
单独扔一个 rate.Limiter 和一个 gobreaker.CircuitBreaker 进去,不联动等于白加。常见反模式是:限流器放行了请求,熔断器却开着,结果请求走到一半失败,令牌白扣、日志混乱、fallback 误触发。
关键点在于状态感知和提前退出:
- 每次调用前查
cb.State(),Open 状态直接返回http.StatusServiceUnavailable,跳过限流等待 - 限流器的
burst值要和熔断器的MaxRequests协调:例如半开时只放 3 个请求,那 burst 就不该设成 100,否则探测请求还没发完,令牌桶就空了 - 监控指标必须暴露
circuit_state{service="xxx"}和requests_total{status="limited"},漏掉任一维度,你就无法判断到底是限流太严,还是熔断误判
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











