• Redis篇:缓存更新策略最佳实践


    前景:

            缓存更新是redis为了节约内存而设计出来的一个东西,主要是因为内存数据宝贵,当我们向redis插入太多数据,此时就可能会导致缓存中的数据过多,所以redis会对部分数据进行更新,或者把他叫为淘汰更合适,一般有以下三种淘汰策略。

    1.缓存更新策略

    1.1.三种常见的更新策略

    内存淘汰:redis自动进行,当redis内存达到咱们设定的max-memery的时候,会自动触发淘汰机制,淘汰掉一些不重要的数据(可以自己设置策略方式)

    超时剔除:当我们给redis设置了过期时间ttl之后,redis会将超时的数据进行删除,方便咱们继续使用缓存

    主动更新 *:我们可以手动调用方法把缓存删掉,通常用于解决缓存和数据库不一致问题 

            

            

    1.2.数据库缓存不一致的解决方案

            由于我们的缓存的数据源来自于数据库,而数据库的数据是会发生变化的,因此,如果当数据库中数据发生变化,而缓存却没有同步,此时就会有一致性问题存在,其后果是:

            用户使用缓存中的过时数据,就会产生类似多线程数据安全问题,从而影响业务,产品口碑等;怎么解决呢?有如下几种方案:

    Cache Aside Pattern 人工编码方式:缓存调用者在更新完数据库后再去更新缓存,也称之为双写方案

    Read/Write Through Pattern : 由系统本身完成,数据库与缓存的问题交由系统本身去处理

    Write Behind Caching Pattern :调用者只操作缓存,其他线程去异步处理数据库,实现最终一致

    1.3.数据一致性问题采用什么方案

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

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

    如果采用第一个方案,那么假设我们每次操作数据库后,都操作缓存,但是中间如果没有人查询,那么这个更新动作实际上只有最后一次生效,中间的更新动作意义并不大,我们可以把缓存删除,等待再次查询时,将缓存中的数据加载出来

    • 删除缓存还是更新缓存?

      • 更新缓存:每次更新数据库都更新缓存,无效写操作较多

      • 删除缓存:更新数据库时让缓存失效,查询时再更新缓存

    • 如何保证缓存与数据库的操作的同时成功或失败?

      • 单体系统,将缓存与数据库操作放在一个事务

      • 分布式系统,利用TCC等分布式事务方案

    应该具体操作缓存还是操作数据库,我们应当是先操作数据库,再删除缓存,原因在于,如果你选择第一种方案,在两个线程并发来访问时,假设线程1先来,他先把缓存删了,此时线程2过来,他查询缓存数据并不存在,此时他写入缓存,当他写入缓存后,线程1再执行更新动作时,实际上写入的就是旧的数据,新的数据被旧数据覆盖了。

    • 先操作缓存还是先操作数据库?

      • 先删除缓存,再操作数据库

      • 先操作数据库,再删除缓存

    因为更新数据库耗时比更新缓存耗时长很多,所以第二种情况发生的概率极低,比第一种低很多,所以综合考虑还是用第二种

    2.实现缓存和数据库双写一致

    比如我们把黑马点评项目拿来做示例:

    2.1 实现商铺的缓存与数据库双写一致

    核心思路:

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

            1.根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间

            2.根据id修改店铺时,先修改数据库,再删除缓存

    先修改ShopServiceImpl中的queryById方法,设置一个过期时间:

    1. /**
    2. * 根据id查询商铺
    3. * @param id
    4. * @return
    5. */
    6. @Override
    7. public Result queryById(Long id) {
    8. //1.从redis查询商铺缓存
    9. String key = RedisConstants.CACHE_SHOP_KEY + id;
    10. String shopJson = stringRedisTemplate.opsForValue().get(key);
    11. //2.判断是否存在
    12. if(StrUtil.isNotBlank(shopJson)){
    13. //3.存在,返回商铺信息
    14. Shop shop = JSONUtil.toBean(shopJson, Shop.class);
    15. return Result.ok(shop);
    16. }
    17. //4.不存在,去数据库查询
    18. Shop shop = getById(id);
    19. //5判断数据库中是否存在
    20. if(shop == null){
    21. //6.数据库中不存在,返回错误信息
    22. return Result.fail("店铺不存在!");
    23. }
    24. //7.数据库中存在,将商铺信息写入redis,返回商铺信息
    25. stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop));
    26. //8.设置过期时间
    27. stringRedisTemplate.expire(key,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
    28. return Result.ok(shop);
    29. }

    再添加更新商铺时,需要进行删除缓存的操作:

            代码分析:通过之前的淘汰,我们确定了采用删除策略,来解决双写问题,当我们修改了数据之后,然后把缓存中的数据进行删除,查询时发现缓存中没有数据,则会从mysql中加载最新的数据,从而避免数据库和缓存不一致的问题。

    ShopServiceImpl中的update方法:

    1. /**
    2. * 更新商铺信息
    3. * @param shop
    4. * @return
    5. */
    6. @Override
    7. @Transactional
    8. public Result update(Shop shop) {
    9. Long id = shop.getId();
    10. if(id == null){
    11. return Result.fail("店铺id不存在");
    12. }
    13. //1.更新数据库
    14. updateById(shop);
    15. //2.删除缓存
    16. stringRedisTemplate.delete(RedisConstants.CACHE_SHOP_KEY +shop.getId());
    17. //返回
    18. return Result.ok();
    19. }

    2.2. 测试

    重启服务之后,先访问一下浏览器,访问商铺界面:

                    ​​​​​​​        

    先去redis客户端查看,目前是有shop的缓存的:

    接下来进行更新商铺操作,看看数据库发生改变的同时,是否会删除redis中缓存的shop:

    我们使用postman发起更新请求:

    发起请求之后,来到redis客户端:

    可以看到,shop的缓存被删除了,说明没问题。

    然后可以再去刷新一下浏览器,shop会重新添加缓存。

            ​​​​​​​        ​​​​​​​        

    这就保证了当数据库更新时,会立即删除redis中的缓存,实现双写一致。

  • 相关阅读:
    LM1875L-TB5-T 音频功率放大器 PDF中文资料_参数_引脚图
    Django系列:Django应用(app)的创建与配置
    2022年能做些什么副业呢?
    杭州亚运会,一个中国TO B厂商的“新样板间”
    阿里云物联网平台搭建
    Pandas 处理CSV文件数据集(五)
    Linux文件和目录管理
    腾讯云中使用ubuntu安装属于自己的overleaf
    Fair原理篇Fair逻辑动态化架构设计与实现
    windows server 2012 R2的C盘空间满了,但是找不到大文件的两种原因
  • 原文地址:https://blog.csdn.net/viperd/article/details/138125468