【Redis实战总结】实际生产代码如何保证Redis与Mysql的一致性

一、前言

当今最流行的缓存中间件当属redis了,由于redis是基于内存操作性能优越,所以被广泛使用。

使用缓存的一般步骤如下:

  1. 先查询缓存,如果缓存命中,直接返回数据
  2. 如果缓存不命中,则查询数据库返回数据,并将查询到的数据放入缓存中

但是当我们想要更新数据时,这时可能会出现缓存与数据库中的数据不一致问题,这里面有各种更新缓存的操作,比如先更新缓存、再更新数据库先更新数据库、再更新缓存,这里不一一列举了,可查阅站内大神写的 https://segmentfault.com/a/11...

二、延迟双删方案

在我们内部一般是通过先更新数据,再删除缓存,再延迟删除的方案来更新缓存的,这样可以使缓存与数据库达到最终一致性。伪代码如下

tx.begin();  // 开启事务
boolean result = updateDB(data);
if (result) {
    boolean cacheResult = deleteCache(dataId);  // 删除缓存
    if (!cacheResult) {
        tx.rollback();  // 回滚事务
        return;
    }
} else {
    tx.rollback();  // 回滚事务
    return;
}
tx.commit();  // 提交事务

// 将dataId放入延迟队列,通过异步地方式再次删除该缓存
// 异步删除缓存失败可以进行重试,如果失败次数达到n,则发送告警信息
delayQueue.offer(dataId);  

这种方案优缺点很明显,优点就是实现简单,缺点就是只能让缓存和数据库达到最终一致性,仍然可能出现一小段时间的不一致

三、分布式锁方案

那如果真的有某些场景想要达到强一致性,这里我们内部选择的是使用分布式锁为了不引入其他组件,使用redis来实现分布式锁)。

那代码如何来实现缓存与数据库的强一致性,伪代码如下:

3.1 查询数据

Object result = getCache(dataId);    // 查询缓存

if (result == null) {                // 缓存未命中

    RLock lock = getRLock();         // 获取分布式锁
    lock.lock();

    try {     
        result = getCache(dataId);    // 再次查询缓存,如果命中缓存,直接返回
        if (result != null) 
            return result;
        
        result = queryDB(dataId);    // 查询数据库
        putCache(dataId, result);    // 将查询结果置入缓存中
    } finally {
        lock.unlock();  // 释放锁
    }
}

return result;

3.2 更新数据

// 获取分布式锁
RLock lock = getRLock();
lock.lock();

try {
    tx.begin();                         // 开启事务
    boolean result = updateDB(data);    // 更新数据库
    if (result) {                       // 如果更新数据库成功
        boolean cacheResult = deleteCache(dataId);  // 删除缓存
        if (!cacheResult) {             // 删除缓存失败
            tx.rollback();              // 回滚事务
            return;
        }
    } else {                            // 更新数据库失败
        tx.rollback();                  // 回滚事务
        return;
    }
    tx.commit();                        // 提交事务
} finally {
    lock.unlock();                      // 释放锁
}

上面的伪代码还有许多可以优化的地方,这里只是把核心部分贴出来,仅供参考。

3.3、存在的问题

一旦引入分布式锁,也将引入新的问题

  • 如果是单点redis,无法保证高可用
  • 如果是redis哨兵或集群模式,极端情况下会存在锁丢失在主从切换时,master还没来的及将锁信息同步到slave时,master挂掉,slave切换为master,此时锁丢失)的情况,如何取舍?(个人偏向于使用单独的单点Redis来做分布式锁,因为在已经需要强一致性的前提下,当该用作分布式锁的redis挂掉时,该业务将不能进行,个人认为也相对合理)

四、总结

在不同的应用场景下使用不同的实现方案:

  1. 不需要强一致性的场景下,首选第一种方案,其实现简单、效率高,不需要引入分布式锁
  2. 需要强一致性的场景下,无奈只能选择第二种方案,但分布式锁的引入也增加了维护难度

你可能感兴趣的:(javaredis)