场景分析:
到底是先更新数据库还是先更新缓存呢?
- 随着互联网的高速发展,互联网系统架构也已经由最初的单体架构转变为分布式、微服务架构模式。从数据体量上来看,各系统存储的数据量越来越大,数据的查询性能越来越低。此时,就需要我们不断的进行优化,一种常用的优化手段就是引入缓存。而引入缓存后,我们在向数据库插入数据时,到底是先更新数据库还是先更新缓存呢?
- MySQL和Redis的关系:MySQL 是数据库,用来持久化数据,一定程度上保证数据的可靠性;Redis 是用来当缓存,用来提升数据访问的性能。
- 关于如何保证MySQL和Redis中的数据一致性(即缓存一致性问题),这一直是个经典问题。
缓存
缓存的定义,一般使用,数据不一致的原因?
- 缓存,从本质上讲,是为了更好的协调两个速度差异比较大的组件而引入的一种中间缓存层。数据读入CPU进行计算处理的效率非常快,磁盘的IO处理速度慢,可以通过内存来缓和CPU和磁盘之间的速度差异。
- 一般使用方式:将数据放入了缓存,最多为缓存设置一个过期时间,到期后,缓存自然就会被清除,后续的请求由于在缓存中获取不到数据,又会从数据库中获取数据,将数据写入缓存。
- 数据不一致的原因:通常缓存不一致是发生在数据有变更的时候。因为每次数据变更你需要同时操作数据库和缓存,而他们又属于不同的系统,无法做到同时操作成功或失败,总会有一个时间差。在并发读写的时候可能就会出现缓存不一致的问题。
缓存更新-策略
从理论上,给缓存设置过去时间,其实是一种更新一致性的表现
在此方案下,可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。这也是一般情况下,使用的最多的一种方式。
- 先更新数据库在更新缓存
例如: A和B线程同时进行更新操作,数据与缓存的数据的不一致。
1.线程A更新了数据库
2.线程B更新了数据库
3.线程B更新了缓存
4.线程A更新了缓存
总结:1.数据库写多读少的场景,频繁更新,大大浪费服务器的性能。2.数据库的数据不是直接写入缓存,需要大量的复杂运算,将运算结果写入缓存,这种策略,也会造成服务器资源的浪费。
- 先删除缓存在更新数据库
例如: 线程A更新缓存,同时,线程B读取缓存的数据。可能会出现下面的执行顺序。
1.线程A删除缓存
2.线程B查询缓存,发现缓存中没有想要的数据
3.线程B查询数据库中的旧数据
4.线程B将查询到的旧数据写入缓存
5.线程A将新数据写入数据库
总结:如果删除缓存失败,出现数据不一致的现象。
- 先更新数据库在删除缓存
极小概率数据不一致的情况,例如: 线程A做查询操作,线程B执行更新操作,其执行的顺序如下所示。
1.缓存刚好失效
2.请求A查询数据库,获取到数据库中的旧值
3.请求B将新值写入数据库
4.请求B删除缓存
5.请求A将查到的旧值写入缓存
总结:如果删除缓存失败,也会出现数据库数据和缓存数据不一致的现象。
- 这样说来,貌似三种方案都不安全呀,那我们该如何做呢?最终要的就是需要引入重试机制。
推荐使用:
实际生产环境种,推荐–先更新数据库再删除缓存,可使用以下方案解决数据不一致的情况:有两种方案:
- 在程序业务逻辑中处理失败重试的操作;
1.更新数据库数据,2.删除缓存数据失败,3.将需要删除的key发送至消息队列,4.自己消费信息,获得需要删除的key,5.继续重试删除操作,直至执行成功。
2.这种方案有一个缺点,对业务线代码造成大量的侵入。
- 借助于阿里巴巴开源的Canal。
1.更新数据库操作,2。数据库的变更信息写入Binlog中,3.订阅程序获取所需要的数据以及key,4.程序逻辑中处理具体的业务逻辑,接收订阅binlog、发起删除缓存的请求,5.尝试删除缓存操作,发现删除失败,6.将这些信息发送至消息队列,7.重新从消息队列中获得该数据,重试操作。
小结
提示:一人智短,众人智长,愿团结学习赢未来,让学习之路更宽广。