当前位置: 首页 > news >正文

学习日报 20250929|缓存击穿及其解决方案

缓存击穿是指热点 key 在缓存过期的瞬间,大量并发请求直接穿透到数据库,导致 DB 压力骤增的问题(例如秒杀活动中某一优惠券的缓存突然过期)。针对该问题,核心解决方案是在缓存失效时,控制对 DB 的并发请求量,常用方案如下:

方案 1:互斥锁(分布式锁)

原理:缓存失效时,只有一个线程能获取锁并查询 DB,其他线程等待重试,避免大量请求直击 DB。适用场景:并发量高、热点数据更新不频繁的场景(如优惠券秒杀)。

代码示例(基于 Redis 分布式锁)
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;@Service
public class CouponSeckillService {private final RedisTemplate<String, Object> redisTemplate;private final CouponDao couponDao; // 数据库访问层// 分布式锁前缀private static final String LOCK_PREFIX = "lock:coupon:";// 锁超时时间(避免死锁,需大于DB查询耗时)private static final long LOCK_EXPIRE = 5000; // 5秒// 重试间隔(单位:毫秒)private static final long RETRY_INTERVAL = 100;public CouponSeckillService(RedisTemplate<String, Object> redisTemplate, CouponDao couponDao) {this.redisTemplate = redisTemplate;this.couponDao = couponDao;}/*** 查询秒杀优惠券信息(解决缓存击穿)*/public Coupon getSeckillCoupon(Long couponId) {String cacheKey = "coupon:seckill:" + couponId;// 1. 先查缓存Coupon coupon = (Coupon) redisTemplate.opsForValue().get(cacheKey);if (coupon != null) {return coupon; // 缓存命中,直接返回}// 2. 缓存失效,尝试获取分布式锁String lockKey = LOCK_PREFIX + couponId;boolean locked = false;try {// 尝试获取锁(setIfAbsent:原子操作,避免并发问题)locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", LOCK_EXPIRE, TimeUnit.MILLISECONDS);if (locked) {// 3. 成功获取锁,查询DBcoupon = couponDao.queryById(couponId);if (coupon != null) {// 4. 从DB查到数据,回写缓存(设置随机过期时间,避免再次同时失效)int expireMinutes = 30 + (int) (Math.random() * 25); // 30-55分钟随机redisTemplate.opsForValue().set(cacheKey, coupon, expireMinutes, TimeUnit.MINUTES);} else {// 5. DB中不存在,设置空值缓存(短期过期,避免缓存穿透)redisTemplate.opsForValue().set(cacheKey, null, 5, TimeUnit.MINUTES);}return coupon;} else {// 6. 未获取到锁,休眠后重试(控制并发)Thread.sleep(RETRY_INTERVAL);return getSeckillCoupon(couponId); // 递归重试}} catch (InterruptedException e) {Thread.currentThread().interrupt();return null;} finally {// 7. 释放锁(确保锁是当前线程持有,避免误删)if (locked) {String unlockScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";redisTemplate.execute(new DefaultRedisScript<>(unlockScript, Integer.class),Collections.singletonList(lockKey),"1" // 与加锁时的value一致);}}}
}

方案 2:热点数据永不过期 + 异步更新

原理

  • 缓存不设置过期时间(逻辑上永不过期),避免因过期导致的击穿。
  • 后台启动定时任务,定期从 DB 更新缓存数据,保证数据一致性。

适用场景:热点数据实时性要求不高(如优惠券基本信息,非库存)。

代码示例
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.TimeUnit;@Service
public class HotCouponService {private final RedisTemplate<String, Object> redisTemplate;private final CouponDao couponDao;// 热点优惠券缓存key前缀(不设置过期时间)private static final String HOT_COUPON_KEY = "coupon:hot:";public HotCouponService(RedisTemplate<String, Object> redisTemplate, CouponDao couponDao) {this.redisTemplate = redisTemplate;this.couponDao = couponDao;}/*** 查询热点优惠券(缓存永不过期)*/public Coupon getHotCoupon(Long couponId) {String cacheKey = HOT_COUPON_KEY + couponId;Coupon coupon = (Coupon) redisTemplate.opsForValue().get(cacheKey);if (coupon == null) {// 缓存未命中(首次加载),直接查DB并写入缓存(无过期时间)coupon = couponDao.queryById(couponId);if (coupon != null) {redisTemplate.opsForValue().set(cacheKey, coupon); // 不设置过期时间}}return coupon;}/*** 定时任务:每30分钟更新热点优惠券缓存(异步更新,避免穿透)*/@Scheduled(fixedRate = 30 * 60 * 1000) // 30分钟执行一次public void refreshHotCouponCache() {// 查询所有热点优惠券ID(可从配置或DB获取)List<Long> hotCouponIds = couponDao.queryHotCouponIds();for (Long id : hotCouponIds) {Coupon latestCoupon = couponDao.queryById(id);if (latestCoupon != null) {redisTemplate.opsForValue().set(HOT_COUPON_KEY + id, latestCoupon);}}}
}

方案 3:熔断降级(临时返回默认值)

原理:缓存失效时,通过熔断机制直接返回默认值(如 “活动太火爆,请稍后再试”),不查询 DB,保护数据库。适用场景:非核心流程、允许临时返回降级结果的场景。

代码示例(基于 Guava RateLimiter 简单降级)
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;@Service
public class CouponDegradeService {private final RedisTemplate<String, Object> redisTemplate;private final CouponDao couponDao;// 限流工具(控制DB查询QPS)private final RateLimiter rateLimiter = RateLimiter.create(10); // 允许每秒10次DB查询private static final String COUPON_KEY = "coupon:info:";public CouponDegradeService(RedisTemplate<String, Object> redisTemplate, CouponDao couponDao) {this.redisTemplate = redisTemplate;this.couponDao = couponDao;}/*** 查询优惠券(熔断降级策略)*/public Coupon getCouponWithDegrade(Long couponId) {String cacheKey = COUPON_KEY + couponId;Coupon coupon = (Coupon) redisTemplate.opsForValue().get(cacheKey);if (coupon != null) {return coupon; // 缓存命中}// 缓存失效,尝试获取令牌(控制DB访问)if (rateLimiter.tryAcquire()) {// 获得令牌,查询DB并更新缓存coupon = couponDao.queryById(couponId);if (coupon != null) {redisTemplate.opsForValue().set(cacheKey, coupon, 30, TimeUnit.MINUTES);}return coupon;} else {// 未获得令牌,返回降级结果return new Coupon() {{setId(couponId);setMessage("活动太火爆,请稍后再试"); // 降级提示}};}}
}

方案对比与选择

方案优点缺点适用场景
分布式锁数据一致性高,适用范围广加锁 / 解锁有性能损耗,可能有死锁风险高并发、数据实时性要求高
永不过期 + 异步更新无锁竞争,性能好数据可能有延迟,需维护定时任务实时性要求低的热点数据
熔断降级实现简单,保护 DB 效果好用户体验可能受影响(返回降级结果)非核心流程、允许临时降级

实际业务中,可结合场景组合使用(例如:分布式锁 + 随机过期时间,既防击穿也防雪崩)

http://www.dtcms.com/a/424843.html

相关文章:

  • Dify 源码本地部署启动及完整步骤解析
  • 有效的字母异位词(二)
  • 简单大气食品农业网站源码站长如何做视频类网站
  • 滕州网站建设 助企网络公司管理系统怎么写
  • 做网站为什么用php网站建设遇到哪些危险
  • 基于扩散模型的任意尺度磁共振图像超分辨率重建:通过渐进式k空间重建与去噪实现|文献速递-文献分享
  • RT调度器
  • 网站生成工具百度域名多少钱
  • 网站移动端是什么问题网站开发属于商标哪个类别
  • 教师做课题可以参考什么网站建设银行网站上的的研究报告
  • 数据库事务中的脏读、不可重复读、幻读
  • 网站的绝对路径怎么做西安站
  • NuttX 实现细节指南
  • 苏州建行网站首页程序员和网站建设
  • 四川住房城乡和城乡建设厅网站网页翻译怎么弄
  • 做小型企业网站多少钱中国机械采购平台
  • 建设中专网站html网站开发图片素材
  • 第四部分:VTK常用类详解(第117章 vtkTubeFilter管状过滤器类)
  • 宁波建设集团股份有限公司招聘宁波网络关键词优化费用
  • 西安开发网站建设交通运输部:全力保障交通网络畅通
  • C语言入门教程 | 第六讲:指针详解 - 揭开C语言最神秘的面纱
  • 蓝桥杯嵌入式2——串口的使用
  • 对象创建流程
  • 如何提高网站流量和转化
  • 如何删除网站黑链望野王绩拼音
  • 做国外有那些网站著名设计公司排名
  • 企业网站管理系统模版源码一对一直播交友app开发
  • 【完整源码+数据集+部署教程】棉花产量预测分割系统: yolov8-seg-bifpn
  • 淘宝客网站域名怎么制作wap网站
  • 网站常用后台路径影视广告公司宣传片