redis7高级之缓存双写一致性之更新策略探讨(代码片段)

晓风残月Lx 晓风残月Lx     2023-04-01     406

关键词:

1.缓存双写一致性

  • 如果redis中有数据

    • 需要和数据库中的值相同
  • 如果redis中无数据

    • 数据库中的值是最新值,且准备回写redis
  • 缓存按照操作分

    • 只读缓存
    • 读写缓存
      • 同步直写策略
        • 写数据库后也同步写 redis 缓存,缓存中的数据和数据中的一致
        • 对于读写缓存来说,要想保证缓存和数据库中的数据一致
      • 异步缓写策略
        • 正常业务运行中,mysql数据变动了,但是可以在业务上容许出现一定时间后才作用于redis,比如仓库、物流系统
        • 异常情况出现了,不得不讲失败的动作重新修补,有可能需要借助kafka或者RabbitMQ等消息中间件,实现重写重试
    • 采用双检加锁策略

      • 多个线程同时去查询数据库的这条数据,就在第一个查询数据的请求上使用一个互斥锁来锁住他。
      • 其他线程获取不到锁就一直等待,等第一个线程查询到了数据,然后做了缓存
      • 后面的线程进来发现已经有了缓存,就直接走缓存
      package com.lv.service.impl;
      
      import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
      import com.lv.User;
      import com.lv.mapper.UserMapper;
      import com.lv.service.UserService;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.data.redis.core.RedisTemplate;
      import org.springframework.stereotype.Service;
      
      import javax.annotation.Resource;
      import java.util.concurrent.TimeUnit;
      
      /**
       * @author 晓风残月Lx
       * @date 2023/3/27 12:39
       */
      @Slf4j
      @Service
      public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService 
      
          public static final String CACHE_KEY_USER = "user:";
          @Resource
          private UserMapper userMapper;
          @Resource
          private RedisTemplate redisTemplate;
      
      
          /**
           *  业务逻辑没有写错,对于小厂中厂(QPS《=1000)可以使用,但是大厂不行
           * @param id
           * @return
           */
          public User findUserById1(Long id)
              User user = null;
      
              String key = CACHE_KEY_USER + id;
      
              // 1.先从redis中查询,如果有直接返回结果,没有再去查询 mysql
              user = (User) redisTemplate.opsForValue().get(key);
      
              if (user == null)
                  // 2. redis中没有,查询mysql
                   user = userMapper.selectById(id);
                   if (user == null)
                       // 3.1 redis + mysql 都无数据
                       // 具体细化,防止多次穿透,业务规定,记录下导致穿透的这个key回写redis
                       return user;
                   else 
                       // 3.2 mysql有,需要回写到redis,保证下一次的缓存命中率
                       redisTemplate.opsForValue().set(key,user);
                   
              
              return user;
          
      
          /**
           * 加强补充,避免突然key失效了,打爆mysql,做一下预防,尽量不出现击穿的情况
           * @param id
           * @return
           */
          public User findUserById2(Long id)
              User user = null;
              String key = CACHE_KEY_USER + id;
      
              // 1.先从redis里面查询,如果有直接返回结果,如果没有再去查询mysql
              // 第一次查询redis,加锁前
              user = (User) redisTemplate.opsForValue().get(key);
              if (user == null)
                  // 2.对于高QPS的优化,进来就先加锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysql
                  synchronized (UserServiceImpl.class)
                      // 第二次查询redis,加锁后
                      user = (User) redisTemplate.opsForValue().get(key);
                      // 3. 二次查redis还是null,可以去查mysql了(mysql默认有数据)
                      if (user == null) 
                          //4 查询mysql拿数据(mysql默认有数据)
                          user = userMapper.selectById(id);
                          if (user == null) 
                              return null;
                           else 
                              // 5. mysql里面有数据的,需要回写redis,完成数据一致性的同步工作
                              redisTemplate.opsForValue().setIfAbsent(key, user, 7L, TimeUnit.DAYS);
                          
                      
                  
              
              return user;
          
      
      

2.数据库和缓存一致性的几种更新策略

目的

  • 达到最终一致性
    • 给缓存设置过期时间,定期清理缓存并回写,是保证最终一致性的解决方案。
    • 我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存,达到一致性,切记,要以mysql的数据库写入库为准。

四种更新策略

可停机的情况

基本上怎么处理都可以

  • 挂牌报错
  • 凌晨升级
  • 服务降级
  • 温馨提示
  • 最好单线程操作(对于重量级的数据操作)
不可停机的情况(推荐最后一种,看场景)
  1. 先更新数据库,在更新缓存

    • 异常问题1

    • 异常问题2

  2. 先更新缓存,再更新数据库

    • 一般业务会将mysq作为底单数据库,有最终解释权

    • 异常问题

  3. 先删除缓存,再更新数据库

    • 异常问题

    • 解决方案(延时双删策略)

      • 注意关键点,我更新完数据库的时间 + sleep的时间 大于 读取数据并写入换的时间 即可(多个100ms即可)
    • 关于延时双删的细节问题

      • 这个线程休眠时间(线程A sleep的时间,就需要大于线程B读取数据再写入缓存的时间)

        • 第一种 :在业务程序运行的时候,统计下线程读数据和写缓存的操作时间,自行评估自己的项目的读数据业务逻辑的耗时,以此为基础来进行估算。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上加百毫秒即可。
        • 第二种: 新启动一个后台监控程序,比如WatchDog监控程序,会加时
      • 这种同步淘汰策略,吞吐量降低怎么办

        • 第二次删除缓存使用 异步删除

  4. 先更新数据库,再删除缓存

    • 异常问题

    • 订阅binlog程序再mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能(下一章具体实现)

    • 解决方法

    分布式的事务问题一定要遵守最终一致性,可以允许短暂的信息滞后

3.总结

  • 先删除缓存值再更新数据库

    • 有可能导致请求因缓存缺失而访问数据库,给数据库带来压力导致打满mysql
    • 如果业务应用中读取数据库和写缓存的时间不好估算,那么,延迟双删中的等待时间就不好设置
  • 先更新数据库,再删除缓存

    • 如果业务层要求必须读取一致性的数据,那么我们就需要在更新数据库时,先在Redis缓存客户端暂停并发读请求,等数据库更新完、缓存值删除后,再读取数据,从而保证数据一致性,这是理论可以达到的效果,但实际,不推荐,因为真实生产环境中,分布式下很难做到实时一致性,一般都是最终一致性。

分布式之数据库和缓存双写一致性方案解析

引言为什么写这篇文章?首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用。在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作。但是在更新缓存方面,对于更新完数据库,是更新缓存呢,... 查看详情

分布式之数据库和缓存双写一致性方案解析

引言为什么写这篇文章?首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用。在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作。但是在更新缓存方面,对于更新完数据库,是更新缓存呢,... 查看详情

分布式之数据库和缓存双写一致性方案解析

为什么写这篇文章?首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用。在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作。但是在更新缓存方面,对于更新完数据库,是更新缓存呢,还是... 查看详情

场景应用:如何保证缓存与数据库的双写一致性?

文章目录如何保证缓存与数据库的双写一致性?四种同步策略:同步策略探究更新缓存还是删除缓存:先操作数据库还是缓存:最终结论:补充:延时双删策略采用读写分离的架构怎么办?第二次删除... 查看详情

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

(目录)实现这个方案:商户查询缓存商户查询缓存1.什么是缓存(Cache)?前言:什么是缓存?举个例子:例如:例1:StaticfinalConcurrentHashMap<K,V>map=newConcurrentHashMap<>();例2:staticfinalCache<K,V>USER_CACHE=CacheBuilder.newBuilder().b 查看详情

场景应用:如何保证缓存与数据库的双写一致性?

文章目录如何保证缓存与数据库的双写一致性?四种同步策略:同步策略探究更新缓存还是删除缓存:先操作数据库还是缓存:最终结论:补充:延时双删策略采用读写分离的架构怎么办?第二次删除... 查看详情

redis缓存双写一致性(代码片段)

目录双写一致性Redis与Mysql双写一致性canal配置流程代码案例双写一致性理解缓存操作细分缓存一致性多种更新策略挂牌报错,凌晨升级先更新数据库,在更新缓存先删除缓存,在更新数据库先更新数据库,在删除缓存延迟双删策略总... 查看详情

redis与mysql双写一致性(代码片段)

双写一致性时为了保证Redis缓存与MySQL数据库中的数据一样我们对Redis中没有的数据,MySQL怎么回写呢?我们用双检加锁策略这样只要第一个请求发过来,后面的请求就不会发送到MySQL,直接从Redis中获取缓存数据就可以了。 为... 查看详情

redis缓存简介以及缓存的更新策略(代码片段)

目录一、什么是缓存二、为什么要使用缓存三、如何使用缓存四、添加商户缓存1、缓存模型和思路 2、代码如下五、缓存更新策略 1、数据库缓存不一致解决方案: 2、数据库和缓存不一致采用什么方案3、CacheAsidePattern实现... 查看详情

一个高频面试题:怎么保证缓存与数据库的双写一致性?

...存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?CacheAsidePattern最经典的缓存+数据库读写的模式,就是CacheAsidePattern。读的时候,先读缓存,缓存没有的话,就读数据库,然后... 查看详情

redis缓存一致性(代码片段)

文章目录缓存一致性读缓存**双检加锁**策略写缓存保障最终数据一致性解决方案先更新数据库,再更新缓存案例演示1->更新缓存异常案例演示2->并发导致先更新缓存,再更新数据库案例演示->并发导致先删除缓存&#... 查看详情

如何保证数据库和缓存双写一致性?(代码片段)

...大家见面了。前言数据库和缓存(比如:redis)双写数据一致性问题,是一个跟开发语言无关的公共问题。尤其在高并发的场景下,这个问题变得更加严重。我很负责的告诉大家,该问题无论在面试,还是工作中遇到的概率非常... 查看详情

redission读写锁解决db和缓存双写不一致(代码片段)

db和缓存双写不一致多线程访问环境下,在更新完db后再去更新缓存,不加锁显而易见的就会出现缓存被覆盖的问题。线程1修改完db去更新缓存的时候慢了一拍。此时线程2在线程1之后修改完db更新成功了缓存。此时线程1... 查看详情

一个经典面试题:如何保证缓存与数据库的双写一致性?

...存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?面试题剖析一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求“缓... 查看详情

如何保证redis缓存与数据库的一致性?(代码片段)

目录1、四种同步策略:2、更新缓存还是删除缓存2.1更新缓存2.2删除缓存3、先操作数据库还是缓存3.1先删除缓存再更新数据库3.2先更新数据库再删除缓存4、延时双删4.1采用读写分离的架构怎么办?5、利用消息队列进行删... 查看详情

redis双写一致性看一篇成高手系列1

首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用。在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作。 但是在更新缓存方面,对于更新完数据库,是更新缓存呢,还是删除缓存。又或... 查看详情

小工匠聊架构-提升性能的大杀器之缓存技术

文章目录Pre为何使用缓存CPU瓶颈IO瓶颈本地缓存or分布式缓存本地缓存分布式缓存如何选择缓存框架缓存通用知识缓存命中率缓存更新策略主动请求DB数据,更新缓存被动请求DB数据,更新缓存缓存过期策略依赖时间的过期策略定... 查看详情

redis什么是缓存与数据库双写不一致?怎么解决?(代码片段)

什么是缓存与数据库双写不一致?怎么解决?1.热点缓存重建1.1什么是热点缓存重建1.2基于DCL(doublechecklock)双重检测锁解决热点缓存并发重建问题1.3分布式锁解决热点缓存并发重建问题2.缓存与数据库双写不一致2.1CacheAsideP... 查看详情