序11月,朋友去美团面试。他说被问到如何保证Redis和MySQL的双写一致性?这个问题其实是在问双写场景下如何保证缓存和数据库的一致性?本文将与您探讨如何回答这个问题。谈论一致性一致性意味着保持数据一致。在分布式系统中,可以理解为多个节点中数据的值是一致的。强一致性:这种级别的一致性最符合用户的直觉。它要求系统写入的内容将是它读出的内容。用户体验是好的,但是实现往往对系统性能有很大的影响。弱一致性:这种一致性级别约束系统不承诺写入成功后立即可以读取写入的值,也不承诺数据多久达到一致性,但会尽量保证一定的时间级别(如第二层)最终数据可以达到一致状态最终一致性:最终一致性是弱一致性的特例,系统会保证在一定时间内数据可以达到一致状态。之所以在这里提出最终一致性,是因为它是弱一致性中备受推崇的一致性模型,也是业界推崇的大型分布式系统数据一致性中的模型。三种经典的缓存模式缓存可以提升性能,缓解数据库压力,但是使用缓存也会造成数据不一致。我们一般如何使用缓存呢?三种经典的缓存模式:Cache-AsidePatternRead-Through/WritethroughWritebehindCache-AsidePatternCache-AsidePattern,即旁路缓存模式,提出来尽可能解决缓存和数据库之间的数据不一致问题.Cache-Aside读流程Cache-AsidePattern的读请求流程如下:读时,先读缓存。如果缓存命中,则直接返回数据。在返回响应时。Cache-AsideWriteProcessCache-AsidePattern的写请求流程如下:更新时,先更新数据库,再删除缓存。Read-Through/Write-Through(Read-Through)在Read/WriteThrough模式下,服务器使用缓存作为主要的数据存储。应用程序与数据库缓存的交互是通过抽象缓存层完成的。Read-ThroughRead-Through的简要流程如下:从缓存中读取数据,读取后直接返回。如果无法读取,则从数据库中加载,写入缓存,然后返回响应。这个简短的过程是不是类似于Cache-Aside?Read-Through其实是在Cache-Provider的基础上多了一层。过程如下:Read-Through其实只是在Cache-Aside之上进行了一层封装,会让程序代码更加简洁,减少数据源。加载。Write-ThroughWrite-Through模式,当有写请求发生时,缓存抽象层同时完成数据源和缓存数据的更新。流程如下:Writebehind(异步缓存写)Writebehind和Read-Through/Write-Through类似的地方,CacheProvider负责缓存和数据库读写。两者有很大区别:Read/WriteThrough同步更新缓存和数据,而WriteBehind只更新缓存不直接更新数据库,而是批量异步更新数据库。这样缓存和数据库的一致性不强,对一致性要求高的系统慎用。但是适合频繁写入的场景,MySQL的InnoDBBufferPool机制就是采用这种模式。操作缓存时,应该删除缓存还是更新缓存?在一般的业务场景中,我们使用Cache-Aside模式。可能有朋友会问,为什么Cache-Aside在写请求的时候会删除缓存,而不是更新缓存呢?我们在操作缓存的时候,是删除缓存还是更新缓存呢?我们来看一个例子:线程A首先发起写操作,第一步是更新数据库线程B然后发起写操作,第二步更新数据库由于网络等原因,线程B先更新缓存线程A更新缓存。此时缓存保存的是A的数据(旧数据),数据库保存的是B的数据(新数据),数据不一致,出现脏数据。如果删除缓存而不是更新缓存,就不会出现这种脏数据问题。与删除缓存相比,更新缓存有两个缺点:如果你写的缓存值是经过复杂的计算得到的。如果更新缓存的频率很高,就会浪费性能。在数据库写入场景多,数据读取场景少的情况下,数据往往先更新再读取,这样也比较浪费性能(其实在写入多的场景下使用缓存并不是很划算)的情况双写,是先操作数据库还是先操作缓存?在Cache-Aside缓存模式下,有小伙伴还是有疑惑的。为什么写请求的时候先操作数据库呢?为什么不先操作缓存呢?假设有两个请求A和B,请求A执行更新操作,请求B执行查询和读取操作。线程A发起写操作,第一步是delcache,此时线程B发起读操作,cachemiss线程B继续读DB,读出一个旧数据,然后线程B将旧数据置入缓存ThreadA写最新的DBdatasauce有问题,缓存中的数据和数据库不一致。缓存存储旧数据,数据库存储新数据。所以Cache-Aside缓存模式选择先操作数据库,而不是先操作缓存。缓存延时双删可能有朋友会说,不用先操作数据库,直接使用缓存延时双删策略?什么是延迟双删?先删除缓存,然后更新数据库休眠一段时间(比如1秒),再删除缓存。这要睡多久?全部1秒?这个休眠时间=读取业务逻辑数据的时间+几百毫秒。为了保证读请求结束,写请求可以删除读请求可能带来的缓存脏数据。删除缓存重试机制无论是延迟双删除还是Cache-Aside,都是先操作数据库再删除缓存,如果第二步删除缓存失败,删除失败会导致数据脏~多删几次如果删除失败,保证删除缓存成功~所以可以引入删除缓存重试机制,写一个更新数据库缓存的请求。由于某些原因,如果删除失败,则将删除失败的key放入消息队列中消费消息队列中的消息,获取要删除的key重试删除缓存。操作读取biglog异步删除缓存并重试删除缓存机制,但是会造成大量业务代码入侵。其实也可以通过数据库的binlog异步淘汰key。以mysql为例,可以使用阿里的canal将binlog日志集合发送到MQ队列,然后通过ACK机制确认并处理这条更新消息,删除缓存,保证数据缓存的一致性
