北京网站策划联系电话,网店营销活动策划方案,上海平台网站建设公司,郑州市建设路第二小学网站Redis和MySQL的数据一致性问题思考
最近有在反思自己工作。因为自己这边是面向业务的#xff0c;而且是和商品数据相关的。所以我平时工作中涉及到的最多的就是MySQL和Redis的数据存储。像我们配置商品是把商品配置到MySQL#xff0c;但是对外toC接口都是直接读取Redis的。所…Redis和MySQL的数据一致性问题思考
最近有在反思自己工作。因为自己这边是面向业务的而且是和商品数据相关的。所以我平时工作中涉及到的最多的就是MySQL和Redis的数据存储。像我们配置商品是把商品配置到MySQL但是对外toC接口都是直接读取Redis的。所以自然而然就涉及到MySQL和Redis的数据一致性问题。下面就是聊聊我自己对于这个问题的一个思考吧。有问题或者有更好方案的朋友也希望可以在评论里点出。
在互联网搜索这个问题很容易就看到概念性的经典方案比如下面的三个经典缓存模式我之前是没在意过这些的但是在学习思考的过程中确实觉得有些概念或者有些名词大家可以了解一下。我这里是简单的总结了一下具体的内容可以参考我看的那篇帖子https://juejin.cn/post/6964531365643550751他总结的很详情 呐就是下面的三个经典缓存模式
三个经典缓存模式
Cache-Aside(旁路缓存) 即读取缓存、读取数据库和更新缓存的操作都在应用系统来完成。业务最常用的缓存策略 读流程服务读取数据先读缓存缓存命中的话直接返回数据。没命中读库写回缓存并返回数据。写流程服务写数据先写数据库再删除缓存。 Read- Through/Write- Through读写穿透 即服务端把缓存作为主要数据存储。应用程序跟数据库缓存交互都是通过抽象缓存层完成的。 读流程Read- Through服务读取数据先读缓存缓存命中的话直接返回数据。没命中读库写回缓存并返回数据。 这里的读流程可以说是和上面一模一样了那搁这还说啥呢 其实这里强调的是在网关层和存储层之间增加了一个缓存层也就是Read-Through实际只是在Cache-Aside之上进行了一层封装 就是上面的是应用程序-缓存-MySQL 下面的是应用程序-Cache Provider - 缓存-MySQL写流程Write-Through当发生写请求时也是由缓存抽象层完成数据源和缓存数据的 应用程序-Cache Provider - 缓存 Cache Provider-MySQL Write behind 异步缓存写入 即服务端把缓存作为主要数据存储。应用程序跟数据库缓存交互通过抽象缓存层完成其中写操作是只更新缓存不直接更新数据库对于数据库的更新采用批量异步方式来更新数据库。 Write behind跟Read-Through/Write-Through都是由Cache Provider来负责缓存和数据库的读写。 不同 Read/Write Through是同步更新缓存和数据的 Write Behind则是只更新缓存不直接更新数据库通过批量异步的方式来更新数据库。 这种方式下缓存和数据库的一致性不强对一致性要求高的系统要谨慎使用。但是它适合频繁写的场景MySQL的InnoDB Buffer Pool机制就使用到这种模式。
Redis和MySQL一致性的两种场景和解决方案
Redis和MySQL的数据一致性我认为还是要结合业务需求我这里分为两个场景
第一种情况是接口获取数据以MySQL数据为主这种情况就是指接口数据获取时先读Redis如果Redis中数据获取失败就要去读MySQL这种情况。读写以MySQL数据为主第二种情况是接口获取数据以Redis数据为主这种情况就是指接口获取数据时只读Redis如果Redis中数据不存在那么就返回获取数据失败。写以MySQL为主读以Redis数据为主
第一种情况读写以MySQL数据为主
这种业务场景下一般使用Cache-Aside模式。就是读先读Redis、再读MySQL写先写MySQL再删除Redis。 注意这里是删除Redis。原因就是考虑到多个线程写的时候先更新MySQL的线程后更新了Redis导致Redis中的数据是旧数据脏数据个人认为主要是为了解决这种问题。当然了删除Redis就意味着我们选择的是延迟加载的方式。所以对于更新Redis就还存在下面另外个优点就是延迟加载在下一次有读请求时才会执行更新操作如果更新Redis的计算是复杂逻辑会在写多读少的情况下减少更新频率节省了性能损耗。 上面对于Redis和MySQL双写的顺序是先写MySQL再写Redis如果我想先写Redis呢 当然也可以先操作Redis但是这样的话就可能存在多线程下一个写线程先删了Redis另一个读线程在这个情况下没读到Redis就把MySQL中的数据写入到Redis中了。然后线程A写入MySQL中新数据所以这样MySQL和Redis就数据不一致了。。。。对于这种就要考虑延迟双删机制。 解决方案
同步 同步双写。Redis和MySQL的更新操作放在同一事务中整个成功整个失败保证双写完全一致此时先更新MySQL和先更新Redis都可以。不过一般以MySQL为主的话还是会优先先更新MySQL中的数据 异步这里虽然是同步去触发更新但是不放在一个事务内不能保证原子性就归属于异步更新当然触发也可以具体选择同步触发还是异步触发的方式 Cache-Aside(旁路缓存)。先写MySQL再删Redis缓存延迟双删。先删Redis再写MySQL再删Redis这里二次删除Redis的过程应该采用sleep休眠一会再删除或者使用延迟队列进行延迟删除缓存。监听binlog。通过监听MySQL的binlog触发异步删除如使用阿里的canal将binlog日志采集发送到MQ队列里面然后通过ACK机制确认处理这条更新消息删除缓存保证数据缓存一致性。 补偿 缓存失败重试机制 写请求更新数据库缓存因为某些原因删除失败把删除失败的key放到消息队列消费消息队列的消息获取要删除的key重试删除缓存操作
第二种情况写以MySQL为主读以Redis数据为主
这种业务情况是指写操作以MySQL为主即写操作的时候先写MySQL再写Redis读数据以Redis中的结果为主如果Redis中有接口就返回数据如果Redis中不存在数据就直接返回数据不存在。所以这个情况下就不能删除Redis了应该采用更新Redis。更新Redis可以保证有效数据在Redis中是永远存在的。那么这种情况下MySQL如何保证和Redis数据一致性呢 解决方案
同步 同步双写。Redis和MySQL的更新操作放在同一事务中整个成功整个失败保证双写完全一致。本地事务或分布式事务 异步这里虽然是同步去触发更新但是不放在一个事务内不能保证原子性就归属于异步更新当然触发也可以具体选择同步触发还是异步触发的方式 MQ消息。MySQL更新完成后直接发送异步MQ消息然后通过消费消息实现Redis数据变更监听binlog。通过监听MySQL的binlog触发异步更新如使用阿里的canal将binlog日志采集发送到MQ队列里面然后通过ACK机制确认处理这条更新消息更新缓存保证数据缓存一致性。数据库触发器。在MySQL中设置触发器当数据库发生变化时触发相应的操作将变化的数据同步到Redis中。通过在MySQL中设置触发器可以在数据发生变化时立即同步更新Redis。数据变更日志。记录数据变更日志将数据变更操作写入日志文件或者数据库中然后通过定时任务或者实时监控方式将变更的数据同步更新到Redis中。这个方法实时监控类似监听binlog方式定时任务的方式就类似于下面提到的补偿机制 补偿在上面异步触发更新的方式中存在更新完MySQL但是更新Redis失败的情况这种情况下就要考虑补偿更新 缓存失败重试机制。 更新Redis失败后同步执行重试如果重试成功即更新成功失败可以采用领起线程重试或下面其他方案。更新失败也则可采用另起一个线程再进行有限次数的重试重试成功即更新成功失败则写入MQ或log表记录等。更新Redis失败后把更新失败的key放到消息队列通过消费消息队列要更新的key达到重试更新Redis操作。更新Redis失败后把更新失败的数据记录到日志中日志可以是日志文件或数据库表然后通过监控方式再补偿更新。 定时任务定时补偿机制。 定时将MySQL中的全量数据更新到Redis中。这里的定时可以采用全量定时和增量定时两种定时更新方式。更新Redis失败后把更新失败的数据记录到日志中定时任务扫描实现失败数据补偿更新。