rate.limiter只适合单机限流,因其基于内存原子操作,无法跨进程同步状态,集群下各实例独立计数导致总流量失控;重启后burst清空引发毛刺;且不支持按用户等维度的差异化限流,集群场景需redis+lua实现。

rate.Limiter 为什么只适合单机限流
rate.Limiter 基于内存状态实现令牌桶,每次调用 Allow() 或 Wait() 都在本地 goroutine 安全地操作原子计数器。它不依赖外部存储,开销极低,但这也意味着:不同进程、不同机器上的限流器完全独立,无法协同统计总流量。
- 集群场景下,5 台服务各配
rate.NewLimiter(100, 20),实际峰值可达 500 QPS,远超预期阈值 - 重启后令牌桶清空,burst 容量丢失,可能引发瞬间毛刺
- 无法做跨接口/跨用户维度的差异化限流(比如按
user_id限流),因为没地方存 key
真要集群限流,得换 Redis + Lua(如 redis-cell 模块)或专用限流服务,rate.Limiter 只能当单机兜底或开发环境模拟用。
gobreaker 的 Timeout 和 Interval 容易搞混
很多人把 Timeout 当成“熔断保持打开的时间”,其实不是:Timeout 是单次调用允许的最大耗时(超时即计入失败);真正控制“Open 状态持续多久”的是 Interval 参数。
-
Timeout: 5 * time.Second→ 调用超过 5 秒就报错,并累加ConsecutiveFailures -
Interval: 30 * time.Second→ Open 状态会持续 30 秒,之后自动进入 HalfOpen -
MaxRequests: 3→ HalfOpen 期间最多放行 3 个请求,全成功才切回 Closed
如果 Interval 设太短(比如 1s),熔断器反复开合,起不到隔离作用;设太长(比如 10min),下游恢复了你也卡在 Open 里——得结合下游平均恢复时间调优。
熔断和限流谁该先执行
顺序错了会放大风险:必须先熔断,再限流。原因很直接——限流是防过载,熔断是防雪崩;一个故障服务响应慢,限流器还在拼命放请求进去,只会更快拖垮它。
- HTTP 中间件链里,
RateLimitMiddleware放CircuitBreakerMiddleware后面,等于“先确认下游还活着,再决定给多少流量” - 对下游服务的调用(如
http.Get)必须包裹在cb.Execute()里,不能只给 handler 加限流 - 别在 handler 里对本服务逻辑做熔断——那是 panic 捕获或业务校验的事,
gobreaker只该出现在明确的外部依赖点
真实压测中见过太多案例:限流放在最外层,结果所有请求都卡在熔断器半开探测上,连接池打满,整个服务不可用。
自定义 ReadyToTrip 时别只看连续失败
用 counts.ConsecutiveFailures > N 很直观,但生产环境容易误熔断。比如网络抖动导致几秒内连续超时,但服务本身健康。
- 更稳的做法是结合错误率:
float64(counts.TotalFailures) / float64(counts.Requests) > 0.6 - 再叠加时间窗口:
time.Since(cb.lastFailureTime) ,避免历史失败干扰当前判断 - 如果下游有重试逻辑,记得在
ReadyToTrip里排除重试请求(比如只统计首次调用失败)
熔断阈值不是越严越好,关键是让失败信号足够“干净”。一次误熔断带来的用户体验损失,往往比多扛几秒负载更难修复。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











