「一发入魂」Redis缓存与数据库数据一致性问题及解决方案

本章内容

在分布系统中,一般会使用Redis缓存来提高数据读写性能,减轻数据库的访问压力,但是Redis与数据库分属于不同的系统,就可能出现缓存与数据库中数据不一致的问题。

缓存引入

在小型业务系统中,由于并发量不高、数据量小,因此,一般直接操作数据库即可。

随着业务的增长,并发量和数据量也会随之增加,频繁访问数据可能会影响数据库的读写性能,因此,可以通过引入缓存来提高数据读写性能。具体做法是将数据库中的数据全量加载到Redis缓存中,读取数据时从缓存中读取。

如图所示:

处理流程:

该方案的优点是性能高,缺点:

针对以上缺陷,很自然就想到以下解决方案,即:

缓存利用率问题

要想提高缓存数据的利用率,就需要动态更新缓存,及时淘汰无效的缓存数据。具体做法是设置缓存数据过期时间,并及时更新缓存数据。

如图所示:

处理流程:

这样,无效数据会随着时间的推移逐渐被淘汰,热点数据也会得到及时更新。

数据一致性问题

要想保证缓存数据与数据库数据的一致性,就需要更新数据库中的数据时,同步更新缓存数据。常见的缓存数据更新策略有两种:

更新数据库+更新缓存

根据数据库与缓存的操作顺序,可分为两种方案:

正常情况,二者没有差别,都能保证缓存数据与数据库数据的一致性。数据一致性问题主要发生在第一步执行成功,第二步执行失败的场景。

无并发场景

先更新缓存,再更新数据库

在更新缓存成功、更新数据库失败的场景中,读取到的数据为更新后的最新值,但数据库中存储的是更新前的旧值,造成数据一致性问题。一旦缓存失效,就会从数据库中加载得到更新前的旧值,对业务造成影响。

先更新数据库,再更新缓存

在更新数据库成功、更新缓存失败的场景中,读取到的数据为更新前的旧值,而数据库中存储的是更新后的最新值,造成数据一致性问题。缓存失效后,会从数据库中加载得到更新后的最新值,对业务造成短暂影响。

高并发场景

线程A和线程B对同一条数据X进行操作时,会存在以下情况:

由此可见,无论是先更新数据库还是先更新缓存,都有可能造成缓存数据与数据库数据的不一致,此外,每次更新数据都同步更新缓存,不仅会影响Redis中其他数据的写入性能,还会造成内存资源的浪费(如:数据更新到缓存,但是很少被使用)。因此,一般不建议使用更新数据库+更新缓存的策略。

更新数据库+删除缓存

与更新数据库+更新缓存类似,根据数据库与缓存的操作顺序,可分为两种方案:

正常情况,二者没有差别,都能保证缓存数据与数据库数据的一致性。数据一致性问题主要发生在第一步执行成功,第二步执行失败的场景。

无并发场景

先删除缓存,再更新数据库

在删除缓存成功、更新数据库失败的场景中,读取到的数据为数据库中未更新成功的旧值,造成数据一致性问题(即:期望值为更新后的最新值,返回值为更新前的旧值)。对业务造成影响。

先更新数据库,再删除缓存

在更新数据库成功、删除缓存失败的场景中,读取到的数据为更新前的旧值,而数据库中存储的是更新后的最新值,造成数据一致性问题。缓存失效后,读取数据时会从数据库中加载得到更新后的最新值,对业务造成短暂影响。

高并发场景

线程A和线程B对同一条数据X进行操作时,会存在以下情况:

由此可见,无论是先更新数据库还是先删除缓存,都有可能造成缓存数据与数据库数据的不一致。一般建议使用先更新数据库、再删除缓存的策略,原因是先删除缓存、再更新数据库策略有可能因缓存缺失而直接访问数据库(即:缓存穿透),给数据库带来压力。

数据一致性问题解决方案

通过前面的场景分析,无论是更新数据库+更新缓存还是更新数据库+删除缓存,只要第二步执行失败,就有可能导致缓存数据与数据库数据的不一致。因此,解决数据不一致问题的关键是保证第二步执行成功,常用的解决方案是:异步重试。

异步重试目前比较主流的方案有两种:

基于MQ的异步重试方案

如图所示:

处理流程:

基于Canal+MQ的异步重试方案

如图所示:

处理流程:

分布式锁方案

在分析更新数据库+更新缓存的高并发场景时,会存在因多个线程并发执行的顺序而导致数据不一致的问题。这种情况可以通过加分布式锁来保证同一时刻只有一个线程对数据进行操作的方式来解决。

处理流程:

注意:使用加分布式锁的方式处理数据一致性问题时,会对处理性能造成一定影响。

延时双删方案

在分析更新数据库+删除缓存的高并发场景时,以下两种情况有可能造成数据不一致:

以上两种情况可以通过延迟双删的方式来进行处理。

处理流程:

延时时长设置:

具体延时时长很难评估,一般根据测试结果和过往经验值进行综合评估,如:延时1 5秒。

强一致性策略

以上数据一致性问题解决方案只能保证数据的最终一致性。要想做到强一致,最常见的方案是使用2PC、3PC、Paxos、Raft这类一致性协议,但它们的性能往往比较差,而且实现方案复杂,还需要考虑各种容错问题。如果业务要求必须读取数据的强一致性,可以采用加分布式锁的方式实现,处理流程->上面的「分布式锁方案」。

总结

1)通过引入缓存的方式来提供应用数据读写性能。

2)引入缓存后会存在缓存利用率和数据一致性问题:

3)及时更新缓存数据有两种策略:

4)更新数据库+更新缓存数据一致性问题解决方案:

5)更新数据库+删除缓存数据一致性问题解决方案:

【阅读推荐】

更多精彩内容,如:

请移步【南秋同学】个人主页进行查阅。内容持续更新中......

【作者简介】

一枚热爱技术和生活的老贝比,专注于Java领域,关注【南秋同学】带你一起学习成长

展开阅读全文

页面更新:2024-03-11

标签:缓存   数据库   数据   分布式   线程   场景   解决方案   性能   方式   时间   方案

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top