网站建设性意见表,怎么做淘宝客网站做淘客,采购软件,工作室logo设计免费生成redis系列整体栏目 内容链接地址【一】redis基本数据类型和使用场景https://zhenghuisheng.blog.csdn.net/article/details/142406325【二】redis的持久化机制和原理https://zhenghuisheng.blog.csdn.net/article/details/142441756【三】redis缓存穿透、缓存击穿、缓存雪崩htt…redis系列整体栏目 内容链接地址【一】redis基本数据类型和使用场景https://zhenghuisheng.blog.csdn.net/article/details/142406325【二】redis的持久化机制和原理https://zhenghuisheng.blog.csdn.net/article/details/142441756【三】redis缓存穿透、缓存击穿、缓存雪崩https://zhenghuisheng.blog.csdn.net/article/details/142577507【四】redisson实现分布式锁实战和源码剖析https://zhenghuisheng.blog.csdn.net/article/details/142646301【五】redis保证和mysql数据一致性https://zhenghuisheng.blog.csdn.net/article/details/142687101 如需转载请输入https://blog.csdn.net/zhenghuishengq/article/details/142687101 redis保证和mysql数据一致性 一redis保证和mysql数据一致性1数据不一致原因1.1更新缓存出现的问题1.2删除缓存出现的问题1.3理想情况 2数据不一致解决方案2.1延迟双删(不推荐)2.2消息队列2.3分布式锁2.4canal同步 一redis保证和mysql数据一致性
在项目中redis一般作为缓存使用从而加快系统的读写效率和吞吐量但是数据最终一般是存储在mysql中因此在实际开发中尤其是在高并发的互联网项目中经常会遇到这种mysql和redis数据不一致的情况。如在一个电商的扣减库存的场景中如果redis中的数据和mysql中的数据没有保证一致那么就很有可能造成超卖的问题那么平台就可能会因为这个问题造成商家的损失
1数据不一致原因
数据不一致原因无非就是那几种原因在分布式场景中并发带来的问题网络抖动带来的问题分布式事务不一致问题等。
1.1更新缓存出现的问题
如下面这个场景以扣减库存的场景为例假设有100个库存此时多个线程对该商品下单。如在一个4s的时间线内线程1和线程2先后下单在不考虑mysql事务的情况下来查看以下会出现的问题其流程如下
首先在第一秒时线程1进行一个扣减库存操作但是由于网络卡顿的原因导致在第4秒才更新redis的缓存只为95并且写入mysql的数据也为95在第二秒时线程2也下单10件商品此时redis缓存还是100因此操作的还是这个100那么扣减库存之后的值变成90然后在第三秒时更新缓存值为90由于暂时不考虑事务因此此时mysql的值为100-10-5为85而redis中的缓存值由于覆盖的问题导致还是95此时就出现了mysql和redis值不一致的问题。 1.2删除缓存出现的问题
更新缓存指的是在写完mysql数据之后立马就去更新缓存效率可能会低一些除了更新缓存还可以删除缓存就是在写的时候不更新缓存而是直接删掉缓存再读数据的时候进行redis缓存数据的写入如以下流程
首先线程1在第一秒时扣减5个库存然后将缓存删除此时redis中的值为95第二秒一个线程3来读数据发现缓存没有就读数据库此时数据库的值为95在第三秒有一个用户下单扣减库存操作为10此时mysql中为85删除缓存由于卡顿问题在第四秒线程3才执行更新缓存操作但是拿的值不是最新值导致原本redis值为85的由于覆盖的问题此时redis的值为95导致redis和mysql数据不一致的问题 1.3理想情况
在理想情况下我们更希望的是每个命令都可以顺序执行将各种无法预料的并发问题直接改成串行化操作。当然如果在此基础上进行优化的话在加锁的同时考虑加一把互斥锁从而保证读读共享其他互斥的操作 2数据不一致解决方案
2.1延迟双删(不推荐)
延迟双删顾名思义就是延迟一段时间删除某些线程因为卡顿原因导致更新缓存的值出错如下图线程3由于卡顿造成缓存覆盖的问题从而导致了redis中缓存和mysql中的值出现不一致的问题但是通过延迟双删的方式对缓存做两次删除并且两次删除间设置一定的时间间隔从而解决部分卡顿问题 其伪代码如下首先是第一次删除缓存然后休眠个300秒然后再次删除缓存。
#第一次删除缓存
redis.delKey(phone:1001)
#休眠300毫秒
Thread.sleep(300)
#再删除缓存
redis.delKey(phone:1001)延迟双删的方式也不能完全解决这种数据不一致问题只能减少这种数据不一致概率的事件发生因为不能保证某些查询的线程要卡顿多久比如我设置300毫秒但是有的线程卡顿一秒因此也不能够完全解决。
其次所有的写操作都得删除两次这对redis来说会有一定的性能影响除了本身的操作之外还得额外的增加一段延时的时间对于使用redis的初衷来说就没那么友好更加的适用于 读多写少 的场景如商品首页等。
2.2消息队列
如果为了强一致性那么可以直接使用消息队列来使用其本质也很简单将并行的操作全部加入到消息队列中串行执行。
消息队列确实可以解决这种数据不一致问题因为都串行执行不会有那种并行和并发的问题。但是使用消息队列需要额外的引入中间件还需要考虑数据的持久化不丢失以及顺序消费等问题如果整个系统是已经有再使用消息中间件的话如kafka、rocketMq这些那么是可以考虑使用这种方式的 2.3分布式锁
在上一篇中讲解了redis分布式锁的使用和底层原理分布式锁的机制和上面的消息队列的机制一样都是将并行执行的操作方式改成串行的执行方式直接通过redisson实现的分布式锁即可
RLock lock redisson.getLock(PHONE_ID);
lock.lock();
lock.unlock();通过这种分布所锁机制可以保证数据的串行执行不出现redis数据脏写问题和覆盖问题 当然也可以通过redis的 读写锁 进行优化部分代码如下直接在读的接口中使用读锁使用后需要释放锁
//获取一把读写锁
RReadWriteLock readWriteLock redissoon.getReadWriteLock(lockKey);
//获取读锁
RLock readLock readWriteLock.readLock();
//加锁
readLock.lock();
//解锁
readLock.unlock();写的接口中使用写锁即可使用后也许把锁释放。
//获取一把读写锁
RReadWriteLock readWriteLock redissoon.getReadWriteLock(lockKey);
//获取写锁
RLock writeLock readWriteLock.writeLock();
//加锁
writeLock.lock();
//解锁
writeLock.unlock();不管是原生的Redission锁还是这种读写锁底层都是通过lua脚本实现这种方式也更加的适用于 读多写少 的场景。相对于消息队列可以选这种方式实现
2.4canal同步
上面几种方式都是需要通过手动的删除缓存或者更新缓存的数据实现数据同步除了上面几种方式之外还可以用阿里的canal开源中间件来实现数据的同步并且通过这种方式不需要认为的去操作缓存
其原理也很简单就类似于mysql的主从复制原理伪装成一个从结点监听mysql的binlog日志然后将执行的mysql的操作通过这个canal中间件同步到redis中。 这样的话也不需要去考虑redis的更新和删除操作只需要查即可查不到再查mysql。
当然如果想直接使用这种canal中间件的话需要手动的重新部署也相当于重新引入了一个新的中间件。如果数据量大的话可以考虑使用这种方式来解决数据不一致问题。