随着互联网的高速发展,使用互联网产品的人也越来越多,团队不可避免得也会面对越来越复杂的高并发业务场景(如下图),比如热点视频/文章的观看(读场景),热点视频/文章的评论,点赞等(写场景)。

众所周知,数据库是把数据存储在磁盘上,访问时需要进行IO操作,在请求量小的情况下,耗时还比较低。但随着数据量的增大,访问量的集中,整个数据库负担加重,响应就会变慢,请求延时上升。进而导致用户侧的等待时间变长,很大程度上影响了用户体验。

为了解决这一问题,在整个请求链路上就引入了缓存的策略,将数据库中的数据copy一份到redis缓存(毕竟可是号称单机抗10w qps)。由于redis的数据是直接存在内存中,因此读写数据速度提升,整体请求响应速度也得到了提高。

但是这样一来,数据就存在了两个地方:缓存redis和数据库db中,不可避免出现redis和db中数据的一致性问题。针对这一问题,小编参考了网上的一些资料,加上自己的理解,简单介绍下该问题的解决方案。

问题分析

在高并发的业务场景下,用户的的操作无非就是读场景和写场景。

针对读场景

  1. 如果redis中存在用户所需数据,直接返回即可;如果不存在,则试图从db中读取数据,读取完成后,将该数据更新到redis中,避免下次再次查询或其他用户查询时,仍从db读取。
  2. 在高并发场景下,两次请求同时请求redis,发现数据并不存在后,都会从db中加载数据,然后再重新将数据写入redis,无论两次请求的写操作谁先谁后,数据本身是一样的,只不过额外进行了一次数据写覆盖。因此不会产生数据不一致的问题。

针对写场景

  1. 如果redis中本身不存在缓存数据,则直接修改db中的数据即可,不会产生数据不一致问题。
  2. 如果redis中已存在缓存数据,则需要同时修改db和redis中的数据,但是二者修改操作的执行必然存在先后顺序。在高并发的场景下,就有可能产生数据不一致的情况。

那么针对此中数据不一致问题,就产生了以下两个疑问:

  1. 由于redis中的数据可有可无,那么当数据发生变化时,是对redis中的数据进行修改,还是直接删除对应的redis,然后通过后续的读请求再回源db,将数据重新写入redis呢?
  2. redis和db的数据写操作的顺序问题,是先更新redis,还是先更新db?

问题1,缓存数据淘汰 or 更新

方案1: 淘汰缓存策略

  • 优点:操作简单,直接将对应缓存删除即可
  • 缺点:由于缓存被删除后,下次的读请求无法命中缓存,需回源db,将数据重新写入redis

方案2: 更新缓存策略

  • 优点:缓存命中率高,只要缓存进行了更新,后续的读请求不会出现缓存未命中(cache miss)的情况
  • 缺点:在某些业务场景下,缓存更新的成本过大。且更新后的缓存不一定会被使用。

综合分析

其实业界一般采用的都是缓存淘汰策略,而非缓存更新策略。原因有三:

  1. 大多数情况下,redis缓存中的数据并不是完全copy db中的数据,而是将db中多张表的数据进行了重新计算,筛选后更新到redis。如果在db某一张表的数据发生了变化的情况下,需要同步重新计算redis中的值,成本过高。
  2. 缓存更新后的新值,无法保证一定会有读请求命中,如果一直没有请求命中该部分冷数据,其实是产生了一定的资源浪费(计算成本+存储成本)。
  3. 相较于淘汰缓存策略中,仅有一次读请求cache miss的结果来说,淘汰缓存策略的缺点完全可以容忍。

举个例子来说,a表中的字段,1分钟更改了100次,如果采用更新缓存策略,则需要计算100次,哪怕1分钟内只有1次读请求;如果采用淘汰缓存策略,如果1分钟内只有1次请求,则只需要计算1次即可,开销大幅度降低。

问题2,redis和db写操作,谁先谁后?

注意:以下的方案讨论都是基于redis淘汰缓存操作以及数据库更新操作保证成功(可通过重试机制解决)的情况下,高并发的业务场景中的解决方案。

方案1: 先淘汰缓存,后更新数据库

正常情况

  • A请求进行写操作,先淘汰缓存,再更新数据库
  • B请求进行读操作,由于A请求已将缓存淘汰,B请求没有在redis中发现所需数据,因此从数据库中读取数据,并更新缓存到redis中

异常情况1

  • A请求进行写操作,先淘汰缓存
  • B请求进行读操作,由于A请求已将缓存淘汰,B请求没有在redis中发现所需数据,因此从数据库中读取数据,并更新缓存到redis中。注意,此时redis中被更新的依然是老数据,A请求的数据库更新操作尚未完成
  • A请求进行数据库更新操作。此时,数据库中是新数据,redis缓存中是老数据,产生了数据不一致的问题。且该不一致会一直持续到缓存自然失效或者下次的更新操作

对于该种异常情况,提供两种解决思路:

  1. 异步更新缓存

    1. A请求进行写操作,先淘汰缓存
    2. B请求进行读操作,由于A请求已将缓存淘汰,B请求没有在redis中发现所需数据,因此从数据库中读取数据。注意,此时不向redis写入新的缓存策略
    3. A请求通过订阅数据库binlog,对redis缓存数据进行异步更新
    4. 该方案虽然解决了数据不一致的问题,但是在数据库更新操作完成前,所有的读请求都会直接打到数据库上,具有比较大的风险。
  2. 延时双删
    1. A请求进行写操作,先淘汰缓存
    2. B请求进行读操作,由于A请求已将缓存淘汰,B请求没有在redis中发现所需数据,因此从数据库中读取数据,并更新缓存到redis中。注意,此时redis中被更新的依然是老数据,A请求的数据库更新操作尚未完成。假设该步骤耗时N秒
    3. A请求进行数据库更新操作。
    4. 由于此时redis中写入了老数据,因此A请求在休眠M秒后(M略大于N),再次对redis进行淘汰缓存操作
    5. 该方案虽然解决了数据不一致的问题,但是由于请求A在更新完数据库之后,需要休眠M秒再次淘汰缓存,一定程度上影响了数据更新操作的吞吐量。可以尝试将等待M秒更新redis的操作放到另一个单独的线程(比如消息队列 + 重试机制)。可以有效缓解吞吐量降低的问题。

异常情况2

  • A请求进行读操作,此时redis缓存中没有数据,因此直接从数据库中读取数据
  • B请求进行写操作,先淘汰缓存,再更新数据库
  • A请求进行将从数据库中读到的老数据,更新到redis。此时产生数据不一致问题。
  • 该种异常情况发生概率极低,一般读操作比写操作要快。如有担心,可以采用上述的延时删除策略

方案2: 先更新数据库,后更新淘汰缓存

正常情况

  • A请求进行写操作,先更新数据库,再淘汰缓存
  • B请求进行读操作,由于A请求已将缓存淘汰,B请求没有在redis中发现所需数据,因此从数据库中读取数据,并更新缓存到redis中

异常情况1

  • A请求进行写操作,先更新数据库
  • B请求进行读操作,由于A请求尚未淘汰缓存,B请求在redis中发现所需数据,因此直接返回老数据,产生了数据不一致的问题
  • A请求淘汰缓存。
  • C请求进行读操作,发现redis中没有数据,因此从数据库中读取新数据,并更新至缓存。数据不一致的问题解决。
  • 该场景下,数据最终一致,只是在高并发下产生了一小段时间的数据不一致。

异常情况2

  • A请求进行读操作,此时redis缓存中没有数据,因此直接从数据库中读取数据
  • B请求进行写操作,更新数据库,并将redis中缓存进行了淘汰(虽然此时redis中并没有任何的缓存)
  • A请求将从数据库中读到的老数据,更新到redis。此时产生数据不一致问题。
  • 该种异常情况发生概率极低,一般读操作比写操作要快。如有担心,可以采用上述的延时删除策略

总结

  • 方案1,先淘汰缓存,后更新数据库的策略,有可能导致长时间的数据不一致问题,可以通过延时双删 or 异步更新缓存策略进行解决。
  • 方案2,先更新数据库,后更新缓存,有可能导致极短时间内的数据不一致,但是数据最终是一致的

思考

其实数据库和缓存的一致性问题,乍一看,情况比较复杂,但是通过分析业务使用场景,以及正常和异常的请求情况,是可以抽丝剥茧,逐步解决的。

限于本篇的篇幅长度,在此只列举出了部分异常情况以及针对这些异常情况提出的一些解决方案,其实在这个过程中,可以讨论和研究的点还有很多:

  • 同步更新缓存和异步更新缓存的差异点,什么样的场景适合同步更新,什么样的场景适合异步更新
  • 延时删除的等待时间M的取值需要考虑哪些因素,网络?数据库主从同步?
  • 如何设计合适的重试机制方案?

这些问题就后续再更新啦~

特别鸣谢

本文参考了以下网上资料,特此鸣谢

  • https://yq.aliyun.com/articles/712285
  • https://my.oschina.net/jiagouzhan/blog/2990423?p=2

别光看,后续更多精彩

赶紧关注公众号:程序猿刘先森

完成该操作所需的数据还不可使用_面试官:如何保障数据库和redis缓存的一致性...相关推荐

  1. jqgrid为什么表头和数据之间有间隙_面试官:你看过Redis数据结构底层实现吗?...

    面试中,redis也是很受面试官亲睐的一部分.我向在这里讲的是redis的底层数据结构,而不是你理解的五大数据结构.你有没有想过redis底层是怎样的数据结构呢,他们和我们java中的HashMap. ...

  2. 完成该操作所需的数据还不可使用(异常来自 HRESULT:0x8000000A)

    环境: AS92+filegeodatabase 如题,错误信息如下: 完成该操作所需的数据还不可使用. (异常来自 HRESULT:0x8000000A) 说明: 执行当前 Web 请求期间,出现未 ...

  3. 面试官问:数据库 delete 表数据,磁盘空间还是被一直占用,为什么?

    以下文章来源方志朋的博客,回复"666"获面试宝典 最近有个上位机获取下位机上报数据的项目,由于上报频率比较频繁且数据量大,导致数据增长过快,磁盘占用多. 为了节约成本,定期进行数 ...

  4. ❤️手撕这十道HiveSQL题还不能吊打面试官,却能保你不被吊打❤️【推荐收藏】

    全网最详细的大数据Hive文章系列,强烈建议收藏加关注! 新文章都已经列出历史文章目录,帮助大家回顾前面的知识重点. 目录 系列历史文章 前言 HiveSQL十题 第一题 1.需求 2.数据准备 3. ...

  5. 怎么往integer型数组添加数据_面试中经常问到的Redis七种数据类型,你都真正了解吗?...

    前言 Redis不是一个简单的键值对存储,它实际上是一个支持各种类型数据结构的存储.在传统的键值存储中,是将字符串键关联到字符串值,但是在Redis中,这些值不仅限于简单的字符串,还可以支持更复杂的数 ...

  6. es删除数据_面试官是怎么来考察你对ES搜索引擎的理解?

    来源:http://1t.click/ZdY 一. 面试官心理分析问这个,其实面试官就是要看看你了解不了解 es 的一些基本原理,因为用 es 无非就是写入数据,搜索数据.你要是不明白你发起一个写入和 ...

  7. synchronized 异常_面试官,别挂电话,Synchronized,我还能说上半小时

    面试官,别挂电话,Synchronized,我还能说上半小时. Synchronized关键字,经常被用于线程同步.执行Synchronized修饰的同步代码块的线程,首先会获得"对象的锁& ...

  8. golang实现的布隆过滤器_面试官:都 2020 年,你在干嘛?还不知道布隆过滤器

    关注过 @Python大星 的小伙伴应该知道,2020 年 4 月 Python 小星最近裸面了阿里巴巴菜鸟网络科技有限公司. 一面中面试官非常重视解决 Redis 缓存穿透问题的利器--布隆过滤器, ...

  9. redis怎么修改_面试官问我Redis事务,还问我有哪些实现方式

    ❝ 「第12期」 距离大叔的80期小目标还有68期,今天大叔要跟大家分享的内容是 -- Reids中的事务.同样,这也是redis中重要指数为四颗星的必备基础知识点.下面一起来了解一下吧. ❞ 相信大 ...

  10. 没有执行此操作所需的足够可用空间。_一文详解 MySQL 高可用之 DRBD | 原力计划...

    作者 | wzy0623责编 | 屠敏出品 | CSDN 博客大多数MySQL高可用解决方案都是基于MySQL自带的各种复制技术.本质上是将一个实例上的数据更新或事务,在其它实例上进行重放,从而完成数 ...

最新文章

  1. 差异备份、增量备份、完全备份的区别
  2. Flutter Live 2018 Flutter 1.0 发布
  3. 深入理解消息队列(场景,对比,原理和设计思想)
  4. 成为Java流专家–第2部分:中级操作
  5. 将Spring Bean注入非托管对象
  6. echart 折线从左到右动画效果_echarts之自动切换折线图
  7. java 自定义注解_Java注解
  8. 【每日SQL打卡】​​​​​​​​​​​​​​​DAY 10丨买下所有产品的客户【难度中等】
  9. 从无到有开发连麦直播技术点整理
  10. Python+tkinter动态创建与销毁组件小案例
  11. nginx工作笔记004---配置https_ssl证书_视频服务器接口等
  12. SOUI自定义控件(3)
  13. 四位共阳极数码管显示函数_求各位大神指正,四位一体共阳极数码管数字钟程序,仿真能运行,实物就只显8个8,不动...
  14. Hmmm:在社交网络上展示不同的真实的你
  15. 一文详尽解释CatBoost
  16. 中国集成电路产业投资建议与十四五需求规模分析报告2022版
  17. 水星路由器上网设置服务器无响应,怎么防止路由器DNS被劫持弹出广告
  18. 互联网乱世之下 一将功成万骨枯
  19. 给button按钮绑定Enter回车键
  20. 软件加密技术和注册机制加密基础(转)

热门文章

  1. python 导出为csv_批量导出SolidWorks模型点坐标值
  2. ISIS出现(Project2.sct(7): error: L6236E: No section matches selector - no section to be FIRST/LAST.)异常
  3. php paypal ipn返回验证,Paypal IPN检测退款,PHP
  4. mysql从字段取出地名_mysql中从字段中URL提取域名信息
  5. matlab三相系统电阻折算,基于MATLAB三相异步电动机调压调速系统方案.doc
  6. 用C#开发Windows服务
  7. Oracle表字段的增加、删除、修改和重命名
  8. iOS开发UI篇--仿射变换(CGAffineTransform)使用小结
  9. UBUNTU14.0.4安装eclipse
  10. Mysql删除数据报外键约束解决方法