聊聊数据库和缓存一致性的几种实现方式

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,经济版 1GB 1个月
简介:

云栖号资讯:【点击查看更多行业资讯
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!


缓存是互联网高并发系统里常用的组件,由于多增加了一层,如果没有正确的使用效果可能适得其反,诸如“缓存是删除还是更新?”,“先操作数据库还是先操作缓存?”都是些老生常谈的话题,今天我们就来聊一聊缓存与数据库的双写一致性的解决方案。

Cache Aside Pattern

在一开始先科普下最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。

  • 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
  • 更新的时候,先更新数据库,然后再删除缓存。

1

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

更新缓存在并发下会带来种种问题,直接删除缓存比较简单粗暴,稳妥。而且还有懒加载的思想,等用到的时候在去数据库读出来放进去,不用到你每次去更新他干嘛,浪费时间资源,而且还有更新失败、产生脏数据的一些风险, 达成这一点共识以后,我们来开始今天的讨论。

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

1、更新数据库成功,删除缓存成功,没毛病。

2、更新数据库失败,程序捕获异常,不会走到下一步,不会出现数据不一致情况。

3、更新数据库成功,删除缓存失败。数据库是新数据,缓存是旧数据,发生了不一致的情况。这里我们来看下怎么解决

  • 重试的机制,如果删除缓存失败,我们捕获这个异常,把需要删除的key发送到消息队列,然后自己创建一个消费者消费,尝试再次删除这个 key。
  • 异步更新缓存,更新数据库时会往 binlog 写入日志,所以我们可以通过一个服务来监听 binlog的变化(比如阿里的 canal),然后在客户端完成删除 key 的操作。如果删除失败的话,再发送到消息队列。

总之,我们要达到最终一致性!

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

1、删除缓存成功,更新数据库成功,没毛病。

2、删除缓存失败,程序捕获异常,不会走到下一步,不会出现数据不一致情况。

3、删除缓存成功,更新数据库失败,此时数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。

虽然没有发生数据不一致的情况,看上去好像一切都很完美,但是以上是在单线程的情况下,如果在并发的情况下可能会出现以下场景

1)线程 A 需要更新数据,首先删除了 Redis 缓存

2)线程 B 查询数据,发现缓存不存在,到数据库查询旧值,写入 Redis,返回

3)线程 A 更新了数据库

2

这个时候,Redis是旧的值,数据库是新的值,还是发生了数据不一致的情况。

延时双删

针对上面这种情况,我们有一种延时双删的方法

1)删除缓存

2)更新数据库

3)休眠 500ms(这个时间,依据读取数据的耗时而定)

4)再次删除缓存

3

你把旧值存在Redis以后,过一段时间我在删除一次,这时把旧值给删掉了,这样就能保证Redis和数据库是同步的了,这么做在一定程度上可以缓解这个问题,但也不是十分完美,比如第一次缓存删除成功了,第二次缓存删除失败,又该怎么办?

内存队列

除了延时双删这个方法,还有个方案就是内存队列,他的思想是串行化,我们在JVM中维护一个内存队列。当更新数据的时候,我们不直接操作数据库和缓存,而是把数据的Id放到内存队列;当读数据的时候发现数据不在缓存中,我们不去数据库查放到缓存中,而是把数据的Id放到内存队列。

后台会有一个线程消费内存队列里面的数据,然后一条一条的执行。这样的话,一个更新数据的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,读到了空的缓存,那么先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。

这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。

等内存队列中将更新数据的操作完成之后,才会去执行下一个操作,也就是读数据的操作,此时会从数据库中读取最新的值,然后写入缓存中。

如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取。

总结

上面说的几种方案,都是比较常见的,也比较简单,没有十全十美的,最后的内存队列也会影响性能以及增加系统的复杂度。今天讨论的Redis和数据库的数据更新是不可能通过事务达到统一的,什么叫做事务,就是一损俱损一荣俱荣,要么都成功要么都失败,这是不能保证的。

我们只能根据相应的场景和所需要付出的代价来采取一些措施,降低数据不一致的问题出现的概率,在数据一致性和性能之间取得一个权衡,具体场景具体使用。

【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/live

立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

原文发布时间:2020-06-09
本文作者:jack_xu
本文来自:“掘金”,了解相关信息可以关注“掘金”

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
22天前
|
缓存 NoSQL Java
Redis 缓存与数据库数据不一致问题
Redis 缓存与数据库数据不一致问题
48 3
|
6天前
|
关系型数据库 Serverless 分布式数据库
揭秘PolarDB Serverless:大促洪峰秒级应对,无感伸缩见证科技魔法!一探云数据库管理的颠覆性革新,强一致性的守护神来了!
【8月更文挑战第13天】在云计算背景下,阿里巴巴的云原生数据库PolarDB Serverless针对弹性伸缩与高性能一致性提供了出色解决方案。本文通过一个电商平台大促活动的真实案例全面测评PolarDB Serverless的表现。面对激增流量,PolarDB Serverless能秒级自动扩展资源,如通过调用`pd_add_reader`快速增加读节点分摊压力;其无感伸缩确保服务平滑运行,不因扩展中断;强一致性模型则保障了数据准确性,即便在高并发写操作下也确保库存等数据的同步一致性。PolarDB Serverless简化了数据库管理,提升了系统效能,是追求高效云数据库管理企业的理想选择。
28 7
|
5天前
|
缓存 NoSQL Redis
一天五道Java面试题----第九天(简述MySQL中索引类型对数据库的性能的影响--------->缓存雪崩、缓存穿透、缓存击穿)
这篇文章是关于Java面试中可能会遇到的五个问题,包括MySQL索引类型及其对数据库性能的影响、Redis的RDB和AOF持久化机制、Redis的过期键删除策略、Redis的单线程模型为何高效,以及缓存雪崩、缓存穿透和缓存击穿的概念及其解决方案。
|
26天前
|
缓存 NoSQL 数据库
Redis问题之在高并发场景下,保证Redis缓存和数据库的一致性如何解决
Redis问题之在高并发场景下,保证Redis缓存和数据库的一致性如何解决
|
4天前
|
存储 缓存 NoSQL
基于SpringBoot+Redis解决缓存与数据库一致性、缓存穿透、缓存雪崩、缓存击穿问题
这篇文章讨论了在使用SpringBoot和Redis时如何解决缓存与数据库一致性问题、缓存穿透、缓存雪崩和缓存击穿问题,并提供了相应的解决策略和示例代码。
13 0
|
27天前
|
canal 消息中间件 缓存
面试题:如何解决缓存和数据库的一致性问题?
面试题:如何解决缓存和数据库的一致性问题?
36 1
|
11天前
|
缓存 监控 Go
[go 面试] 缓存策略与应对数据库压力的良方
[go 面试] 缓存策略与应对数据库压力的良方
|
5天前
|
存储 关系型数据库 MySQL
MySQL——数据库备份上传到阿里云OSS存储
MySQL——数据库备份上传到阿里云OSS存储
18 0
|
8天前
|
SQL 存储 关系型数据库
"MySQL增列必锁表?揭秘InnoDB在线DDL,让你的数据库操作飞一般,性能无忧!"
【8月更文挑战第11天】在数据库领域,MySQL凭借其稳定高效的表现深受开发者喜爱。对于是否会在给数据表添加列时锁表的问题,MySQL的行为受版本、存储引擎等因素影响。从5.6版起,InnoDB支持在线DDL,可在改动表结构时保持表的可访问性,避免长时间锁表。而MyISAM等则需锁表完成操作。例如,在使用InnoDB的表上运行`ALTER TABLE users ADD COLUMN email VARCHAR(255);`时,通常不会完全锁表。虽然在线DDL提高了灵活性,但复杂操作或大表变更仍可能暂时影响性能。因此,进行结构变更前应评估其影响并择机执行。
29 6
|
7天前
|
Oracle 关系型数据库 MySQL
Mysql和Oracle数据库死锁查看以及解决
【8月更文挑战第11天】本文介绍了解决MySQL与Oracle数据库死锁的方法。MySQL可通过`SHOW ENGINE INNODB STATUS`查看死锁详情,并自动回滚一个事务解除死锁;也可手动KILL事务。Oracle则通过查询V$LOCK与V$SESSION视图定位死锁,并用`ALTER SYSTEM KILL SESSION`命令终止相关会话。预防措施包括遵循ACID原则、优化索引及拆分大型事务。