当前位置: 首页 > news >正文

缓存与数据库的数据一致性方案介绍

在很多系统中重要数据通常都是写入关系数据库如mysql中,为了实现读写分离,提高系统负载能力,缩短响应时间通常还需要用到缓存。

缓存带来了系统性能的提升同时也把数据一致性问题摆在了开发者面前,在数据库使用读写分离和主从同步的情况下这种一致性问题会变得更加复杂。本文将介绍几种提升一致性的方案供大家参考。

背景介绍

一般使用缓存(本文中的缓存不特指某一种分布式缓存或本地缓存)的方式为在读数据时首先读取缓存,如果缓存没有则读数据库然后将数据写入缓存最后返回;写数据时首先清除缓存内的数据,然后写数据库。

这种方式在数据库配置了主从库时会遇到数据不一致的问题,首先来看一下这种实现的具体流程如下图:

                                   

                                                           图1 主从数据不一致

在读数据时如果缓存中没有数据则读取从库的数据,然后写入缓存中并返回;在写数据时先清除缓存中的数据,然后将数据写入主库,主库数据会被同步到从库。

这种实现方式的主要问题在于当数据写入主库后,缓存没有数据,这时读请求会读取从库的数据。此时如果发生主从延迟,主库的数据还没有写到从库,则应用服务器会将从库读到的脏数据写入缓存服务器中,如果写入的数据没有加有效期或有效期很长就会造成数据不一致,如果主从延迟时间较长可能会导致大面积的数据不一致。下面将介绍几种解决数据一致性问题的方案。

加有效期

给缓存中的数据增加有效期是解决一致性问题最简单的确保数据最终一致性的方法,这种方法在缓存中没有数据需要查询数据库时将查询结果放入缓存的时候设置一个有效期(更新数据时仍然先清除缓存数据),非常适用于更新频率较低的数据,例如商品信息。

但是单纯给数据加上有效期也存在一些明显的问题,如果有效期较长就会出现上面提到的数据不一致的问题,如果有效期较短就会出现缓存效率不高经常读库的情况。在使用这种方法的时候就需要我们根据数据的更新频率确定合适的有效期时间,当冷热数据并存时这种方案就显得难以兼顾。那么什么策略既能确保数据的最终一致性又能充分利用缓存呢?这就要提到业内使用最多的双淘汰了。

双淘汰

双淘汰与本文第一个方案相比在读取数据时是相同的,区别在于更新数据的流程。在更新数据时仍然首先清除缓存的数据,然后将数据写入到数据库中,然后将数据记录在一个延迟队列或哈希表中,同时另一个线程不断读取延迟队列或者哈希表,根据数据存入的时间也预先设定的延迟时间再次清除缓存了的数据。

可以看出预先设定的延迟时间应该大于数据库主从同步较慢情况下的同步时间,这样就能确保在主从延迟的情况下缓存中的脏数据也能被清除保证了数据一致性。流程如下图,C语言中可以使用哈希表实现。

虽然双淘汰保证了数据的最终一致性并提高了缓存的使用率,但在两次“淘汰”之间读取的数据仍然有可能是脏数据,这种情况会在主从延迟较长的情况下尤为明显。对于某些对实时一致性要求较高的系统如何获得更好的读一致性呢,这里需要提到双淘汰的另一种变型。

                                                 

                                                                   图2 双淘汰

另一种双淘汰

为了提高读到数据的准确性这种方法在更新数据时首先清除缓存数据并在缓存中存入这个数据对应的标记,在写入数据库成功后再将数据写入到一个延迟队列或哈希表中。在读取数据时从缓存读取数据,如果存在直接返回,如果不存在则读取数据对应的标记,如果标记存在则读主库否则读从库,最后将数据写入缓存中。

同时另一个线程不断读取延迟队列或者哈希表,根据数据存入的时间也预先设定的延迟时间再次清除缓存了的数据。整个读写过程如下图。可以看出这种方法通过在缓存增加一个标记将部分读请求分流到了主库,这个标记可以是数据的主键或其他唯一标识,通过牺牲一部分主库的性能提高了读请求的数据一致性。

                                               

                                                                  图3 双淘汰2

目前为止没有哪一种缓存策略是万能的,基本上我们仍需要根据具体的业务场景和数据类型选择合适的缓存策略。数据量越大数据情况也复杂通常就需要越复杂的缓存策略,希望本文介绍的几个方案对读者今后的开发有所帮助。

相关文章:

  • Qt之QPropertyAnimationQEasingCurve
  • Android内存优化之图片内存优化
  • 重构到更深层的模型
  • 初识python: flush 实现进度条打印
  • es6 方法具名参数及默认值
  • 【大数据安全】Apache Kylin 安全配置(Kerberos)
  • mysql报错sql injection violation, syntax error: syntax error, expect RPAREN, actual IDENTIFIER
  • 聊聊redis的数据结构的应用
  • 利用POI和反射实现Excel自动识别实体类导入
  • 随谈10年的技术生涯和技术成长(转)
  • 以太坊开发环境
  • 8.Kafka offset机制
  • Webview独立进程并通过AIDL实现数据通信
  • axios执行原理了解一下!
  • MySQL语句执行分析(二)
  • 「译」Node.js Streams 基础
  • 【跃迁之路】【477天】刻意练习系列236(2018.05.28)
  • 4. 路由到控制器 - Laravel从零开始教程
  • Docker 笔记(2):Dockerfile
  • EventListener原理
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • Median of Two Sorted Arrays
  • MySQL的数据类型
  • php ci框架整合银盛支付
  • Python十分钟制作属于你自己的个性logo
  • React-redux的原理以及使用
  • React中的“虫洞”——Context
  • Vim 折腾记
  • 从零开始学习部署
  • 浅谈web中前端模板引擎的使用
  • 一道面试题引发的“血案”
  • 自定义函数
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • ​LeetCode解法汇总1276. 不浪费原料的汉堡制作方案
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • #我与Java虚拟机的故事#连载17:我的Java技术水平有了一个本质的提升
  • #周末课堂# 【Linux + JVM + Mysql高级性能优化班】(火热报名中~~~)
  • ( 用例图)定义了系统的功能需求,它是从系统的外部看系统功能,并不描述系统内部对功能的具体实现
  • (26)4.7 字符函数和字符串函数
  • (33)STM32——485实验笔记
  • (JSP)EL——优化登录界面,获取对象,获取数据
  • (vue)页面文件上传获取:action地址
  • (二)JAVA使用POI操作excel
  • (二)构建dubbo分布式平台-平台功能导图
  • (附源码)ssm基于web技术的医务志愿者管理系统 毕业设计 100910
  • (十三)Java springcloud B2B2C o2o多用户商城 springcloud架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)...
  • (五)Python 垃圾回收机制
  • (已解决)什么是vue导航守卫
  • (原創) 如何讓IE7按第二次Ctrl + Tab時,回到原來的索引標籤? (Web) (IE) (OS) (Windows)...
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案一
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • (转载)从 Java 代码到 Java 堆
  • .mysql secret在哪_MYSQL基本操作(上)