缓存就是数据交换的缓冲区(称作Cache),是存贮数据的临时地方,一般读写性能较高
之前没有使用缓存是的模型
# 具体实现流程 1 redis中查询商户缓存 2 判断是否存在 3 存在直接返回 4 不存在根据id去数据库查询 5 数据库也不存在,返回错误 6 存在则写入redis中 7 返回
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的数据已经删除了。
客户端请求的数据,在数据库和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
集群方案防止宕机不可用