什么是/使用缓存(Cache),缓存更新策略数据库缓存不一致解决方案 及 实现缓存与数据库双写一致

Posted Perceus

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是/使用缓存(Cache),缓存更新策略数据库缓存不一致解决方案 及 实现缓存与数据库双写一致相关的知识,希望对你有一定的参考价值。

(目录)


实现这个方案商户查询缓存


商户查询缓存

1. 什么是缓存 ( Cache )?

前言:什么是缓存?

举个例子:

例如:

例1:Static final ConcurrentHashMap<K,V> map = new ConcurrentHashMap<>(); 
例2:static final Cache<K,V> USER_CACHE = CacheBuilder.newBuilder().build(); 
例3:Static final Map<K,V> map =  new HashMap();

为什么要使用缓存

但是缓存也会增加代码复杂度和运营的成本:


如何使用缓存

实际开发中,会构筑多级缓存来使系统运行速度进一步提升。

例如:本地缓存redis中的缓存并发使用


2. 添加 商户缓存、商铺分类缓存

@GetMapping("/id")
public Result queryShopById(@PathVariable("id") Long id) 
    //这里是直接查询数据库
    return shopService.queryById(id);


缓存模型和思路

标准操作方式:


添加商户缓存代码:

代码思路:

 @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result queryShopById(Long id) 

        // 1. 从 Redis 中查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        // 2. 判断是否存在
        // 3. 存在 , 直接返回
        if(StrUtil.isNotBlank(shopJson))
            Shop shop=JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        
        // 4. 不存在 ,根据 id 去数据库查询
        Shop shop = getById(id);
        // 5. 不存在 , 返回 404
        if(shop==null)
            return Result.fail("商铺不存在");
        

        // 6. 存在 , 写入 Redis
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop));

        return Result.ok(shop);
    

添加商铺分类缓存:

package com.hmdp.controller;
 
 
import com.hmdp.dto.Result;
import com.hmdp.entity.ShopType;
import com.hmdp.service.IShopTypeService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import javax.annotation.Resource;
import java.util.List;
 
 
@RestController
@RequestMapping("/shop-type")
public class ShopTypeController 
    @Resource
    private IShopTypeService typeService;
 
//    public Result queryTypeList() 
//        List<ShopType> typeList = typeService
//                .query().orderByAsc("sort").list();
//        return Result.ok(typeList);
//    
 
    @GetMapping("list")
    public Result queryTypeList() 
        return typeService.queryTypeLists();
    

package com.hmdp.service;
 
import com.hmdp.dto.Result;
import com.hmdp.entity.ShopType;
import com.baomidou.mybatisplus.extension.service.IService;
 
 
public interface IShopTypeService extends IService<ShopType> 
 
    Result queryTypeLists();

package com.hmdp.service.impl;
 
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.ShopType;
import com.hmdp.mapper.ShopTypeMapper;
import com.hmdp.service.IShopTypeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisConstants;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
 
import javax.annotation.Resource;
import java.util.List;
 
 
@Service
public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService 
 
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryTypeLists() 
        //获取radis中商户
        String shopType=stringRedisTemplate.opsForValue().get("shopType");
        if (StrUtil.isNotBlank(shopType)) 
            //存在,直接返回
            List<ShopType> shopTypes = JSONUtil.toList(shopType, ShopType.class);
            return Result.ok(shopTypes);
        
        //不存在,从数据库中查询写入redis
        List<ShopType> shopTypes = query().orderByAsc("sort").list();
        //不存在,返回错误
        if (shopTypes == null) 
            return Result.fail("分类不存在");
        
        //将查询到的信息存入radis
        stringRedisTemplate.opsForValue().set("shopType",JSONUtil.toJsonStr(shopTypes));
        //返回
        return Result.ok(shopTypes);
 
    


效果展示:

没添加缓存前:耗时 1.54s

添加缓存后:254ms


3. 缓存更新策略

缓存更新是redis为了节约内存而设计出来的一个东西,主要是因为内存数据宝贵,当我们向redis插入太多数据。

此时就可能会导致缓存中的数据过多,所以redis会对部分数据进行更新,或者把他叫为淘汰更合适。


数据库缓存不一致解决方案:

有如下几种方案:


数据库和缓存不一致采用什么方案

综合考虑使用方案一,但是方案一调用者如何处理呢?这里有几个问题

操作缓存和数据库时有三个问题需要考虑:

  1. 删除缓存还是更新缓存?
  1. 如何保证缓存与数据库的操作的同时成功或失败?
  1. 先操作缓存还是先操作数据库?

因为缓存操作极快,方案二出现问题可能性低


4. 实现商铺和缓存与数据库双写一致

核心思路如下:

修改ShopController中的业务逻辑,满足下面的需求

修改重点代码1:修改ShopServiceImpl的queryById方法


修改重点代码2

代码分析:

/**
     *  查询商铺
     * @param id
     * @return
     */
    @Override
    public Result queryShopById(Long id) 

        // 1. 从 Redis 中查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        // 2. 判断是否存在
        // 3. 存在 , 直接返回
        if(StrUtil.isNotBlank(shopJson))
            Shop shop=JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        
        // 4. 不存在 ,根据 id 去数据库查询
        Shop shop = getById(id);
        // 5. 不存在 , 返回 404
        if(shop==null)
            return Result.fail("商铺不存在");
        

        // 6. 存在 , 写入 Redis
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);

        return Result.ok(shop);
    

以上是关于什么是/使用缓存(Cache),缓存更新策略数据库缓存不一致解决方案 及 实现缓存与数据库双写一致的主要内容,如果未能解决你的问题,请参考以下文章

缓存读写策略 - Cache Aside.md

缓存策略

RedisRedis 的缓存使用技巧(商户查询缓存)

RedisRedis 的缓存使用技巧(商户查询缓存)

RedisRedis 的缓存使用技巧(商户查询缓存)

NSURLRequestCachePolicy 缓存策略