一手房发帖网站怎样做,wordpress阿里图标库,网页搜索能力属于专业技术素养,网页制作模板的作用前言
缓存设计是应用系统设计中重要的一环#xff0c;是通过空间换取时间的一种策略#xff0c;达到高性能访问数据的目的#xff1b;但是缓存的数据并不是时刻存在内存中#xff0c;当数据发生变化时#xff0c;如何与数据库中的数据保持一致#xff0c;以满足业务系统…前言
缓存设计是应用系统设计中重要的一环是通过空间换取时间的一种策略达到高性能访问数据的目的但是缓存的数据并不是时刻存在内存中当数据发生变化时如何与数据库中的数据保持一致以满足业务系统要求本篇将给出具体分析。 一致性分类
强一致性这种一致性级别是最符合用户直觉的它要求系统写入什么读出来的也会是什么用户体验好但实现起来往往对系统的性能影响大这种情况比如秒杀系统商家后台他会设置秒杀商品参与秒杀活动一旦说他参与了秒杀活动商品的库存本来是在数据库里的此时必须直接被加载到缓存里缓存立马就要可以被使用。弱一致性这种一致性级别约束了系统在写入成功后不承诺立即可以读到写入的值也不承诺多久之后数据能够达到一致但会尽可能地保证到某个时间级别比如秒级别后数据能够达到一致状态最终一致性最终一致性是弱一致性的一个特例系统会保证在一定时间内能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来是因为它是弱一致性中非常推崇的一种一致性模型也是业界在大型分布式系统的数据一致性上比较推崇的模型比如微博的粉丝数页面每天的访问数
缓存更新机制
缓存的更新一般分为被动更新与主动更新被动更新是指缓存在有效期到后被淘汰。
被动更新如下步骤step1: 发起方查数据缓存中没有从数据库中获取并写入缓存同时设置过期时间 tstep2: 在 t 内所有的查询都由缓存提供所有的写直接写数据库step3: 当缓存数据到过期时间 t 后缓存数据失效。后面的查询回到了第 1 步。
主动更新一般为调用方发起缓存与数据库同时更新缓存分为删除、更新数据库分为更新通过组合与先后顺序分为如下四种情况更新缓存、更新数据库更新数据库更新缓存删除缓存更新数据库更新数据库删除缓存
更新缓存、更新数据库
这种情况当缓存更新成功数据库更新不成功时数据不一致的风险比较高所以一般不采用。
更新数据库、更新缓存
当更新完数据库缓存的加载前需要通过大量复杂计算才能得出缓存的值不仅让发起方阻塞影响性能而且如果缓存命中率不高很少使用更浪费前期的复杂计算成本与缓存空间这里就不符合懒加载的设计思想故一般也不采用。
删除缓存、更新数据库
如图所示当两个调用方线程高并发访问的情况下A 线程先删除缓存再更新数据库此过程时间较长B 线程在 A 删除缓存后迅速读取缓存因缓存每命中从数据库中读取再加载缓存此时缓存还是旧值等 A 线程更新完数据库后发现又出现数据不一致的现象。 一般大概率情况下出现此根源的原因是读比写快所以这种一般也不采用如果非得采用需要在写完数据库之后延迟一段时间再删除一次缓存也就是我们熟知的 延时双删延迟多久呢一般看数据库的更新时长来决定此做法也会带来系统吞吐量下降。
Cache-Aside
也叫做旁路缓存模式流程就是先更新数据库再删除缓存虽然这种方式也会带来不一致的情况比如如下场景 前提缓存无数据数据库有数据。A查询B更新过程如下step1: A 查缓存无数据去读数据库旧值step2: B 更新数据库为新值step3: B 删除缓存step4: A 将旧值写入缓存。
该场景最终也会出现不一致产生的根源是是读比写慢这种是小概率事件一般很少出现如果非要解决这种情况还是上面说的延迟双删
Read/Write Through
上面的方式数据库是缓存的来源主导是数据库而 Read/Write Through模式相当于缓存占主导。在 cache-aside 模式中我们的应用代码需要维护两个数据存储一个是缓存Cache一个是数据库Repository。而 Read/Write Through 做法是把更新数据库Repository的操作由缓存自己代理了所以对于应用层来说就简单很多了。可以理解为应用认为后端就是一个单一的存储而存储自己维护自己的 Cache。
Read Through
Read Through 就是在查询操作中更新缓存也就是说当缓存失效的时候过期或 LRU 换出Cache Aside 是由调用方负责把数据加载入缓存而 Read Through 则用缓存服务自己来加载从而对应用方是透明的。 这个简要流程和 Cache-Aside 很像其实Read-Through就是多了一层Cache-Provider流程如下 Read-Through 实际只是在Cache-Aside之上进行了一层封装它会让程序代码变得更简洁同时也减少数据源上的负载 Write Through
Write Through和 Read Through 相仿不过是在更新数据时发生。当有数据更新的时候如果没有命中缓存直接更新数据库然后返回。如果命中了缓存则更新缓存然后再由 Cache 自己同步更新数据库 值得注意的是该方案在实现过程中程序启动时需将数据库的数据 提前放到缓存中不能等启动完成再放缓存中。
Read Through/Write Through 策略的特点是由缓存节点而非应用程序来和数据库打交道在我们开发过程中相比 Cache Aside 策略要少见一些原因是我们经常使用的分布式缓存组件无论是 Memcached 还是 Redis 都不提供写入数据库和自动加载数据库中的数据的功能。而我们在使用本地缓存的时候可以考虑使用这种策略。
Write Behind
Write Behind 又叫 Write Back。底层思想就是在更新数据的时候只更新缓存不更新数据库而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的 I/O 操作速度飞快因为是直接操作内存同时带来吞吐量大幅上升因为异步Write Behind 还可以合并对同一个数据的多次操作所以性能的提高是相当可观的。 但是其带来的问题是数据不是强一致性的而且可能会丢失我们知道 Unix/Linux 非正常关机会导致数据丢失就是因为这个事。在软件设计上我们基本上不可能做出一个没有缺陷的设计就像算法设计中的时间换空间空间换时间一个道理有时候强一致性和高性能高可用和高性性是有冲突的。如果说软件功能模块的思维是逻辑与实现那么软件架构设计的思维是权衡与取舍。 这种方式下缓存和数据库的一致性不强对一致性要求高的系统要谨慎使用。但是它适合频繁写的场景MySQL 的InnoDB Buffer Pool 机制就使用到这种模式。 Write Behind 实际应用
按照实际架构应用层面落地参考方案流程图如下以用户发表视频为例 按照这种方案正常来说只要 10 分钟内数据正常写完就没问题可以实现最终一致性但是一旦超出 10 分钟就会缓存失效造成缓存不一致
如果出现这种 kafka 消费入库失败则会触发报警系统看具体是什么问题基本上 kaka 入库失败只有两种情况一种是出错了另外一种是消费能力不够那么直接扩容即可
后记
说到底这个问题根本没有通用方案需要根据场景做权衡比如类似于微博这样高并发场景那么上边只要涉及删除缓存的方案基本都很难实现因为很可能删除缓存的下一时间热点数据直接全部打在数据库上整个服务直接崩溃
然后本质上缓存和数据库的更新就不是一个原子操作想要彻底实现强一致性可以了解下分布式事务或是其他强一致性协议比如说两阶段提交协议 —— prepare, commit/rollback比如 Java 7 的 XAResource还有 MySQL 5.7 的 XA Transaction有些 cache 也支持 XA比如 EhCache
参考链接
美团二面Redis 与 MySQL 双写一致性如何保证 - 掘金 (juejin.cn)分布式缓存--缓存与数据库一致性方案 - 小猪爸爸 - 博客园 (cnblogs.com)如何保障 MySQL 和 Redis 的数据一致性 | 二哥的 Java 进阶之路 (javabetter.cn)三种缓存策略Cache Aside 策略、Read/Write Through 策略、Write Back 策略分布式系统中的缓存与数据库一致性 本文由博客一文多发平台 OpenWrite 发布