提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
/**
* 根据id查询商铺信息
* @param id 商铺id
* @return 商铺详情数据
*/
@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
return Result.ok(shopService.queryById(id));
}
@Override
public Result queryById(Long id) {
String key = CACHE_SHOP_KEY+id;
//1、从redis中查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
//2、判断是否存在
if (StrUtil.isNotBlank(shopJson)){
//3、存在,直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
//4、不存在,根据id查询数据库
Shop shop = getById(id);
//5、数据库中不存在,返回错误
if (shop==null){
return Result.fail("店铺不存在");
}
//6、数据库中存在,写进redis并返回
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop));
return Result.ok(shop);
}
先删除缓存,再更新数据库
不考虑并发的情况下,不会出现缓存和数据库不一致的问题
并发情况下,就会存在缓存和数据不一致的问题
无论那种方式,都会存在缓存和数据库不一致的问题
解决缓存于数据库部不一致的问题https://juejin.cn/post/6844903929281511438
@Override
@Transactional
public Result updateShop(Shop shop) {
Long id = shop.getId();
if (id==null){
return Result.fail("店铺id不能为空");
}
//1、更新数据库
updateById(shop);
//2、删除缓存
stringRedisTemplate.delete(CACHE_SHOP_KEY+shop.getId());
return Result.ok();
}
@Override
public Result queryById(Long id) {
String key = CACHE_SHOP_KEY+id;
//1、从redis中查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
//2、判断是否存在
if (StrUtil.isNotBlank(shopJson)){
//3、存在,直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
//4、不存在,根据id查询数据库
Shop shop = getById(id);
//5、数据库中不存在,返回错误
if (shop==null){
return Result.fail("店铺不存在");
}
//6、数据库中存在,写进redis并返回
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
return Result.ok(shop);
}
上面的解决方式是,给redis中的key都设置上时间作为兜底方案,更新数据的时候,先更新数据库再删除缓存,这种方案并不能带来强一致性
@Override
public Result queryById(Long id) {
String key = CACHE_SHOP_KEY+id;
//1、从redis中查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
//2、判断是否存在
if (StrUtil.isNotBlank(shopJson)){
//3、存在,直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
//todo 判断命中的是否是空值
if (shopJson==null){
return Result.fail("店铺信息不存在");
}
//4、不存在,根据id查询数据库
Shop shop = getById(id);
//5、数据库中不存在,返回错误
if (shop==null){
//todo 将控制,写入redis
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
return Result.fail("店铺不存在");
}
//6、数据库中存在,写进redis并返回
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
return Result.ok(shop);
}
互斥锁适合于解决强一致性的场景,对缓存和数据库一致性要求比较高的;如果对缓存和数据库一致性要求不高,而追求高可用的场景,逻辑过期更适合
public Shop queryWithMutex(Long id){
String key = CACHE_SHOP_KEY+id;
//1、从redis中查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
//2、判断是否存在
if (StrUtil.isNotBlank(shopJson)){
//3、存在,直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return shop;
}
//todo 判断命中的是否是空值
if (shopJson==null){
return null;
}
//4、实现缓存重建
//4.1、获取互斥锁
String lockKey = "locl:shop"+id;
boolean isLock = tryLock(lockKey);
//4.2、判断是否获取成功
//4.3、失败,休眠并重试
Shop shop=null;
try {
if (!isLock){
Thread.sleep(3000);
return queryWithMutex(id);
}
//4.4、成功,根据id查询数据库
shop = getById(id);
//5、数据库中不存在,返回错误
if (shop==null){
//todo 将控制,写入redis
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
return null;
}
//6、数据库中存在,写进redis并返回
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
}catch (Exception e){
throw new RuntimeException(e);
}finally {
//7、释放互斥锁
unLocak(lockKey);
}
return shop;
}
public Shop queryWithLogcialExpire(Long id){
String key = CACHE_SHOP_KEY+id;
//1、从redis中查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
//2、判断是否存在
if (StrUtil.isBlank(shopJson)){
return null;
}
// todo 命中
//4、需要先把json反序列化为对象
RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
//5、判断是否过期
LocalDateTime expireTime = redisData.getExpireTime();
if (expireTime.isAfter(LocalDateTime.now())){
//5.1 未过期,直接返回店铺信息
return shop;
}
//5.2 已过期,需要缓存重建
//6、缓存重建
//6.1、获取互斥锁
String lockKey = LOCK_SHOP_KEY+id;
//6.2 判断是否获取锁成功
boolean islock = tryLock(lockKey);
if (islock){
// 6.3 成功,开启独立线程,实现缓存重建、
CACHE_REBUILD_EXECUTOR.submit(()->{
try{
saveShop2redis(id,30L);
}catch (Exception e){
throw new RuntimeException(e);
}finally {
//释放锁
unLocak(lockKey);
}
});
}
// 6.4、失败,返回过期的商铺信息
return shop;
}
private void saveShop2redis(Long id,Long expireSeconds) throws InterruptedException {
//查询店铺数据
Shop shop = getById(id);
Thread.sleep(200);
//封装逻辑过期时间
RedisData redisData = new RedisData();
redisData.setData(shop);
redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
//写入redis
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
}
@Data
public class RedisData {
private LocalDateTime expireTime;
private Object data;
}