在云服务中,缓存极其重要。所谓缓存,其实就是一个高速数据存储层。当缓存存在时,以后再次请求数据时,会直接访问缓存,提高数据访问速度。但是缓存中存储的数据通常是短暂的,需要经常更新缓存。而我们操作缓存和数据库,分为读操作和写操作。读操作的详细过程是请求数据。如果缓存中有数据,则直接读取并返回。如果不存在,将从数据库中读取。成功后会将数据放入缓存中。写操作分为以下四种:先更新缓存,再更新数据库,先更新数据库,再更新缓存,先删除缓存,再更新数据库,先更新数据库,再删除缓存对于一些不需要高一致性的数据,比如like数据等,可以先更新缓存,然后定时同步到数据库。在其他情况下,我们通常会等待数据库操作成功后再操作缓存。下面主要介绍数据库更新成功后更新缓存和删除缓存的区别和改进方案。先更新数据库,再删除缓存先更新数据库,再删除缓存。这种模式也叫cacheaside,是目前比较流行的处理缓存数据库一致性的方法。它的优点是:数据不一致的概率极低,实现简单。因为没有更新缓存,而是删除了缓存,在并发写入和写入的情况下,不会出现数据不一致的情况。并发读写时出现数据不一致。场景中,具体可以看下图:这种情况出现的概率比较低,肯定是在一定的时间间隔内同时有两次或多次写入和多次读取,所以大部分业务都容忍这种小概率不一致。虽然发生的概率很低,但还是有一些方案可以减少影响甚至更小。优化方案第一种方案是:使用较短的过期时间来减少影响。这种方式有两个缺点:删除后,读请求会丢失。如果缓存不一致,不一致的时间取决于过期时间的设置。第二种方案是采用延迟双删策略,例如:1分钟后删除缓存。这种做法也有两个缺点:删除缓存前的时间可能不一致。删除后,读取请求会丢失。第三种方案是双更新策略,类似于延迟双删除策略。不同的是这个方案并没有删除缓存而是更新缓存,所以读请求不会遗漏。但是另一个缺点仍然存在。先更新数据库,再更新缓存与先更新数据库,再删除缓存的操作相比,先更新数据库,再更新缓存的操作,可以避免用户请求直接命中数据库,反过来导致缓存穿透的问题。这个解决方案是更新缓存。我们需要注意并发读写和并发写入场景导致的数据不一致。我们来看看并发读写的情况。步骤如下图所示:可以看到,由于第4步和第5步都被缓存了,如果第4步发生在第5步之前,旧值会覆盖新值。那就是缓存不一致的情况。这种情况只需要修改第5步即可解决。优化方案可以通过第五步中的addcache代替setcache,在redis中使用setnx命令进行优化。修改步骤示意图如下:解决了并发读写场景导致的数据不一致问题,我们再来看并发读写场景导致的数据不一致问题。不一致的情况如下图所示。线程A先于线程B更新完DB,但是线程B先更新完缓存,导致缓存被线程A的旧值覆盖,也有办法优化这种情况。这里介绍两种主流的方法:使用分布式锁,使用版本号,使用分布式锁。解决并发读写问题,第一个思路就是消除并发写。使用分布式锁,排队写操作执行,理论上可以解决并发写的问题,但是目前还没有可靠的分布式锁实现。无论分布式锁是基于Zookeeper、etcd还是redis实现的,为了防止程序挂了锁无法释放,我们都会为锁设置租约/过期时间。想象一个场景:如果进程卡死几分钟(虽然概率很低),导致锁失效,其他线程获取了锁。这时候又出现了并发读写的场景,还是有可能造成数据不一致。使用版本号并发写入造成的数据不一致是因为低版本覆盖了高版本。那么我们就可以找到一种方法来防止这种情况发生。一个可行的方案是引入版本号。如果写入的数据低于当前版本号,覆盖将被丢弃。缺点:在应用层维护版本的成本非常高,难以大规模实施修改数据模型。每增加一个版本,都需要修改,让版本自增。无论是更新缓存还是删除缓存,优化后数据不一致的概率都会降低。至底部。但是有没有一种方法既简单又不会造成数据不一致呢?下面介绍一下Rockscache。Rockscache简介Rockscache也是一种保持缓存一致性的方法。它采用的缓存管理策略是:更新数据库后,将缓存标记为已删除。主要通过以下两个方法实现:Fetch函数实现了之前的查询缓存TagAsDeleted函数实现了标签删除的逻辑。在运行时,只要在读取数据时调用Fetch,更新数据库后调用TagAsDeleted,就可以保证缓存的最终一致性。该策略有4个特点:无需引入版本,几乎可以适用于所有的缓存场景架构同“更新DB后删除缓存”,无额外负担,性能高:变化只是替换原来的GET/SET/DELETELua脚本的强一致性方案性能也很高。和常见的防缓存击穿方案一样,在Rockscache策略中,缓存中的数据是一个包含几个字段的hash:value:数据本身lockUtil:数据锁的过期时间,当进程查询缓存没有数据时,它首先对缓存进行短时间的锁定,然后查询DB,然后更新缓存所有者:datalocker的uuid证明,因为Rockscache方案不更新缓存,所以只需要保证一致性即可并发读写数据。我们来看看Rockscache是??如何解决数据不一致问题的。首先回顾一下cacheaside模式导致数据不一致的原因。下面结合cacheaside模式下数据不一致的场景,说说Rockscache是??怎么解决的。我们要解决的核心问题是防止旧值被写入缓存。Rockscache的解决方案如下:查询请求,如果无法读取缓存中的数据,还需要做另外一个操作:锁定缓存,为key设置一个uuid(代码示例:https://github.com/dtm-labs/r...)写入删除缓存的请求时,需要删除锁(代码示例:https://github.com/dtm-labs/r...)设置缓存为读时request,通过uuid对比,发现不是你加锁,说明有写请求更新数据,则放弃修改缓存(代码示例:https://github.com/dtm-labs/r...)至此我们完成了rockscache策略缓存更新。但是,和其他的缓存更新策略一样,我们都默认缓存操作成功后,数据库操作成功。但这是错误的。即使在实际操作过程中成功操作了数据库,缓存操作也有可能失败。因此可以通过以下三种方式来保证缓存更新成功:本地消息表监听binlogdtm的二阶段消息除了缓存更新,Rockscache还有以下两个作用:防止缓存崩溃和防止渗透和缓存雪崩。这些都是非常实用的功能。我建议您尝试一下。参考资料https://dtm.pub/app/cache.htmlhttps://smartkeyerror.oss-cn-...
