昆明建网站的公司,cpanel伪静态wordpress,photoshop基础入门教程,网站备案证书下载MySQL与Redis都是常用的数据存储和缓存系统。为了提高应用程序的性能和可伸缩性#xff0c;很多应用程序将MySQL和Redis一起使用#xff0c;其中MySQL作为主要的持久存储#xff0c;而Redis作为主要的缓存。在这种情况下#xff0c;应用程序需要确保MySQL和Redis中的数据是…MySQL与Redis都是常用的数据存储和缓存系统。为了提高应用程序的性能和可伸缩性很多应用程序将MySQL和Redis一起使用其中MySQL作为主要的持久存储而Redis作为主要的缓存。在这种情况下应用程序需要确保MySQL和Redis中的数据是同步的以确保数据的一致性。
什么是一致性
“数据一致”一般指的是缓存中有数据缓存的数据值数据库中的值。但根据缓存中是有数据为依据则“一致”可以包含两种情况
1缓存中有数据缓存的数据值数据库中的值。
2缓存中本没有数据数据库中的值最新值有请求查询数据库时会将数据写入缓存则变为上面的“一致”状态。
“数据不一致”缓存的数据值≠数据库中的值缓存或者数据库中存在旧值导致其他线程读到旧数据。 一致性就是数据保持一致在分布式系统中可以理解为多个节点中数据的值是一致的。
强一致性这种一致性级别是最符合用户直觉的它要求系统写入什么读出来的也会是什么用户体验好但实现起来往往对系统的性能影响大弱一致性这种一致性级别约束了系统在写入成功后不承诺立即可以读到写入的值也不承诺多久之后数据能够达到一致但会尽可能地保证到某个时间级别比如秒级别后数据能够达到一致状态最终一致性最终一致性是弱一致性的一个特例系统会保证在一定时间内能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来是因为它是弱一致性中非常推崇的一种一致性模型也是业界在大型分布式系统的数据一致性上比较推崇的模型
导致数据不一致的原因
1 在高并发的业务场景下数据库大多数情况都是用户并发访问最薄弱的环节。所以就需要使用redis做一个缓冲操作让请求先访问到redis而不是直接访问MySQL等数据库
2读取缓存步骤一般没有什么问题但是一旦涉及到数据更新数据库和缓存更新就容易出现缓存(Redis)和数据库MySQL间的数据一致性问题
3这个业务场景主要是解决读数据从Redis缓存一般都是按照下图的流程来进行业务操作。 应对策略
针对缓存更新问题提出了一个旁路缓存的缓存更新套路这个策略分为以下三种场景
1失效应用程序先从缓存取数据没有得到则从数据库中取数据成功后放到缓存中。
2命中应用程序从缓存中取数据取到后返回。
3更新先把数据存到数据库中成功后再让缓存失效。
不管是先删缓存再更新数据库还是先更新数据库再删缓存都会导致缓存跟数据不一致问题
先写MySQL再写Redis 先写Redis再写MySQL 先删除Redis再写MySQL 先写 MySQL再删除 Redis 不管是先写数据库再删除缓存还是先删除缓存再写库都有可能出现数据不一致的情况。
现有的大部分业务场景下大多采用读写分离的操作来提升数据库吞吐量但是并发读写访问的时候对缓存和数据库相互交叉执行操作则会出现数据不一致问题。
在进行数据更新时就涉及到先更新缓存还是先更新数据库了其实两种方式都有数据一致性问题
举个例子假如业务A为写请求业务B为读请求
1.先更新数据库再更新缓存
步骤1业务A先更新数据库此时该业务线由于宕机或者其他原因延迟没有继续进行。
步骤2业务B读取数据读取的是缓存中的旧数据。
步骤3业务A恢复过来更新缓存
可以看到由于写请求延迟可能会读到旧的缓存数据。
2.先更新缓存再更新数据库
步骤1业务A先删除缓存
步骤2业务B进入业务B发现缓存中没有数据直接从数据库中进行读取读到了数据库中的旧数据
步骤3业务A更新数据库并返回。
可以看到由于写请求延迟可能读到旧的数据库数据。
因为写和读是并发的没法保证顺序,就会出现缓存和数据库的数据不一致的问题。
解决方案
1读写请求串行化
最为简单的一种方法写请求在更新之前需要先获得分布式锁获取到锁才能去更新数据库获取不到则进行等待超时直接返回更新失败。更新完数据库后更新缓存如果更新失败放到内存队列中进行重新尝试。读请求则同样需要获得锁然判断缓存中是否有数据有则直接读缓存没有则直接读数据库并更新缓存。
这种方案可以保证数据的一致性。但是会降低系统吞吐量等待时间长这在需要数据强一致的情况下适用。银行转账
2删除缓存
1.先删除缓存后更新数据库2.先更新数据库后删除缓存
先删除缓存后更新数据库第二步操作失败数据库没有更新成功那下次读缓存发现不存在则从数据库中读取并重建缓存此时数据库和缓存依日保持一致。
但如果是先更新数据库后删除缓存第二步操作失败数据库是最新值缓存中是旧值发生不致。所以这个方案依旧存在问题。
总之和前面提到的问题类似第二步失败依旧有不一致的风险
我们再来看[并发]问题这个问题是我们需要关注的[重点]
先更新数据库后删除缓存
依旧是 2 个线程并发[读写]数据
1.缓存中 X 不存在 (数据库 X 1) 2.线程 A 读取数据库得到目值 (X 1) 3.线程 B 更新数据库 (X 2) 4.线程 B 删除缓存 5.线程 A 将日值写入缓存 (X 1)
最终 X的值在缓存中是 1 (日值) 在数据库中是 2(新值)也发生不一致
这种情况[理论]来说是可能发生的但实际真的有可能发生吗?
其实概率[很低]这是因为它必须满足 3 个条件
1.缓存刚好已失效 2.读请求 写请求并发 3.更新数据库 除缓存的时间 (步 3-4) 要比读数据库 写缓存时间短(步 2 和5)
仔细想一下条件 3 发生的概率其实是非常低的因为写数据库一般会先[加锁]所以写数据库通常是要比读数据库的时间更长的这么来看[先更新数据库 再删除缓存]的方案是可以保证数据一致性的。
所以我们应该采用这种方案来操作数据库和缓存
如何保证两步都执行成功?
无论是更新缓存还是删除缓存只要第二步发生失败那么就会导致数据库和缓存不一致。 保证第二步成功执行就是解决问题的关键.
程序在执行过程中发生异常最简单的解决办法是什么? 答案是:异步重试 如果是同步重试立即重试很大概率还会失败[重试次数]设置多少才合理? 重试会一直[占用]这个线程资源无法服务其它客户端请求 异步其实就是把重试请求写到消息队列中然后由专门的消费者来重试直到成功。
为了避免第二步执行失败我们可以把操作缓存这一步直接放到消息队列中由消费者来操作缓存
到这里你可能会问写消息队列也有可能会失败啊? 而且引入消息队列这又增加了更多的维扩成本这样做值得吗?
这个问题很好但我们思考这样一个问题:如果在执行失败的线程中一直重试还没等执行成功此时如果项目[重启]了那这次重试请求也就[丢失]了那这条数据就一直不一致了
所以这里我们必须把重试消息或第二步操作放到另一个[服务]中这个服务用[消息队列]最为合适。
消息队列保证可靠性: 写到队列中的消息成功消费之前不会丢失(重启项目也不担心)消息队列保证消息成功投递: 下游从队列拉取消息成功消费后才会删除消息否则还会继续投递消息给消费者 (符合我们重试的需求)
至于写队列失败和消息队列的维护成本问题 写队列失败: 操作缓存和写消息队列[同时失败]的概率其实是很小的维护成本: 我们项目中一般都会用到消息队列维护成本并没有新增很多
参考资料https://www.zhihu.com/question/319817091 https://www.jb51.net/database/285448xty.htm https://baijiahao.baidu.com/s?id1706150811910444110wfrspiderforpc