当前位置: 首页 > 后端技术 > Java

【Redis】实战代码是如何保证Redis和Mysql的一致性的

时间:2023-04-02 01:26:09 Java

1.前言现在最流行的缓存中间件是redis。由于redis基于内存操作,性能优越,所以被广泛使用。使用缓存的一般步骤如下:先查询缓存,如果缓存命中,直接返回数据,如果缓存没有命中,再查询数据库返回数据,并将查询到的数据放入缓存但是当我们要更新数据的时候,那么缓存和数据库中的数据可能会出现不一致的情况。更新缓存有多种操作,比如先更新缓存,再更新数据库,或者先更新数据库,再更新缓存。它们未在此处列出。可以参考站内的高手。撰文:https://segmentfault.com/a/11900000419986152.延迟双删方案在我们内部,我们更新缓存一般是先更新数据,再删除缓存,再延迟删除,这样缓存并且数据库可以达到最终一致性。伪代码如下tx.begin();//开始事务booleanresult=updateDB(data);如果(结果){booleancacheResult=deleteCache(dataId);//删除缓存if(!cacheResult){tx.rollback();//回滚事务return;}}else{tx.rollback();//回滚事务return;}tx.commit();//Committransaction//将dataId放入延迟队列,再次异步删除此缓存//异步删除缓存失败可以重试。如果失败次数达到n,则发送告警信息delayQueue.offer(dataId);这种方案的优缺点是显而易见的。数据库达到最终一致性,可能还会有一小段时间不一致。3、分布式锁方案如果有一些场景想实现强一致性,这里我们内部选择使用分布式锁(为了不引入其他组件,使用redis实现分布式锁)。代码如何实现缓存和数据库的强一致性?伪代码如下:3.1查询数据Objectresult=getCache(dataId);//查询缓存if(result==null){//缓存未命中RLocklock=getRLock();//获取分布式锁lock.lock();尝试{结果=getCache(dataId);//再次查询缓存,如果命中缓存,直接返回if(result!=null)returnresult;结果=queryDB(dataId);//查询数据库putCache(dataId,result);//将查询结果放入缓存}finally{lock.unlock();//释放锁}}returnresult;3.2更新数据//获取分布式锁RLocklock=getRLock();lock.lock();try{tx.begin();//开始事务booleanresult=updateDB(data);//更新数据库if(result){//如果更新数据库成功booleancacheResult=deleteCache(dataId);//删除缓存if(!cacheResult){//删除缓存失败tx.rollback();//回滚事务return;}}else{//更新数据库失败tx.rollback();//回滚事务返回;}tx.commit();//提交事务}finally{lock.unlock();//释放锁}上面的伪代码还有很多可以优化的地方,这里只贴出核心部分,仅供参考3.3.存在的问题一旦引入分布式锁,也会引入新的问题。如果是单点redis,就无法保证高可用。如果是redissentinel或者cluster模式,极端情况下会出现锁丢失(主从切换时,master还没来,锁信息同步到slave时,master挂了,slave切换给master,这时候锁丢了)怎么取舍?(个人更喜欢用单点的Redis做分布式锁,因为在强一致性的前提下,当应该做分布式锁的redis挂了,业务就没法继续了,我觉得比较合理)4.总结在不同的应用场景下使用不同的实现方案来保证数据库和缓存的一致性:在不需要强一致性的场景下,首选第一种方案,实现简单,高在效率上,而不是在需要引入分布式锁和强一致性要求的场景下,只好选择第二种方案,但是分布式锁的引入不仅降低了性能,还增加了维护难度