限流器必须全局复用并按ip+path分桶,熔断器只对明确失败计数且需与限流协同;健康检查需绕过二者,sentinel-go规则须显式加载且resource严格匹配。

标准库 rate.Limiter + gobreaker 组合就能稳住大多数 Go 服务,但顺序、复用和状态协同错了,防护就形同虚设。
限流器必须全局复用,且按 IP+Path 分桶
把 rate.NewLimiter 放在 handler 里每次新建,等于没限——每个请求拿到的都是全新令牌桶,burst 被无限重置。真正的做法是:提前初始化一个 map 或 sync.Map,key 为 hash(ip + path),value 是复用的 *rate.Limiter 实例。
- 提取真实 IP 要考虑反向代理:
net.ParseIP(r.Header.Get("X-Forwarded-For"))需配合可信跳数校验,否则可被伪造 - 别用纯 IP 做 key:否则
/login被刷爆,整个 IP 的/healthz也会被卡住 -
rate.Every(200 * time.Millisecond)比rate.Limit(5)更不易错——后者容易误读成“每秒 5 次”,实际是“每 200ms 1 次” - 务必用
limiter.Wait(ctx),而非Allow():前者会阻塞并尊重 context timeout;后者非阻塞,高并发下可能瞬间打穿阈值
熔断器不能无差别包装所有调用
gobreaker 是状态机,不是开关。它只对明确失败负责,比如 HTTP 5xx、连接超时、或业务返回的特定错误码(如 status == 503),而不是对所有 err != nil 计数。
- 别把整个 handler 包进
cb.Execute:数据库查询、本地缓存这类低风险操作没必要熔断,反而增加延迟和误判 -
MaxRequests: 3是半开试探请求数,不是熔断阈值;真正触发熔断靠ReadyToTrip函数,例如counts.ConsecutiveFailures > 5 -
Timeout是熔断打开后持续时间,至少设 30 秒;太短会导致反复开闭,加剧下游压力 - 半开状态下的第一次试探请求,必须带
context.WithTimeout,否则试探本身可能拖垮恢复流程
限流必须在熔断之前执行
如果先熔断再限流,熔断打开后所有请求走 fallback,限流器收不到真实流量,IP 统计停摆;等熔断关闭瞬间,积压请求全涌进来,直接击穿。
- 正确链路:HTTP 中间件 → 限流(放行/拒绝)→ 路由匹配 → 对目标 handler 做熔断包装
- 健康检查接口(如
/healthz)必须绕过限流和熔断中间件,否则 K8s 会因探测失败不断重启 Pod - 当
cb.State() == gobreaker.StateOpen时,限流逻辑应主动返回http.StatusServiceUnavailable,避免令牌被无效占用 - 不要在
cb.Execute外部手动调用OnSuccess/OnError:这会绕过内部计数逻辑,导致状态判断失准
sentinel-go 的规则加载和埋点极易失效
sentinel-go 的限流和熔断规则默认都不生效——必须显式调用对应加载函数,且 resource 名称大小写、traffic type、参数完整性缺一不可。
-
flow.LoadRules和circuitbreaker.LoadRules是两个独立函数,混用或漏掉任一,对应机制就静默失效 - 规则里的
resource字符串必须和sentinel.Entry("xxx")完全一致,包括大小写和路径拼写 - 熔断策略如
ErrorRatio必须设置MinRequestAmount和StatIntervalInMs,否则规则校验失败被跳过 - 每次
Entry后必须defer e.Exit():不退出会导致统计指标错乱,后续决策完全失真
最常被忽略的不是代码怎么写,而是限流与熔断之间的状态感知——它们不是两道独立的门,而是一套联动的闸机。没打通 cb.State() 和限流逻辑,或者没让监控指标真正反映决策依据,防护就只剩个壳。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











