缓存三剑客解决方案
缓存三剑客解决方案
1.缓存雪崩
定义:大量缓存数据在同一时间点集体失效,导致所有请求直接穿透到数据库,引发数据库瞬时高负载甚至崩溃。
解决方案:设置过期随机值,避免大量缓存同时失效
// 缓存雪崩防护:随机过期时间 + 双层缓存// 设置随机过期时间(基础时间 + 随机偏移)Random random = new Random();long expire = baseExpire + random.nextInt(5 * 60 * 1000); // 基础5分钟 + 随机5分钟内data = loader.load();setCache(key, data, expire);setCache(backupKey, data, expire * 2); // 备份缓存过期时间更长return data;
}
2. 缓存击穿解决方案
定义:某个热点Key突然失效(如过期或被删除),同时有大量并发请求访问该Key,导致请求全部穿透到数据库。
方案1:互斥锁(分布式锁)
@Nullable// todo 3、缓存击穿 -> 互斥锁:只能由一个线程进行缓存构建,其他线程等待,吞吐量较低private Shop huchi(Long id) {String shopJsonStr = stringRedisTemplate.opsForValue().get("cache:shop:" + id);if (StrUtil.isNotBlank(shopJsonStr)) {return JSONUtil.toBean(shopJsonStr, Shop.class);}// 未命中获取锁String tryLockKey = "cache:shop:lock:" + id;Shop shop = null;try {boolean tryLock = getLock(tryLockKey);// 未命中:不断休眠直至获取成功while (!tryLock) {Thread.sleep(50);tryLock = getLock(tryLockKey);}// 获取互斥锁,进行缓存的构建shop = getById(id);if (shop == null) {// 数据库中也不存在时候,进行空字符串缓存stringRedisTemplate.opsForValue().set("cache:shop:" + id, "", 2, TimeUnit.MINUTES);return null;}stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop), 2, TimeUnit.MINUTES);} catch (Exception e) {e.getStackTrace();} finally {unLock(tryLockKey);}return shop;}
方案2:逻辑过期(适合高并发读场景)
@Nullable// todo 3、缓存击穿 -> 逻辑过期:通过设置逻辑过期时间,然后判断是否过期来确定是否进行缓存更新private Shop exLogical(Long id) {ExecutorService executorService = Executors.newFixedThreadPool(10);String shopJsonStr = stringRedisTemplate.opsForValue().get("cache:shop:" + id);// 如果不存在那就是一定不存在if (StrUtil.isBlank(shopJsonStr)) {return null;}//RedisDate redisDate = JSONUtil.toBean(shopJsonStr, RedisDate.class);Shop shop = JSONUtil.toBean((JSONObject) redisDate.getObject(), Shop.class);// 未逻辑过期if (redisDate.getEx().isAfter(LocalDateTime.now())) {return shop;}// 逻辑过期// 缓存重建String tryLockKey = "cache:shop:lock:" + id;boolean tryLock = getLock(tryLockKey);if (tryLock) {// 开启独立的线程去独立的进行缓存executorService.submit(() -> {try {this.saveShopRedis(id, 20L);} finally {unLock(tryLockKey);}});}return shop;}// 手动设置逻辑过期时间private void saveShopRedis(Long id, Long ex) {Shop shop = getById(id);RedisDate redisDate = new RedisDate();redisDate.setEx(LocalDateTime.now().plusSeconds(ex));redisDate.setObject(shop);stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(redisDate));}
3. 缓存穿透解决方案
定义:查询数据库中根本不存在的数据(如非法ID或恶意攻击),导致请求绕过缓存直接访问数据库。
方案1:缓存空对象
@Nullable// todo 1、解决缓存穿透问题private Shop chaungtou(Long id) {// 缓存穿透解决方案 -> 缓存""空字符冲String shopJsonStr = stringRedisTemplate.opsForValue().get("cache:shop:" + id);if (StrUtil.isNotBlank(shopJsonStr)) {return JSONUtil.toBean(shopJsonStr, Shop.class);}// shopJsonStr == "":代表用户访问的是一个数据库中不存在的数据if (shopJsonStr != null) {// 店铺不存在return null;}Shop shop = getById(id);if (shop == null) {// 数据库中也不存在时候,进行空字符串缓存stringRedisTemplate.opsForValue().set("cache:shop:" + id, "", 2, TimeUnit.MINUTES);return null;}stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop), 2, TimeUnit.MINUTES);return shop;}