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

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

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

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

问题分析

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

针对读场景如果redis中存在用户所需数据,直接返回即可;如果不存在,则试图从db中读取数据,读取完成后,将该数据更新到redis中,避免下次再次查询或其他用户查询时,仍从db读取。

在高并发场景下,两次请求同时请求redis,发现数据并不存在后,都会从db中加载数据,然后再重新将数据写入redis,无论两次请求的写操作谁先谁后,数据本身是一样的,只不过额外进行了一次数据写覆盖。因此不会产生数据不一致的问题。

针对写场景如果redis中本身不存在缓存数据,则直接修改db中的数据即可,不会产生数据不一致问题。

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

那么针对此中数据不一致问题,就产生了以下两个疑问:由于redis中的数据可有可无,那么当数据发生变化时,是对redis中的数据进行修改,还是直接删除对应的redis,然后通过后续的读请求再回源db,将数据重新写入redis呢?

redis和db的数据写操作的顺序问题,是先更新redis,还是先更新db?

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

方案1: 淘汰缓存策略优点:操作简单,直接将对应缓存删除即可

缺点:由于缓存被删除后,下次的读请求无法命中缓存,需回源db,将数据重新写入redis

方案2: 更新缓存策略优点:缓存命中率高,只要缓存进行了更新,后续的读请求不会出现缓存未命中(cache miss)的情况

缺点:在某些业务场景下,缓存更新的成本过大。且更新后的缓存不一定会被使用。

综合分析

其实业界一般采用的都是缓存淘汰策略,而非缓存更新策略。原因有三:大多数情况下,redis缓存中的数据并不是完全copy db中的数据,而是将db中多张表的数据进行了重新计算,筛选后更新到redis。如果在db某一张表的数据发生了变化的情况下,需要同步重新计算redis中的值,成本过高。

缓存更新后的新值,无法保证一定会有读请求命中,如果一直没有请求命中该部分冷数据,其实是产生了一定的资源浪费(计算成本+存储成本)。

相较于淘汰缓存策略中,仅有一次读请求cache miss的结果来说,淘汰缓存策略的缺点完全可以容忍。

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

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

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

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

正常情况A请求进行写操作,先淘汰缓存,再更新数据库

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

异常情况1A请求进行写操作,先淘汰缓存

B请求进行读操作,由于A请求已将缓存淘汰,B请求没有在redis中发现所需数据,因此从数据库中读取数据,并更新缓存到redis中。注意,此时redis中被更新的依然是老数据,A请求的数据库更新操作尚未完成

A请求进行数据库更新操作。此时,数据库中是新数据,redis缓存中是老数据,产生了数据不一致的问题。且该不一致会一直持续到缓存自然失效或者下次的更新操作

对于该种异常情况,提供两种解决思路:异步更新缓存。A请求进行写操作,先淘汰缓存

B请求进行读操作,由于A请求已将缓存淘汰,B请求没有在redis中发现所需数据,因此从数据库中读取数据。注意,此时不向redis写入新的缓存策略

A请求通过订阅数据库binlog,对redis缓存数据进行异步更新

该方案虽然解决了数据不一致的问题,但是在数据库更新操作完成前,所有的读请求都会直接打到数据库上,具有比较大的风险。

延时双删。A请求进行写操作,先淘汰缓存

B请求进行读操作,由于A请求已将缓存淘汰,B请求没有在redis中发现所需数据,因此从数据库中读取数据,并更新缓存到redis中。注意,此时redis中被更新的依然是老数据,A请求的数据库更新操作尚未完成。假设该步骤耗时N秒

A请求进行数据库更新操作。

由于此时redis中写入了老数据,因此A请求在休眠M秒后(M略大于N),再次对redis进行淘汰缓存操作

该方案虽然解决了数据不一致的问题,但是由于请求A在更新完数据库之后,需要休眠M秒再次淘汰缓存,一定程度上影响了数据更新操作的吞吐量。可以尝试将等待M秒更新redis的操作放到另一个单独的线程(比如消息队列 + 重试机制)。可以有效缓解吞吐量降低的问题。

异常情况2A请求进行读操作,此时redis缓存中没有数据,因此直接从数据库中读取数据

B请求进行写操作,先淘汰缓存,再更新数据库

A请求进行将从数据库中读到的老数据,更新到redis。此时产生数据不一致问题。

该种异常情况发生概率极低,一般读操作比写操作要快。如有担心,可以采用上述的延时删除策略

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

正常情况A请求进行写操作,先更新数据库,再淘汰缓存

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

异常情况1A请求进行写操作,先更新数据库

B请求进行读操作,由于A请求尚未淘汰缓存,B请求在redis中发现所需数据,因此直接返回老数据,产生了数据不一致的问题

A请求淘汰缓存。

C请求进行读操作,发现redis中没有数据,因此从数据库中读取新数据,并更新至缓存。数据不一致的问题解决。

该场景下,数据最终一致,只是在高并发下产生了一小段时间的数据不一致。

异常情况2A请求进行读操作,此时redis缓存中没有数据,因此直接从数据库中读取数据

B请求进行写操作,更新数据库,并将redis中缓存进行了淘汰(虽然此时redis中并没有任何的缓存)

A请求将从数据库中读到的老数据,更新到redis。此时产生数据不一致问题。

该种异常情况发生概率极低,一般读操作比写操作要快。如有担心,可以采用上述的延时删除策略

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

方案2,先更新数据库,后更新缓存,有可能导致极短时间内的数据不一致,但是数据最终是一致的

思考

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

限于本篇的篇幅长度,在此只列举出了部分异常情况以及针对这些异常情况提出的一些解决方案,其实在这个过程中,可以讨论和研究的点还有很多:同步更新缓存和异步更新缓存的差异点,什么样的场景适合同步更新,什么样的场景适合异步更新

延时删除的等待时间M的取值需要考虑哪些因素,网络?数据库主从同步?

如何设计合适的重试机制方案?

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

特别鸣谢

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

别光看,后续更多精彩

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

redis做mysql缓存的优点_面试官:如何保障数据库和redis缓存的一致性相关推荐

  1. redis删除过期key的算法_面试官别再问我Redis内存满了该怎么办了

    概述 Redis的文章,我之前写过一篇关于「Redis的缓存的三大问题」,累计阅读也快800了,对于还只有3k左右的粉丝量,能够达到这个阅读量,已经是比较难了. 这说明那篇文章写的还过得去,收到很多人 ...

  2. java如何实现redis分片存储_面试官:你说一下Redis吧,怎么实现高可用,还有持久化怎么做的?...

    前言 作为Java程序员,在面试过程中,缓存相关的问题是躲不掉的,肯定会问,例如缓存一致性问题,缓存雪崩.击穿.穿透等.说到缓存,那肯定少不了Redis,我在面试的时候也是被问了很多关于Redis相关 ...

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

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

  4. redis查看key的过期时间_面试官:你在Redis中设置过带过期时间的Key吗?

    点击上方小伟后端笔记关注公众号 每天阅读Java干货文章 熟悉Redis的同学应该知道,Redis的每个Key都可以设置一个过期时间,当达到过期时间的时候,这个key就会被自动删除. 在为key设置过 ...

  5. 你如何理解mysql的读写分离_面试官:谈谈你对Mysql数据库读写分离的了解,并且有哪些注意事项?...

    这篇文章讲述的不是Mysql具体的如何实现读写分离,而是指什么时候需要上读写分离,及其相关的注意事项. 因为用户的增多,数据的增多,单机的数据库往往支撑不住快速发展的业务,所以数据库集群就产生了!今天 ...

  6. redis查看key的过期时间_面试官:Redis过期后key是怎么样清理的?

    前言 笔者一个同事面试某大厂时问到的一个问题,这里拿来讲讲:Redis过期后key是怎么样清理的? 在Redis中,对于过期key的清理主要有惰性清除,定时清理,内存不够时清理三种方法,下面我们就来具 ...

  7. mysql 修改字段长度_面试官:InnoDB记录存储结构都不知道,你敢说你懂MySQL?

    前言 了解MySQL的人都知道,MySQL服务器上负责对表中数据的读取和写入工作的部分是存储引擎,而MySQL的存储引擎有MyISAM和InnoDB.不同的存储引擎一般是由不同的人为实现不同的特性而开 ...

  8. springboot 手动提交事务_面试官你都工作3年了,也做过5个项目了,怎么连事务机制都不会...

    分享职场生活.职场攻略.程序员创业资源,为一线开发者提供优质内容 张工是一名java程序员,最近到某互联网公司面试,面试官问了这样一个问题: 有这样一个场景,需要往订单主表和明细表插入数据,如何保证订 ...

  9. 【大厂面试】面试官看了赞不绝口的Redis笔记

    文章目录 一.Redis简介 二.Redis API的使用和理解 (一)通用命令 (二)单线程架构 (三)数据结构和内部编码 (四)字符串 (五)hash (字典) (六)列表 (七)Set集合 (八 ...

最新文章

  1. 如何在Java中解析命令行参数?
  2. no nlsxbe in java.library.path
  3. mockito 静态_在Java 8中使用不带静态导入的Mockito
  4. Linux入门笔记——文件操作命令1
  5. powerCat进行常规tcp端口转发
  6. 【原】常见的模块,你语义化了没
  7. 《HTML5和CSS3快速参考》——1.3HTML5的品牌化
  8. java 接口和抽象类的区别_Java中的接口与抽象类:有什么区别?
  9. 关于USB-Audio(USB麦克风)设备的录音验证
  10. matlab如何更改程序中所有同名_MATLAB作图实例:52:添加网格线并编辑位置
  11. 【Android】Service管理通知栏通知模板
  12. Linux系统中sit0是做什么的
  13. HTML5隐藏图片代码,HTML5终极备忘大全(图片版+文字版)
  14. 网桥、交换机、路由器等的区别
  15. 面向服务的体系架构(SOA)—架构篇
  16. 力扣题解-977. 有序数组的平方
  17. 《以幽默的方式过一生》-琢磨先生读书笔记
  18. 【观察】与时俱进提供智慧费用管理新体验,SAP Concur “以行践言”的新价值...
  19. HIVE 内存溢出常见问题
  20. Android 手机中国移动网络接入点设置

热门文章

  1. 宝塔面板免费防火墙waf ——linux系统
  2. fastadmin 宝塔免费防火墙拦截图片选择
  3. 如何通过3D-MAX制作三维地图模型
  4. 数据表设计与mysql入门(一)
  5. 老罗专访:Android 源码之旅
  6. OpenGL基础图形编程(九)OpenGL颜色
  7. Python批量更改图片颜色,可做证件照底色的更换
  8. 环渤海新闻网《访工程教育家罗忠忱外孙女林霞》
  9. 如本科技发布超大视野3D工业相机,用于物流、汽车制造大视野拆码垛、搬运场景
  10. [RK3568 Android12] 以太网 eth0,eth1 RTL8211F