文章目录
- 数据一致性
- 缓存常见问题
- 缓存穿透
- 缓存击穿
- 缓存雪崩
数据一致性
1 思路
- 查询数据的时候,如果缓存未命中,则查询数据库,将数据写入缓存设置超时时间
- 修改数据时,先修改数据库,在删除缓存。
2 代码实现
- 修改更新方法,添加超时时间
@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);
}
//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
@PutMapping
public Result updateShop(@RequestBody Shop shop) {
// 写入数据库
//shopService.updateById(shop);
//return Result.ok();
return shopService.update(shop);
}
- 修改service代码 延时双删策略
@Override
public 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,并设置空值过期时间
@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("店铺不存在");
}
//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(有效时间)
- 集群方案防止宕机不可用