Resilience4j默认限流与熔断状态仅存于内存,多实例下必须通过Redis实现分布式共享,否则导致限流失效、熔断形同虚设及雪崩风险;需用Lua脚本原子操作Redis令牌桶,并同步熔断状态与超时降级策略。

Resilience4j 本身不直接依赖 Redis,所有限流、熔断状态都默认存在内存里;要防雪崩,必须让多个客户端实例共享同一套限流/熔断状态,否则每个实例各自为政,流量照样打穿下游——所以关键不是“用不用 Redis”,而是“怎么把 Resilience4j 的状态持久化或同步到 Redis”。
为什么 RateLimiterConfig 默认配置在集群下会失效
Resilience4j 的 RateLimiter 默认使用内存计数器,limitForPeriod 和 limitRefreshPeriod 只对单个 JVM 生效。当服务部署多实例(比如 4 个 Pod),每个实例都允许每秒 10 个请求,实际入口流量就可能是 40 QPS,远超下游服务承受能力。
- 现象:压测时单实例限流正常,上线后监控显示下游 502/超时陡增,但网关层限流日志没触发拒绝
- 根本原因:
RateLimiter实例未共享,Redis 没被接入限流路径 - 误区:以为引入
resilience4j-ratelimiter就自动分布式了——其实它只是“支持插拔存储”,不配就是本地内存
必须自己实现 RateLimiterRegistry + RedisBackend
Resilience4j 提供了 RateLimiterRegistry 接口和 RateLimiterConfig,但不提供开箱即用的 Redis 实现。你需要手动桥接:用 Lua 脚本操作 Redis 令牌桶,再包装成 RateLimiter 实例注册进 Registry。
- 核心动作:重写
RateLimiter的acquirePermission方法,底层调用redis.eval(…)执行原子扣令牌脚本 - Key 设计要带业务维度:
ratelimit:order-service:{userId}或ratelimit:payment-api:global - 别直接用
StringRedisTemplate做 if-else 判断——高并发下必然超发,必须靠 Lua 保证读-算-写原子性 - 示例关键片段:
String script = "local tokens = redis.call('hget', KEYS[1], 'tokens') ...";脚本里要处理last_time、tokens、时间衰减、容量上限三者关系
熔断状态也得存 Redis,否则半开探测会误判
CircuitBreaker 的状态机(Closed/Open/Half-Open)默认存在堆内存。多实例下,某个实例因下游异常进入 Open 状态,其他实例仍处于 Closed,继续发请求,等于熔断形同虚设。
- 后果:下游已瘫痪,但 3/4 的流量还在重试,加剧雪崩
- 解决方案:用 Redis Hash 存状态,例如
circuitbreaker:payment-service:state,字段含status、failureCount、lastModified - 注意
transitionToHalfOpenState()必须是分布式锁+条件更新,避免多个实例同时切 Half-Open 导致探测请求翻倍 - 不要忽略
onSuccess/onError回调里的 Redis 写操作——它们得和状态变更强一致,建议用 pipeline 批量提交
最容易被忽略的点:超时与降级必须协同 Redis 限流
只做限流或只做熔断都不够。真实雪崩常由“慢请求堆积→线程耗尽→新请求排队→超时激增→熔断来不及触发”链式引发。
- 必须配
TimeLimiter:给每个外部调用设硬超时,比如timeLimiterConfig.timeoutDuration(Duration.ofSeconds(2)) - 降级逻辑不能只返回 mock 数据:要检查 Redis 中当前
RateLimiter的剩余令牌数,若已接近 0,直接走兜底缓存或空响应,别再触发任何下游 - 监控埋点要拉通:
rate_limiter_available_permissions(Redis 查得)、circuit_breaker_state(Redis 读得)、time_limiter_failed_calls三个指标必须同源采集,否则看不出协同效果


















