Redis本身不提供熔断能力,熔断降级需在应用层实现;应结合缓存空值+随机TTL、连接池治理及按业务维度隔离的熔断器协同防御缓存击穿。

Redis 本身不提供熔断能力,防穿透和熔断必须靠上层框架(如 Sentinel)或业务代码主动判断;Redis 只能做分布式计数、状态标记、信号存储——它只是“信号板”,不是“决策者”。
为什么 Redis 不能直接熔断
Redis 是无状态的键值存储,没有内置的失败统计、状态机、半开探测或自动切换逻辑。所谓“Redis 熔断”,本质是应用层用 Redis 存数据(比如 fail_count:order-service)、读数据(比如 sentinel:state:order-service),再由 Sentinel 或自定义逻辑做判断和降级。常见误解是把 Redis 当成熔断器,其实它只承担两个角色:
- 共享故障计数器:多个实例写入同一 key(如用
INCR+EXPIRE统计 1 分钟内异常次数) - 全局开关存储:用
SETNX写入circuit_breaker:order-service表示已熔断,所有节点读这个 key 决定是否跳过调用
如果直接在 Redis 里写 Lua 脚本模拟熔断状态机,会因脚本不可中断、无法跨 key 原子更新、缺乏时序控制而极易出错——这不是设计用途。
Sentinel 怎么结合 Redis 做防穿透熔断
Sentinel 的 @SentinelResource 注解本身不依赖 Redis,但你可以用 Redis 作为其外部状态源,实现“跨实例一致的熔断判定”。关键不在注解,而在 blockHandler 和 fallback 里的配合逻辑:
- 在
handleBlock方法里,不只是返回兜底值,还要调用redisTemplate.opsForValue().increment("fail_count:" + resource)记录本次被限流/失败的请求 - 另起一个定时任务(或用 Spring Scheduler),每 10 秒扫描所有
fail_count:*,对连续 5 次超阈值(如 1 分钟失败 ≥ 20 次)的服务,执行redisTemplate.opsForValue().set("circuit_breaker:" + resource, "OPEN", 60, TimeUnit.SECONDS) - 在业务方法入口加一层 AOP 切面,先查
redisTemplate.opsForValue().get("circuit_breaker:" + resource),若为"OPEN"直接 throwBlockException,触发 Sentinel 的 fallback 流程 - 不要把熔断状态存在本地内存(
ConcurrentHashMap),否则多实例间不同步;Redis 是唯一能保证“所有节点看到同一熔断状态”的共享媒介
防穿透:Redis 在缓存击穿场景下的真实作用
“防穿透”常被误认为是熔断子集,其实它是独立问题:恶意请求查大量不存在的 user:id=999999999,导致缓存 miss、DB 压力飙升。Redis 在这里不是挡刀的,而是配合布隆过滤器或空值缓存做“前置过滤”:
- 布隆过滤器(Bloom Filter)部署在网关或应用内存中,快速判断 ID 是否“可能不存在”;若返回 false,直接拒绝,不碰 Redis
- 若布隆说“可能存在”,再查 Redis —— 若 key 不存在,**必须写入一个短 TTL 的空值**,如
SET user:999999999 "" EX 60,防止重复穿透 - 注意:空值不能永久缓存,否则脏数据无法更新;也不能 TTL 过短(如 1s),否则起不到防护作用;60s 是较稳妥的折中值
- 若用 Redisson 的
RLocalCachedMap,记得关闭writeMode = WRITE_THROUGH,否则空值会同步写 DB,反而加重穿透
最容易被忽略的三个点
第一,Sentinel 的熔断统计默认只看本实例的异常,不做集群聚合 —— 如果你有 10 个订单服务实例,每个都报 3 次 DB 超时,Sentinel 不会自动合并成“总失败 30 次”,必须靠 Redis 中央计数器补足;
第二,Redis 故障时,circuit_breaker:* key 读不到,应默认走“CLOSE”态(即放行),而不是卡死或抛 NPE —— 否则 Redis 挂了,熔断器也跟着瘫痪;
第三,空值缓存的 key 命名必须带业务前缀且可预测,否则布隆过滤器无法预判,例如 user:id=123 和 user:123 是两个 key,过滤器校验的格式必须和实际 Redis 查询完全一致。


















