Redis之缓存一致性

前面的文章我们已经介绍过了缓存的三种异常(缓存击穿、缓存穿透和缓存雪崩)以及如何解决。这篇文章我们来讲解一下缓存一致性问题。

什么是缓存一致性问题?

一般情况下,Redis是用作应用程序和数据库之间读操作的缓存,主要目的是减少数据库的IO,还可以提升数据的IO性能。

当应用程序要去读取一个数据时,首先会尝试从Redis中读取,如果命中了就直接返回结果,如果没有命中,就从数据库中查询,查询到数据后再把这个数据缓存到Redis中并返回结果。

在这样的架构中,会出现一个问题,就是一份数据,同时保存在数据库和Redis中,当数据发生变化的时候,需要同时更新Redis和MySql,由于更新是有先后顺序的,这种两边写入的情况下,并不能像单纯数据库操作一样,可以满足ACID的特性。因此,就可能存在一方更新成功而另一方更新失败的情况,从而出现缓存和数据库中数据不一致的问题,也就是缓存一致性问题。

如何解决?

我们先将出现缓存一致性问题的场景进行分类,分别是先删除缓存,再更新数据库先更新数据库,再删除缓存两种场景。

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

先删除缓存,此时数据库还没有及时更新成功,此时如果有另一个线程来读取缓存中的值,由于缓存被删除,这条线程就会去数据库中进行读取,但是数据库此时还没有更新,所以读取到的还是旧值,缓存不一致问题就发生了。

解决方法------延时双删

延时双删的方案的思路是,为了避免更新数据库的时候,其他线程从缓存中读取不到数据,就在更新完数据库之后,再Sleep一段时间,然后再次删除缓存,具体的流程如下:

(1)线程1删除缓存,然后去更新数据库;

(2)线程2来读缓存,发现缓存已经被删除,所以直接从数据库中读取数据,这时候由于数据库还没有及时更新,所以读取到的还是旧值,然后把旧值写入了缓存;

(3)线程1,根据估算的时间来进行Sleep操作,Sleep醒来后,线程1再次删除缓存;

(4)如果此时还有其他线程来读取缓存的话,就会再次从数据库中读取到最新值。

存在的问题

如何取Sleep的时间其实很难进行衡量,如果时间取的过短,很有可能数据库还没有更新的时候双删操作就完成了,缓存就会一直保持旧值;如果时间取的过长,就会导致更新后的缓存又被删除了,造成重复写入缓存。

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

如果将上面的场景反过来,这个产生的问题会更加明显,就是更新数据库的操作成功了,但是缓存删除操作失败了,或者没有来得及进行缓存删除操作,这个时候其他线程去访问缓存,拿到的还是原来的旧值,缓存一致性问题出现了。

解决方案------消息队列

先更新数据库,成功之后往消息队列中发送消息,消费到消息后再删除缓存,借助消息队列的重试机制来实现缓存的删除,最终达到数据的一致性。

存在的问题

(1)引入消息中间件之后,问题更加复杂了,怎么保证消息不丢失更加麻烦;

(2)就算不考虑消息丢失的问题,由于缓存的删除不是立即进行的,所以不能保证缓存一致性的高时效性,只能保证最终的缓存一致性。

进阶版的消息队列解决方案

为了解决缓存一致性的问题单独引入一个消息队列,太复杂了。其实,一般的大公司本身都会有监听binlong消息的消息队列存在,主要是为了做一些核对的工作。

可以借助监听binlog消息的消息队列来做删除缓存的操作。这样做的好处是,不用引入一个新的消息队列到业务代码中,同时保证了高可用。

当然问题还是上面提到的问题,不过在并发量不是特别高的场景下,这种做法的实时性和一致性都还算可以接受的。

为什么是删除缓存而不是更新缓存?

就以先更新数据库,再删除缓存为例,如果不是删除缓存而是更新缓存的话,如果数据库在1个小时内更新了2000次,那么相对的缓存也要跟着更新2000次,但是在这1个小时内,缓存只被访问到了1次,那么这2000次的更新操作和1次的删除操作相比,显然是删除操作的性能更高一些。换句话说,缓存不需要实时更新,只有在用到缓存的时候在去数据库中读取新数据即可。

这篇文章我们讲解了Redis的缓存一致性问题和解决方案,大家有什么问题或者勘误的话可以在评论区留言,笔者看到都会回复的。

相关推荐
抠脚学代码3 分钟前
Ubuntu Qt x64平台搭建 arm64 编译套件
数据库·qt·ubuntu
利刃大大18 分钟前
【高并发内存池】五、页缓存的设计
c++·缓存·项目·内存池
jakeswang28 分钟前
全解MySQL之死锁问题分析、事务隔离与锁机制的底层原理剖析
数据库·mysql
Heliotrope_Sun42 分钟前
Redis
数据库·redis·缓存
一成码农1 小时前
MySQL问题7
数据库·mysql
吃饭最爱1 小时前
JUnit技术的核心和用法
数据库·oracle·sqlserver
专注API从业者1 小时前
Python/Java 代码示例:手把手教程调用 1688 API 获取商品详情实时数据
java·linux·数据库·python
雨落Liy1 小时前
SQL 函数从入门到精通:原理、类型、窗口函数与实战指南
数据库·sql
Kt&Rs2 小时前
MySQL复制技术的发展历程
数据库·mysql
小小菜鸡ing2 小时前
pymysql
java·服务器·数据库