MySQL数据库和Redis缓存一致性的更新策略

大家好,我是哪吒。

上一期分享了,一次线上事故,导致公司损失400万完成了Redis避免使用大Key的学习和理解。

一、更新策略

1、如果Redis中有数据,需要和数据库中的值相同。

2、如果Redis中无数据,数据库中的最新值要对Redis进行同步更新。

二、读写缓存

1、同步直写策略

写入数据库也同步写Redis缓存,缓存和数据库中的数据一致;对于读写缓存来说,要保证缓存和数据库中的数据一致,就要保证同步直写策略。

2、异步缓写策略

某些业务运行中,MySQL数据更新之后,允许在一定时间后再进行Redis数据同步,比如物流系统。

当出现异常情况时,不得不将失败的动作重新修补,需要借助rabbitmq或kafka进行重写。

三、双检加锁策略

多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。

其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。

后面的线程进来发现已经有缓存了,就直接走缓存。

public String get(String key){
    // 从Redis缓存中读取
    String value = redisTemplate.get(key);

    if(value != null){
        return value;
    }

    synchronized (RedisTest.class){
        // 重新尝试从Redis缓存中读取
        value = redisTemplate.get(key);
        if(value != null){
            return value;
        }

        // 从MySQL数据库中查询
        value = studentDao.get(key);
        // 写入Redis缓存
        redisTemplate.setnx(key,value,time);
        return value;
    }
}
复制代码

四、数据库和缓存一致性的更新策略

1、先更新数据库,再更新Redis

按照常理出牌的话,应该都是如此吧?那么,这种情况下,会有啥问题呢?

如果更新数据库成功后,更新Redis之前异常了,会出现什么情况呢?

数据库与Redis内缓存数据不一致。

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

多线程情况下,会有问题。

比如

  1. 线程1更新redis = 200;
  2. 线程2更新redis = 100;
  3. 线程2更新MySQL = 100;
  4. 线程1更新MySQL = 200;

结果呢,Redis=100、MySQL=200;我擦!

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

  1. 线程1删除了Redis的缓存数据,然后去更新MySQL数据库;
  2. 还没等MySQL更新完毕,线程2杀来,读取缓存数据;
  3. 但是,此时MySQL数据库还没更新,线程2读取了MySQL中的旧值,然后线程2,还会将旧值写入Redis作为数据缓存;
  4. 线程1更新完MySQL数据后,发现Redis中已经有数据了,之前都删过了,那我就不更新了;

完蛋了。。

延时双删

延时双删可以解决上面的问题,只要sleep的时间大于线程2读取数据再写入缓存的时间就可以了,也就是线程1的二次清缓存操作要在线程2写入缓存之后,这样才能保证Redis缓存中的数据是最新的。

/**
 * 延时双删
 * @autor 哪吒编程
 */
public void deleteRedisData(Student stu){
    // 删除Redis中的缓存数据
    jedis.del(stu);

    // 更新MySQL数据库数据
    studentDao.update(stu);

    // 休息两秒
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    // 删除Redis中的缓存数据
    jedis.del(stu);
}
复制代码

延迟双删最大的问题就是sleep,在效率为王的今天,sleep能不用还是不用为好。

你不睡我都嫌你慢,你还睡上了...

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

  1. 线程1先更新数据库,再删除Redis缓存;
  2. 线程2在线程1删除Redis缓存之前发起请求,得到了未删除的Redis缓存;
  3. 线程1此时才删除Redis缓存数据;

问题还是有,这翻来覆去的,没完没了了。

这种情况如何解决呢?

引入消息中间件解决战斗,再一次详细的复盘一下。

  1. 更新数据库;
  2. 数据库将操作信息写入binlog日志;
  3. 订阅程序提取出key和数据;
  4. 尝试删除缓存操作,发现删除失败;
  5. 将这些数据信息发送到消息中间件中;
  6. 从消息中间件中获取该数据,重新操作;

5、总结

哪吒推荐使用第四种方式,先更新数据库,再删除缓存。

方式①和方式②缺点太过明显,不考虑; 方式③中的sleep,总是让人头疼; 方式④是一个比较全面的方案,但是增加了学习成本、维护成本,因为增加了消息中间件。

五、MySQL主从复制工作原理

1、当 master 主服务器上的数据发生改变时,则将其改变写入二进制事件日志文件中;

2、salve 从服务器会在一定时间间隔内对 master 主服务器上的二进制日志进行探测,探测其是否发生过改变,

如果探测到 master 主服务器的二进制事件日志发生了改变,则开始一个 I/O Thread 请求 master 二进制事件日志;

3、同时 master 主服务器为每个 I/O Thread 启动一个dump Thread,用于向其发送二进制事件日志;

4、slave 从服务器将接收到的二进制事件日志保存至自己本地的中继日志文件中;

5、salve 从服务器将启动 SQL Thread 从中继日志中读取二进制日志,在本地重放,使得其数据和主服务器保持一致;

6、最后 I/O Thread 和 SQL Thread 将进入睡眠状态,等待下一次被唤醒;

猜你喜欢

转载自juejin.im/post/7219674355491553340