一般来说,只要你用到了缓存,不管是Redis还是memcache,就可能会涉及到数据库缓存与数据的一致性问题,这里我们以Redis为例。

我们该如何保证Redis与数据库的一致性呢?

So easy:

更新的时候,先更新数据库,然后再删除缓存。
读的时候,先读缓存;如果没有的话,就读数据库,同时将数据放入缓存,并返回响应。
乍一看,一致性问题貌似很好的得到了解决。但仔细一想,你会发现还是有问题:如果先更新了数据库,删除缓存的时候失败了怎么办?那么数据库中是新数据,缓存中是老数据,数据出现不一致了。

改进方案:

先删除缓存,后更新数据库。因为即使后面更新数据库失败了,缓存是空的,读的时候会从数据库中重新拉,虽然都是旧数据,但数据是一致的。

所以方案就变成了:

更新的时候,先删除缓存,然后再更新数据库。
读的时候,先读缓存;如果没有的话,就读数据库,同时将数据放入缓存,并返回响应。

到这里是不是问题就得到了彻底的解决了呢?其实并没有,在高并发的场景下,会出现这样的情况:数据发生了变更,先删除了缓存,然后去修改数据库。此时还没来得及修改,一个请求过来了,去读缓存,发现缓存空了,去读数据库,读到了准备修改前的旧数据,并且把旧数据放到了缓存。随后,数据变更程序完成了数据库的修改。那么完了,这个时候发生数据不一致了......

解决方案:

针对这种情况,可以先把“修改DB”的操作放到一个JVM队列,后面读请求过来之后,“更新缓存”的操作也放进同一个JVM队列,每个队列,对于一个作业线程,按照队列的顺序,依次执行相关操作,这样就可以保证“更新缓存”一定是在DB修改之后,以保证数据一致性,具体如下图所示:

细想该方案,其实还有几个优化点

1、读请求过多的时候,队列里面会有多个“更新缓存”操作串在一起,其实是没有意义的,往队列里面塞数据的时候可以先判断一下,有的话就不用再塞进去了

2、遇到更新DB比较频繁的业务场景时,可能会导致读请求长时间阻塞,这个时候可以通过扩机器增加吞吐量,或者可以先返回一个旧的值。

最新文章

  1. 兼容各浏览器的js判断上传文件大小
  2. java19
  3. 关于Liferay所有的能够进行自定义和扩展的东西的总结
  4. JSF 2 textbox example
  5. heibernate增删改查总结一下自己的不足
  6. 在Java中system.out.println使用方法
  7. css 层叠式样式表
  8. selenium.common.exceptions.WebDriverException: Message: 'phantomjs' executab
  9. 从零开始学习PYTHON3讲义(二)把Python当做计算器
  10. 扒一扒JVM的垃圾回收机制,下次面试你准备好了吗
  11. swift 学习- 11 -- 属性
  12. Delphi操作剪贴板
  13. 使用TheFolderSpy监控文件夹的变化-邮件通知
  14. 使用maven命令把jar包加入maven仓库
  15. 【Java入门提高篇】Day20 Java容器类详解(三)List接口
  16. Javascript中的垃圾回收机制
  17. python 全局变量的使用
  18. mysql5.0版本下载地址
  19. 【loj2472】IIIDX
  20. BI项目中的ETL设计详解(数据抽取、清洗与转换 )(转载)

热门文章

  1. Mysql的caching_sha2_password的坑
  2. RESR API (三)之Views
  3. cannot assign to struct field xxx in map
  4. Oracle,Mysql 获取用户下所有表名,获取表所有的列名及数据类型
  5. CSS3——对齐 组合选择符 伪类 伪元素 导航栏 下拉菜单
  6. C++:利用如下公式,编写函数计算∏的值,直到最后一项的绝对值小于e,主程序接收从键盘输入的e,输出∏的值(保留5位小数)。 ∏/4 = 1-1/3+1/5-1/7...
  7. 知识图谱之图数据库Neo4j
  8. django F和Q 关键字使用
  9. 华南理工大学 “三七互娱杯” G HRY and tree
  10. iview报错[Vue warn]: Error in render: "TypeError: ctx.injections.tableRoot.$scopedSlots[ctx.props.column.slot] is not a function"