1. 什么是缓存雪崩?真实场景的具象化理解
某电商平台凌晨0点开启双11秒杀活动,后台系统为减轻数据库压力,将商品库存数据缓存在Redis中,所有缓存Key都设置了1小时固定过期时间。当0:55分时,100万用户同时刷新页面,此时缓存集群中80%的Key同时失效,导致海量请求直接穿透到数据库,MySQL连接池瞬间被打满,整个服务瘫痪。
这种因大量缓存集中失效导致数据库压力激增的现象,就是我们常说的「缓存雪崩」。就像高峰期的地铁站突然所有闸机同时故障,导致乘客全部涌向人工通道。
2. 应急工具箱
2.1 随机过期时间方案(Redis+SpringBoot)
// 商品服务缓存设置示例
public void setProductCache(String productId, Product product) {
// 基础过期时间30分钟
int baseExpire = 1800;
// 随机浮动范围±300秒(5分钟)
int randomRange = new Random().nextInt(600) - 300;
// 最终过期时间计算
int finalExpire = baseExpire + randomRange;
redisTemplate.opsForValue().set(
"product:" + productId,
product,
finalExpire,
TimeUnit.SECONDS
);
}
关键技术点说明:
- 基础过期时间设定业务容忍的最低缓存周期
- 随机浮动范围建议控制在基础时间的10%-20%
- 使用ThreadLocalRandom替代Random可获得更好性能
2.2 热点数据永不过期方案(Redis+Lua)
-- 商品库存查询脚本
local key = KEYS[1]
local stock = redis.call('GET', key)
if not stock then
-- 从数据库加载数据
stock = loadFromDB(key)
-- 设置永久缓存(实际可设较长过期时间)
redis.call('SET', key, stock)
end
return stock
-- 定时更新脚本(每小时执行)
local keys = redis.call('KEYS', 'product:*')
for _,k in ipairs(keys) do
local newStock = getLatestStockFromDB(k)
redis.call('SET', k, newStock)
end
方案特点:
- 消除集中过期风险
- 需要配套异步更新机制
- 建议对冷数据设置兜底过期时间
3. 熔断降级(Sentinel+Redis)
// 基于Sentinel的熔断降级配置
@SentinelResource(
value = "productQuery",
fallback = "queryProductFallback",
blockHandler = "queryProductBlock",
exceptionsToIgnore = {IllegalArgumentException.class}
)
public Product queryProduct(String productId) {
// 正常业务逻辑
}
// 降级方法(返回兜底数据)
private Product queryProductFallback(String productId, Throwable ex) {
return new Product().setDefaultData();
}
// 流量控制规则配置
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("productQuery");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(500); // 阈值设为500QPS
rules.add(rule);
FlowRuleManager.loadRules(rules);
熔断策略组合:
- 慢调用比例(>800ms请求占比超50%)
- 异常比例(错误率超过60%)
- 异常数(5分钟内异常超过100次)
4. 多级缓存架构设计(Redis+本地缓存)
// 多级缓存加载逻辑示例
public Product getProduct(String productId) {
// 第一级:本地缓存
Product product = localCache.get(productId);
if (product != null) return product;
// 第二级:分布式锁防击穿
RLock lock = redisson.getLock("lock:" + productId);
try {
lock.lock();
// 双重检查
product = localCache.get(productId);
if (product != null) return product;
// 第三级:Redis缓存
product = redisTemplate.opsForValue().get("product:" + productId);
if (product != null) {
localCache.put(productId, product);
return product;
}
// 终极回源:数据库查询
product = db.queryProduct(productId);
redisTemplate.opsForValue().set("product:"+productId, product, 30, TimeUnit.MINUTES);
localCache.put(productId, product);
return product;
} finally {
lock.unlock();
}
}
多级缓存注意事项:
- 本地缓存建议使用Caffeine或Guava Cache
- 需要处理缓存一致性问题
- 建议设置本地缓存上限
5. 灾后快速恢复手册
步骤一:服务降级 立即启用静态兜底数据,暂时关闭非核心功能:
location /product {
# 正常服务路径
proxy_pass http://backend;
# 熔断时切换
error_page 502 503 504 = @fallback;
}
location @fallback {
root /static/fallback;
try_files /product_default.html =404;
}
步骤二:渐进式重建 使用限流工具逐步重建缓存:
# 使用redis-cli批量设置过期时间
redis-cli -h 127.0.0.1 -p 6379 --scan --pattern 'product:*' | \
xargs -I{} redis-cli -h 127.0.0.1 -p 6379 EXPIRE {} 3600
# 使用管道加速写入
cat product_data.txt | redis-cli --pipe
6. 应用场景与技术选型
典型应用场景:
- 电商大促活动
- 新闻热点事件
- 定时任务触发的缓存刷新
- 集群批量重启场景
技术方案对比:
方案 | 响应延迟 | 实现复杂度 | 数据一致性 | 适用场景 |
---|---|---|---|---|
随机过期时间 | 低 | 简单 | 最终一致 | 常规业务场景 |
永不过期+异步更新 | 最低 | 复杂 | 强一致 | 高频访问热点数据 |
多级缓存 | 极低 | 较复杂 | 最终一致 | 高并发读场景 |
7. 经验总结与避坑指南
监控指标三要素:
- 缓存命中率低于90%时触发预警
- 数据库QPS突增50%立即告警
- Redis集群连接数使用率超过75%扩容
容量规划黄金法则:
所需Redis内存 = (单品缓存大小 × 日均UV × 缓存周期天数) × 安全系数(1.5)
避坑实践:
- 避免使用KEYS命令扫描大数据量
- 批量操作使用pipeline提升10倍性能
- 集群模式每个分片保留15%-20%内存余量