温州专业微网站制作,wordpress修改首页模板文件,外贸英文网站,网站建设企业站有哪些要求数据缓存
在我们实际的业务场景中#xff0c;一定有很多需要做数据缓存的场景#xff0c;比如售卖商品的页面#xff0c;包括了许多并发访问量很大的数据#xff0c;它们可以称作是是“热点”数据#xff0c;这些数据有一个特点#xff0c;就是更新频率低#xff0c;读…数据缓存
在我们实际的业务场景中一定有很多需要做数据缓存的场景比如售卖商品的页面包括了许多并发访问量很大的数据它们可以称作是是“热点”数据这些数据有一个特点就是更新频率低读取频率高这些数据应该尽量被缓存从而减少请求打到数据库上的机会减轻数据库的压力。
哪类数据适合缓存
缓存量大但又不常变化的数据比如详情评论等。对于那些经常变化的数据其实并不适合缓存一方面会增加系统的复杂性缓存的更新缓存脏数据另一方面也给系统带来一定的不稳定性缓存系统的维护。
但一些极端情况下你需要将一些会变动的数据进行缓存比如想要页面显示准实时的库存数或者其他一些特殊业务场景。这时候你需要保证缓存不能一直有脏数据这就需要再深入讨论一下。
缓存的利与弊
我们到底该不该上缓存的这其实也是个trade-off权衡的问题。
上缓存的优点
能够缩短服务的响应时间给用户带来更好的体验。能够增大系统的吞吐量依然能够提升用户体验。减轻数据库的压力防止高峰期数据库被压垮导致整个线上服务BOOM
上了缓存也会引入很多额外的问题
缓存有多种选型是内存缓存memcached还是redis你是否都熟悉如果不熟悉无疑增加了维护的难度本来是个纯洁的数据库系统。缓存系统也要考虑分布式比如redis的分布式缓存还会有很多坑无疑增加了系统的复杂性。在特殊场景下如果对缓存的准确性有非常高的要求就必须考虑缓存和数据库的一致性问题。
如何保证缓存和数据库一致性
说了这么多缓存的必要性那么使用缓存是不是就是一个很简单的事情了呢我之前也一直是这么觉得的直到遇到了需要缓存与数据库保持强一致的场景才知道让数据库数据和缓存数据保持一致性是一门很高深的学问。
从远古的硬件缓存操作系统缓存开始缓存就是一门独特的学问。这个问题也被业界探讨了非常久争论至今。我翻阅了很多资料发现其实这是一个权衡的问题。值得好好讲讲。
以下的讨论会引入几方观点我会跟着观点来写代码验证所提到的问题。
不更新缓存而是删除缓存
大部分观点认为做缓存不应该是去更新缓存而是应该删除缓存然后由下个请求去去缓存发现不存在后再读取数据库写入缓存。
观点引用《分布式之数据库和缓存双写一致性方案解析》孤独烟 原因一线程安全角度 同时有请求A和请求B进行更新操作那么会出现 1线程A更新了数据库 2线程B更新了数据库 3线程B更新了缓存 4线程A更新了缓存 这就出现请求A更新缓存应该比请求B更新缓存早才对但是因为网络等原因B却比A更早更新了缓存。这就导致了脏数据因此不考虑。 原因二业务场景角度 有如下两点 1如果你是一个写数据库场景比较多而读数据场景比较少的业务需求采用这种方案就会导致数据压根还没读到缓存就被频繁的更新浪费性能。 2如果你写入数据库的值并不是直接写入缓存的而是要经过一系列复杂的计算再写入缓存。那么每次写入数据库后都再次计算写入缓存的值无疑是浪费性能的。显然删除缓存更为适合。 其实如果业务非常简单只是去数据库拿一个值写入缓存那么更新缓存也是可以的。但是淘汰缓存操作简单并且带来的副作用只是增加了一次cache miss建议作为通用的处理方式。
先操作缓存还是先操作数据库
那么问题就来了我们是先删除缓存然后再更新数据库还是先更新数据库再删缓存呢 《【58沈剑架构系列】缓存架构设计细节二三事》58沈剑 对于一个不能保证事务性的操作一定涉及“哪个任务先做哪个任务后做”的问题解决这个问题的方向是如果出现不一致谁先做对业务的影响较小就谁先执行。 假设先淘汰缓存再写数据库第一步淘汰缓存成功第二步写数据库失败则只会引发一次Cache miss。 假设先写数据库再淘汰缓存第一步写数据库操作成功第二步淘汰缓存失败则会出现DB中是新数据Cache中是旧数据数据不一致。 沈剑老师说的没有问题不过没完全考虑好并发请求时的数据脏读问题让我们再来看看孤独烟老师《分布式之数据库和缓存双写一致性方案解析》 先删缓存再更新数据库 该方案会导致请求数据不一致 同时有一个请求A进行更新操作另一个请求B进行查询操作。那么会出现如下情形: 1请求A进行写操作删除缓存 2请求B查询发现缓存不存在 3请求B去数据库查询得到旧值 4请求B将旧值写入缓存 5请求A将新值写入数据库 上述情况就会导致不一致的情形出现。而且如果不采用给缓存设置过期时间策略该数据永远都是脏数据。 所以先删缓存再更新数据库并不是一劳永逸的解决方案再看看先更新数据库再删缓存这种方案怎么样 先更新数据库再删缓存这种情况不存在并发问题么 不是的。假设这会有两个请求一个请求A做查询操作一个请求B做更新操作那么会有如下情形产生 1缓存刚好失效 2请求A查询数据库得一个旧值 3请求B将新值写入数据库 4请求B删除缓存 5请求A将查到的旧值写入缓存 ok如果发生上述情况确实是会发生脏数据。 然而发生这种情况的概率又有多少呢 发生上述情况有一个先天性条件就是步骤3的写数据库操作比步骤2的读数据库操作耗时更短才有可能使得步骤4先于步骤5。可是大家想想数据库的读操作的速度远快于写操作的不然做读写分离干嘛做读写分离的意义就是因为读操作比较快耗资源少因此步骤3耗时比步骤2更短这一情形很难出现。 先更新数据库再删缓存依然会有问题不过问题出现的可能性会因为上面说的原因变得比较低
所以如果你想实现基础的缓存数据库双写一致的逻辑那么在大多数情况下在不想做过多设计增加太大工作量的情况下请先更新数据库再删缓存!
我非要数据库和缓存数据强一致怎么办
那么如果我非要保证绝对一致性怎么办先给出结论
没有办法做到绝对的一致性这是由CAP理论决定的缓存系统适用的场景就是非强一致性的场景所以它属于CAP中的AP。
所以我们得委曲求全可以去做到BASE理论中说的最终一致性。 最终一致性强调的是系统中所有的数据副本在经过一段时间的同步后最终能够达到一个一致的状态。因此最终一致性的本质是需要系统保证最终数据能够达到一致而不需要实时保证系统数据的强一致性 大佬们给出了到达最终一致性的解决思路主要是针对上面两种双写策略先删缓存再更新数据库/先更新数据库再删缓存导致的脏数据问题进行相应的处理来保证最终一致性。
缓存延时双删
问先删除缓存再更新数据库中避免脏数据
答案采用延时双删策略。
上文我们提到在先删除缓存再更新数据库的情况下如果不采用给缓存设置过期时间策略该数据永远都是脏数据。
那么延时双删怎么解决这个问题呢
1先淘汰缓存
2再写数据库这两步和原来一样
3休眠1秒再次淘汰缓存
这么做可以将1秒内所造成的缓存脏数据再次删除。
那么这个1秒怎么确定的具体该休眠多久呢
针对上面的情形读者应该自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上加几百ms即可。这么做的目的就是确保读请求结束写请求可以删除读请求造成的缓存脏数据。
如果你用了mysql的读写分离架构怎么办
ok在这种情况下造成数据不一致的原因如下还是两个请求一个请求A进行更新操作另一个请求B进行查询操作。
1请求A进行写操作删除缓存
2请求A将数据写入数据库了
3请求B查询缓存发现缓存没有值
4请求B去从库查询这时还没有完成主从同步因此查询到的是旧值
5请求B将旧值写入缓存
6数据库完成主从同步从库变为新值
上述情形就是数据不一致的原因。还是使用双删延时策略。只是睡眠时间修改为在主从同步的延时时间基础上加几百ms。
采用这种同步淘汰策略吞吐量降低怎么办
ok那就将第二次删除作为异步的。自己起一个线程异步删除。这样写的请求就不用沉睡一段时间后了再返回。这么做加大吞吐量。
所以在先删除缓存再更新数据库的情况下可以使用延时双删的策略来保证脏数据只会存活一段时间就会被准确的数据覆盖。
在先更新数据库再删缓存的情况下缓存出现脏数据的情况虽然可能性极小但也会出现。我们依然可以用延时双删策略在请求A对缓存写入了脏的旧值之后再次删除缓存。来保证去掉脏缓存。
删缓存失败了怎么办重试机制
看似问题都已经解决了但其实还有一个问题没有考虑到那就是删除缓存的操作失败了怎么办比如延时双删的时候第二次缓存删除失败了那不还是没有清除脏数据吗
解决方案就是再加上一个重试机制保证删除缓存成功。
缓存、数据库一致性解决方案
方案一保证先删缓存再更新数据库操作的一致性 流程如下所示
1更新数据库数据
2缓存因为种种问题删除失败
3将需要删除的key发送至消息队列
4自己消费消息获得需要删除的key
5继续重试删除操作直到成功
然而该方案有一个缺点对业务线代码造成大量的侵入。于是有了方案二在方案二中启动一个订阅程序去订阅数据库的binlog获得需要操作的数据。在应用程序中另起一段程序获得这个订阅程序传来的信息进行删除缓存操作。
方案二保证先更新数据库再删缓存的一致性 流程如下图所示
1更新数据库数据
2数据库会将操作信息写入binlog日志当中
3订阅程序提取出所需要的数据以及key
4另起一段非业务代码获得该信息
5尝试删除缓存操作发现删除失败
6将这些信息发送至消息队列
7重新从消息队列中获得该数据重试操作。
而读取binlog的中间件可以采用阿里开源的canal 阿里开源MySQL中间件Canal快速入门
方案三通过分布式锁保证不同请求操作互斥并通过写锁提高性能
解决不一致的关键点是解决淘汰缓存与更新库表两操作之间其它请求产生的数据不一致问题。 我们可以采用分布式锁操作数据前先要获得对应的锁淘汰缓存与更新库表放入同一把写锁中与其它读请求互斥防止其间产生旧数据。读写互斥、写写互斥、读读共享可满足读多写少的场景数据一致也保证了并发性。
方案四通过队列实现请求的串行化
在系统中维护n个任务队列读写数据时根据数据的唯一标识将请求放入不同队列。每个队列对应一个工作线程每个线程串行的执行队列中的请求。
详细可以参考缓存与数据库一致性保证
小结
引用陈浩《缓存更新的套路》最后的总结语作为小结 分布式系统里要么通过2PC或是Paxos协议保证一致性要么就是拼命的降低并发时脏数据的概率 缓存系统适用的场景就是非强一致性的场景所以它属于CAP中的APBASE理论。 异构数据库本来就没办法强一致只是尽可能减少时间窗口达到最终一致性。 还有别忘了设置过期时间这是个兜底方案 结束语
本文总结并探讨了缓存数据库双写一致性问题。
文章内容大致可以总结为如下几点 对于读多写少的数据请使用缓存。 为了保持数据库和缓存的一致性会导致系统吞吐量的下降。 为了保持数据库和缓存的一致性会导致业务代码逻辑复杂。 缓存做不到绝对一致性但可以做到最终一致性。 对于需要保证缓存数据库数据一致的情况请尽量考虑对一致性到底有多高要求选定合适的方案避免过度设计。
参考
https://blog.csdn.net/sufu1065/article/details/108459758https://blog.csdn.net/ljyhust/article/details/108649249https://mp.weixin.qq.com/s?spma2c6h.12873639.0.0.2020fe8dDvjfTJ__bizMjM5ODYxMDA5OQmid404202261idx1sn1b8254ba5013952923bdc21e0579108escene21#wechat_redirect