缓存击穿
- 互斥锁解决:
/**
* 互斥锁解决缓存击穿
*/
private Boolean tryLock(String key){
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
private void unlock(String key){
stringRedisTemplate.delete(key);
}
public Shop queryWithMutex(Long id){
// 2.可能存在,从Redis中查询id
String cacheShopKey = CACHE_SHOP_KEY + id;
String shopJson = stringRedisTemplate.opsForValue().get(cacheShopKey);
if (StrUtil.isNotBlank(shopJson)) {
// 2.1命中返回商铺信息
return JSONUtil.toBean(shopJson, Shop.class);
}
Shop shop = null;
String lockKey = LOCK_SHOP_KEY + id;
Boolean isLock = tryLock(lockKey);
try {
// 3.未命中,尝试获取锁
if (!isLock) {
// 3.1.未获取到锁,休眠递归
Thread.sleep(50);
return queryWithMutex(id);
}
// 3.2.1.获取到锁,根据id查数据库
shop = getById(id);
Thread.sleep(200);
// 3.2.2.商铺不在数据库,返回错误信息
if(shop == null){
return null;
}
// 3.2.3.商铺在数据库,写入Redis
stringRedisTemplate.opsForValue().set(cacheShopKey, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 4.释放互斥锁
unlock(lockKey);
}
// 5.返回数据
return shop;
}
/**
* 根据id查询店铺信息
* @param id
* @return
*/
@Override
public Result queryById(Long id) {
// 1.布隆过滤器解决缓存穿透
if (!querySolvingPassThrough(id)) {
return Result.fail("商铺不存在!");
}
// 2.互斥锁解决缓存击穿
Shop shop = queryWithMutex(id);
if (shop == null) {
return Result.fail("商铺不存在!");
}
return Result.ok(shop);
}
压力测试:
结果:1000个线程并发访问,只查询数据库一次
- 逻辑过期:
// 预热
@SpringBootTest
class HmDianPingApplicationTests {
@Autowired
private ShopServiceImpl shopService;
@Test
void testSaveShop() throws Exception {
shopService.saveShopToRedis(1L, 10L);
}
}
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
/**
* 提前预热
* @param id
*/
public void saveShopToRedis(Long id, Long expireSeconds) throws Exception {
// 1.查询店铺数据
Shop shop = getById(id);
// 模拟延迟
Thread.sleep(200);
// 2.封装逻辑过期时间
RedisData redisData =
new RedisData(LocalDateTime.now().plusSeconds(expireSeconds), shop);
// 3.写入Redis
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}
/**
* 逻辑过期解决缓存击穿
* @param id
* @return
*/
public Shop queryWithLogicalExpire(Long id){
String cacheShopKey = CACHE_SHOP_KEY + id;
// 1.查询Redis
String redisDataJson = stringRedisTemplate.opsForValue().get(cacheShopKey);
// 2.未命中返回空
if (StrUtil.isBlank(redisDataJson)) {
return null;
}
// 3.命中,判断缓存是否过期
RedisData redisData = JSONUtil.toBean(redisDataJson, RedisData.class);
// redisData中的data是JSONObject而不是Object
Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
// 4.未过期,返回商铺信息
if(LocalDateTime.now().isAfter(redisData.getExpireTime())){
// 5.过期,尝试获取互斥锁
String lockKey = LOCK_SHOP_KEY + id;
Boolean isLock = tryLock(lockKey);
// 5.1.获取锁失败,跳过
if(isLock){
// 5.2.获取锁成功,开启独立线程
CACHE_REBUILD_EXECUTOR.submit(() -> {
// 5.2.1.缓存重建
try {
this.saveShopToRedis(id, 20L);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// 5.2.2.释放锁
unlock(lockKey);
}
});
}
}
// 5.3.返回旧商铺信息
return shop;
}
/**
* 根据id查询店铺信息
* @param id
* @return
*/
@Override
public Result queryById(Long id) {
// // 1.布隆过滤器解决缓存穿透
// if (!querySolvingPassThrough(id)) {
// return Result.fail("商铺不存在!");
// }
// // 2.互斥锁解决缓存击穿
// Shop shop = queryWithMutex(id);
// if (shop == null) {
// return Result.fail("商铺不存在!");
// }
// 逻辑过期解决缓存击穿
Shop shop = queryWithLogicalExpire(id);
if (shop == null) {
return Result.fail("商铺不存在!");
}
return Result.ok(shop);
}