redis集群宕机后,redis.get()直接抛异常而非返回null,是因为客户端(如jedis)在连接断开或节点不可达时默认抛出jedisconnectionexception等异常;若业务代码用try-catch捕获后再查库,会在故障瞬间引发大量线程阻塞于db连接池,加剧雪崩。

Redis集群宕机后,redis.get() 为什么直接抛异常而不是返回 null
因为 Redis 客户端(如 Jedis、Lettuce)在连接断开或节点不可达时,默认行为是抛出 JedisConnectionException 或 RedisCommandTimeoutException,而不是静默返回 null。业务代码若写成“没拿到就查库”,就会在集群不可用的瞬间,所有线程同时进入数据库查询分支。
常见错误现象:
- 日志里大量出现
Cannot get Jedis connection或NOAUTH Authentication required(其实是连不上,但鉴权错误被误报) - 数据库监控显示 QPS 瞬间从 800 跳到 12000,连接池满,
wait_timeout告警频发 - 接口平均耗时从 20ms 暴涨到 2s+,且超时集中在
userMapper.selectById这类 DAO 方法
关键点:哨兵(Sentinel)只管服务发现,不改客户端行为。它切换主从要 2–10 秒,这期间所有请求照样失败——你不能指望 redisTemplate.opsForValue().get() 在网络不通时自动兜底。
为什么 try-catch 包住 redis.get() 再查库是危险操作
这不是防御,是放大雪崩。异常捕获发生在调用已失败之后,此时线程已在等待 DB 连接;并发高时,几百个线程卡在 dataSource.getConnection() 上,把连接池彻底锁死。
正确做法是前置拦截,而非事后补救:
- 用
Sentinel或Resilience4j监控redis.get()的失败率和 P99 延迟,阈值设为失败率 > 40% 或延迟 > 300ms - 熔断开启后,直接跳过 Redis 和 DB,走本地缓存或静态兜底值:
cache.getIfPresent(key)或fallbackUserMap.get(key) - 绝不允许降级逻辑里再触发任何远程调用(包括查备库、调下游 HTTP 接口)
注意:熔断器必须按 key 前缀隔离,比如 "user:*" 熔断不影响 "dict:status",否则配置类数据也失效,系统连基础枚举都渲染不出来。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
本地缓存(Caffeine)怎么配才不和 Redis 冲突
本地缓存不是 Redis 的镜像,而是故障时的缓冲垫。配错会引发脏数据或穿透加剧。
参数差异与陷阱:
-
expireAfterWrite(5, TimeUnit.MINUTES):本地缓存过期时间必须显著短于 Redis(如 Redis 是 1h,本地最多设 5min),否则 Redis 恢复后,本地还挂着旧值,用户看到的是“昨天的价格” -
maximumSize(10000):别盲目设大,内存占用会随 key 数量线性增长;建议按业务维度分 cache loader,比如userCache、orderCache单独控制 - 空值必须缓存:
cache.put(key, null),但过期时间设成 60 秒,防止恶意刷不存在的 user_id 导致本地缓存膨胀 - 回源必须异步:
executor.submit(() -> loadFromDBAndRefreshBoth(key)),主流程 return 本地值,绝不等
性能影响:Caffeine 是堆内缓存,无序列化开销,单机 QPS 可达 10w+;但要注意 GC 压力——如果 value 是大对象(如含图片 base64 的用户详情),需用 weakValues() 或手动清理。
集群恢复后,冷启动阶段如何避免二次雪崩
Redis 集群恢复≠缓存可用。此时 Redis 是空的,第一个请求查库成功后写入 Redis,但第 2–100 个并发请求仍会全部打库——这就是“重建雪崩”。
预热不是可选项,是上线前必做动作:
- 运维重启集群前,用脚本跑
redis-cli --scan --pattern "user:*" | xargs -I{} redis-cli get {}检查 key 是否还在;不在就触发预热任务 - 预热脚本应调用业务接口(如
/api/v1/user/preheat?ids=1001,1002),由服务端统一走loadFromDBAndRefreshBoth(),保证两级缓存一致 - 预热 key 的过期时间要加随机偏移:
ttl = 3600 + ThreadLocalRandom.current().nextInt(0, 600),避免预热完又集体过期
最容易被忽略的一点:预热必须覆盖“最近 1 小时访问 Top 1000 的 key”,而不是随便挑 1000 个。冷启动压垮数据库的从来不是总量,而是那几个高频 key 的并发重建。










