文章目录
- 数据一致性
- 缓存常见问题
- 缓存穿透
- 缓存击穿
- 缓存雪崩
数据一致性
1 思路
- 查询数据的时候,如果缓存未命中,则查询数据库,将数据写入缓存设置超时时间
- 修改数据时,先修改数据库,在删除缓存。
2 代码实现
- 修改更新方法,添加超时时间
@Overridepublic Result queryById(Long id) {//1 redis中查询商户缓存String shopJson = (String)redisTemplate.opsForValue().get("cache.shop:" + id);//2 判断是否存在if(StrUtil.isNotBlank(shopJson)){//3存在直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}//4 不存在根据id去数据库查询Shop shop = this.getById(id);//5 数据库也不存在,返回错误if(shop==null){return Result.fail("店铺不存在");}//6 存在则写入redis中redisTemplate.opsForValue().set("cache.shop:" + id,JSONUtil.toJsonStr(shop),30, TimeUnit.MINUTES);//7 返回return Result.ok(shop);}
- 修改ShopController
@PutMappingpublic Result updateShop(@RequestBody Shop shop) {// 写入数据库//shopService.updateById(shop);//return Result.ok();return shopService.update(shop);}
- 修改service代码 延时双删策略
@Overridepublic Result update(Shop shop) {Long id = shop.getId();if(id==null){return Result.fail("店铺id不存在");}// 删除缓存redisTemplate.delete("cache.shop:" + id);// 更新数据库updateById(shop);Thread.sleep(800);// 删除缓存redisTemplate.delete("cache.shop:" + id);return Result.ok();}
3 修改完代码以后,将所有的缓存删除,执行查询操作,多了超时
4 用postman执行修改方法: localhost:8081/shop
{"area":"大关","sold":3035,"address":"金华路锦昌文华苑29号","name":"102茶餐厅","x":120.149192,"y":30.316078,"typeId":1,"id":1
}
执行完成以后,数据库的数据发生改变,查看redis的数据已经删除了。
这样能保证百分之99的数据一致性问题,无法保证完全一致性,这个适合小项目,数据一致性要求不高的地方使用,如果对数据一致性要求高的不建议使用,建议使用数据库和redis数据同步进行的操作,可以上csdn进行搜索查看实现方式。
缓存常见问题
缓存穿透
客户端请求的数据,在数据库和redis中都不存在,这样缓存永远都不会生效,请求最终都到了数据库上。
解决方案:
- 当在数据库查询的结果也不存在的时候,可以返回null值给redis,并且设置TTL
-
布隆过滤器
布隆过滤器是一种数据结构,底层是位数组,通过将集合中的元素多次hash得到的结果保存到布隆过滤器中。主要作用就是可以快速判断一个元素是否在集合里面,但是因为算法的原因,也有一定概率的错误。
开发的时候我们一般选择空值值方式。
-
代码方式实现
根据id查询的时候,如果信息不存在,则要将空值写入redis,并设置空值过期时间
@Overridepublic Result queryById(Long id) {//1 redis中查询商户缓存String shopJson = (String)redisTemplate.opsForValue().get("cache.shop:" + id);//2 判断是否存在if(StrUtil.isNotBlank(shopJson)){//3存在直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}if(shopJson!=null){return Result.fail("店铺不存在");}//4 不存在根据id去数据库查询Shop shop = this.getById(id);//5 数据库也不存在,返回错误if(shop==null){// 空值写入redis中redisTemplate.opsForValue().set("cache.shop:" + id,"",3, TimeUnit.MINUTES);return Result.fail("店铺不存在");}//6 存在则写入redis中redisTemplate.opsForValue().set("cache.shop:" + id,JSONUtil.toJsonStr(shop),30, TimeUnit.MINUTES);//7 返回return Result.ok(shop);}
缓存击穿
也叫热点key问题,一个被高并发访问且业务复杂的key突然失效了,无数的请求瞬间给数据库带来的巨大冲击
解决方案: 互斥锁 逻辑过期
互斥锁思路:
查询缓存的时候,未命中需要获取锁 代码ShopServiceImpl
@Override
public Result queryById(Long id) {//1 redis中查询商户缓存String shopJson = (String)redisTemplate.opsForValue().get("cache.shop:" + id);//2 判断是否存在if(StrUtil.isNotBlank(shopJson)){//3存在直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}if(shopJson!=null){return Result.fail("店铺不存在");}Shop shop = null;String lockKey = "lock.id:" + id;try {//代码到这里说明没有命中缓存,那么就可以获取锁了boolean isLock = tryLock(lockKey);// 如果没有拿到锁,则等待一会,递归执行代码if(!isLock){Thread.sleep(100);queryById(id);}//获取锁成功//4 不存在根据id去数据库查询shop = this.getById(id);//5 数据库也不存在,返回错误if(shop==null){// 空值写入redis中redisTemplate.opsForValue().set("cache.shop:" + id,"",3, TimeUnit.MINUTES);return Result.fail("店铺不存在");}//6 存在则写入redis中redisTemplate.opsForValue().set("cache.shop:" + id,JSONUtil.toJsonStr(shop),30, TimeUnit.MINUTES);} catch (InterruptedException e) {e.printStackTrace();} finally {unlock(lockKey);}//7 返回return Result.ok(shop);
}// 获取锁
private boolean tryLock(String key){Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);
}
//释放锁
private void unlock(String key){redisTemplate.delete(key);
}
缓存雪崩
同一时间段内,大量的缓存key失效或者redis宕机,到时大量的请求到达数据库,带来巨大的压力。
解决方案
- 给key设置随机的TTL(有效时间)
- 集群方案防止宕机不可用