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双写一致性问题

相关推荐
莳花微语几秒前
Oracle 用户/权限/角色管理
数据库·oracle
GUIQU.3 分钟前
【Oracle】游标
数据库·oracle
平平无奇。。。3 分钟前
Mysql库的操作和表的操作
linux·数据库·mysql
雪花凌落的盛夏6 分钟前
PostgreSQL数据库备份
数据库·postgresql
Lao A(zhou liang)的菜园7 分钟前
Oracle双平面适用场景讨论会议
数据库·平面·oracle
小小星球之旅8 分钟前
redis缓存常见问题
数据库·redis·学习·缓存
小李是个程序10 分钟前
数据库完整性
数据库·sql
梁云亮20 分钟前
SpringBoot中缓存@Cacheable出错
spring boot·缓存
GreatSQL20 分钟前
GreatSQL连接数被打满的3种紧急解决方案
数据库
bxlj_jcj24 分钟前
解锁Java多级缓存:性能飞升的秘密武器
java·缓存·面试·架构