Redis之缓存双写一致性理论分析

何为缓存双写一致性?

问题: 上面业务逻辑你用java代码如何写?

面试题

解决方案以及策略的探讨

双写一致性,谈谈你的理解。

  • 如果redis中有数据,需要和数据库中的值相同
  • 如果redis中无数据,数据库中的值是最新值,且准备回写redis
  • 缓存按照操作来分,细分2种:只读缓存与读写缓存
  • 读写缓存分为同步直写策略与异步缓写策略
  • 同步直写策略:写数据库后也同步写redis缓存 ,缓存和数据库中的数据一致。对于读写缓存来说,要想保证缓存和数据库中的数据一致,就要采用同步直写
  • 异步缓写策略:正常业务运行中 ,mysql数据变动了,但是可以在业务上容许出现一定时间后才作用于redis,比如仓库、物流系统。异常情况出现了,不得不将失败的动作重新修补,有可能需要借助Kafka或者Rocketmq等消息中间件,实现消息重写。

一图代码你如何写

案例:业务逻辑并没有写错,但对于小厂QPS小于1000可以使用,但大厂不行

在高并发的情景下,统统将请求打到mysql,mysql的数据库的压力就比较大,统统写入redis中,容易出现数据覆盖的情景。理由:从mysql中查数据,以及回写到redis这两步不是原子操作。容易在高并发的情况下,被多个线程打爆。

解决方案:双检加锁策略

  • 多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它
  • 其他的线程 走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。
  • 后面的线程 进来发现已经有缓存了,就直接走缓存

    根据上面的方案解释,改写后的代码:加锁版本
java 复制代码
package com.atguigu.redis.service;

import com.atguigu.redis.entities.User;
import com.atguigu.redis.mapper.UserMapper;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @auther zzyy
 * @create 2021-05-01 14:58
 */
@Service
@Slf4j
public class UserService {
    public static final String CACHE_KEY_USER = "user:";
    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 业务逻辑没有写错,对于小厂中厂(QPS《=1000)可以使用,但是大厂不行
     * @param id
     * @return
     */
    public User findUserById(Integer 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.selectByPrimaryKey(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(Integer id)
    {
        User user = null;
        String key = CACHE_KEY_USER+id;

        //1 先从redis里面查询,如果有直接返回结果,如果没有再去查询mysql,
        // 第1次查询redis,加锁前
        user = (User) redisTemplate.opsForValue().get(key);
        if(user == null) {
            //2 大厂用,对于高QPS的优化,进来就先加锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysql
            synchronized (UserService.class){
                //第2次查询redis,加锁后
                user = (User) redisTemplate.opsForValue().get(key);
                //3 二次查redis还是null,可以去查mysql了(mysql默认有数据)
                if (user == null) {
                    //4 查询mysql拿数据(mysql默认有数据)
                    user = userMapper.selectByPrimaryKey(id);
                    if (user == null) {
                        return null;
                    }else{
                        //5 mysql里面有数据的,需要回写redis,完成数据一致性的同步工作
                        redisTemplate.opsForValue().setIfAbsent(key,user,7L,TimeUnit.DAYS);
                    }
                }
            }
        }
        return user;
    }

}

 

 

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

目的:达到最终一致性

可以停机的情况:

挂牌报错、凌晨升级、温馨提示、服务降级

单线程,这样重量级的数据操作最好不要多线程。

4种更新策略

  • 先更新数据库,再更新缓存
    异常问题1:redis更新失败了

    异常问题2:多线程的乱序问题
  • 先更新缓存,再更新数据库
    不太推荐,业务上一般将mysql作为底单数据库,保证最后解释
    异常问题:多线程的问题
  • 先删除缓存,再更新数据库(适合读多写少的业务情景)
    异常问题:多线程数据更新失败或者超时时,容易将旧数据写入缓存



    上面的三步串接起来看的流程

    总结:先删除缓存,再更新数据库
    如果数据库更新失败或超时或返回不及时,导致B线程请求访问缓存时发现redis里面没数据,缓存缺失,B再去读取mysql时,从数据库中读取到旧值,还写回redis,导致A白干了
    解决方案:延迟双删

    延迟双删的面试题
    1.这个删除操作的休眠期需要多久呢?

2.这种同步淘汰策略,吞吐量降低该怎么办?

3.后续看门狗watchdog源码分析

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

    微软方案
    微软解决方案链接

    存在的问题

    解决方案:

    最终一致性的业务举例:

    缓存与数据库一致性更新策略小总结


视频讲解
redis双写一致性问题

相关推荐
山外有山a几秒前
从 Neo4j 数据库中提取数据并绘制图谱
数据库·neo4j
Full Stack Developme2 小时前
SQL 版本历史
数据库·sql
杰克逊的日记4 小时前
mysql数据实时全量+增量迁移
数据库·mysql·数据迁移
linuxxx1105 小时前
centos7 升级MariaDB 到 10.5 或更高版本
数据库·mariadb
换个网名有点难5 小时前
django怎么配置404和500
数据库·django
Adellle6 小时前
MySQL
数据库·后端·mysql
就是有点傻7 小时前
C# 中实现一个线程持续读取,另一个线程负责写入,且写入时读取线程暂停
数据库·c#
云观秋毫7 小时前
试试智能体工作流,自动化搞定运维故障排查
运维·数据库·自动化
是沫沫子耶7 小时前
mysql实例
数据库
比钻石还闪亮的nan人7 小时前
CentOS 7下安装PostgreSQL 15
数据库·postgresql