缓存与数据库一致性保证

本文主要讨论这么几个问题:

1.啥时候数据库和缓存中的数据会不一致

2.不一致优化思路

3.如何保证数据库与缓存的一致性

 

1需求缘起

 

上一篇《缓存架构设计细节二三事》引起了广泛的讨论,其中有一个结论:当数据发生变化时,“先淘汰缓存,再修改数据库”这个点是大家讨论的最多的。

上篇文章得出这个结论的依据是,由于操作缓存与操作数据库不是原子的,非常有可能出现执行失败。

 


  

假设先写数据库,再淘汰缓存:第一步写数据库操作成功,第二步淘汰缓存失败,则会出现DB中是新数据,Cache中是旧数据,数据不一致【如上图:db中是新数据,cache中是旧数据】。

 


  

假设先淘汰缓存,再写数据库:第一步淘汰缓存成功,第二步写数据库失败,则只会引发一次Cache miss【如上图:cache中无数据,db中是旧数据】。

 

结论:先淘汰缓存,再写数据库。

 

引发大家热烈讨论的点是“先操作缓存,在写数据库成功之前,如果有读请求发生,可能导致旧数据入缓存,引发数据不一致”,这就是本文要讨论的主题。

 

2为什么数据会不一致

 

回顾一下上一篇文章中对缓存、数据库进行读写操作的流程。

写流程:

(1)先淘汰cache

(2)再写db

读流程:

(1)先读cache,如果数据命中hit则返回

(2)如果数据未命中miss则读db

(3)将db中读取出来的数据入缓存

 
 

什么情况下可能出现缓存和数据库中数据不一致呢?

 

在分布式环境下,数据的读写都是并发的,上游有多个应用,通过一个服务的多个部署(为了保证可用性,一定是部署多份的),对同一个数据进行读写,在数据库层面并发的读写并不能保证完成顺序,也就是说后发出的读请求很可能先完成(读出脏数据):

(a)发生了写请求A,A的第一步淘汰了cache(如上图中的1)

(b)A的第二步写数据库,发出修改请求(如上图中的2)

(c)发生了读请求B,B的第一步读取cache,发现cache中是空的(如上图中的步骤3)

(d)B的第二步读取数据库,发出读取请求,此时A的第二步写数据还没完成,读出了一个脏数据放入cache(如上图中的步骤4)

即在数据库层面,后发出的请求4比先发出的请求2先完成了,读出了脏数据,脏数据又入了缓存,缓存与数据库中的数据不一致出现了。

 

3不一致优化思路

 

能否做到先发出的请求一定先执行完成呢?常见的思路是“串行化”,今天将和大家一起探讨“串行化”这个点。

先一起细看一下,在一个服务中,并发的多个读写SQL一般是怎么执行的


 

上图是一个service服务的上下游及服务内部详细展开,细节如下:

(1)service的上游是多个业务应用,上游发起请求对同一个数据并发的进行读写操作,上例中并发进行了一个uid=1的余额修改(写)操作与uid=1的余额查询(读)操作

(2)service的下游是数据库DB,假设只读写一个DB

(3)中间是服务层service,它又分为了这么几个部分

  ①最上层是任务队列

  ②中间是工作线程,每个工作线程完成实际的工作任务,典型的工作任务是通过数据库连接池读写数据库

  ③最下层是数据库连接池,所有的SQL语句都是通过数据库连接池发往数据库去执行的

 

 工作线程的典型工作流是这样的:

提问:任务队列其实已经做了任务串行化的工作,能否保证任务不并发执行?

答:不行,因为

(1)1个服务有多个工作线程,串行弹出的任务会被并行执行;

(2)1个服务有多个数据库连接,每个工作线程获取不同的数据库连接会在DB层面并发执行。

 

提问:假设服务只部署一份,能否保证任务不并发执行?

答:不行,原因同上。

 

提问:假设1个服务只有1条数据库连接,能否保证任务不并发执行?

答:不行,因为:

(1)1个服务只有1条数据库连接,只能保证在一个服务器上的请求在数据库层面是串行执行的

(2)因为服务是分布式部署的,多个服务上的请求在数据库层面仍可能是并发执行的

 

提问:假设服务只部署一份,且1个服务只有1条连接,能否保证任务不并发执行?

答:可以,全局来看请求是串行执行的,吞吐量很低,并且服务无法保证可用性

 

完了,看似无望了。

1)任务队列不能保证串行化

2)单服务多数据库连接不能保证串行化

3)多服务单数据库连接不能保证串行化

4)单服务单数据库连接可能保证串行化,但吞吐量级低,且不能保证服务的可用性,几乎不可行,那是否还有解?

 
 

退一步想,其实不需要让全局的请求串行化,而只需要“让同一个数据的访问能串行化”就行。

在一个服务内,如何做到“让同一个数据的访问串行化”,只需要“让同一个数据的访问通过同一条DB连接执行”就行。

如何做到“让同一个数据的访问通过同一条DB连接执行”,只需要“在DB连接池层面稍微修改,按数据取连接即可”。

获取DB连接的CPool.GetDBConnection()【返回任何一个可用DB连接】改为

CPool.GetDBConnection(longid)【返回id取模相关联的DB连接】

这个修改的好处是:

(1)简单,只需要修改DB连接池实现,以及DB连接获取处

(2)连接池的修改不需要关注业务,传入的id是什么含义连接池不关注,直接按照id取模返回DB连接即可

(3)可以适用多种业务场景,取用户数据业务传入user-id取连接,取订单数据业务传入order-id取连接即可

这样的话,就能够保证同一个数据例如uid在数据库层面的执行一定是串行的

 

稍等稍等,服务可是部署了很多份的,上述方案只能保证同一个数据在一个服务上的访问,在DB层面的执行是串行化的,实际上服务是分布式部署的,在全局范围内的访问仍是并行的,怎么解决呢?能不能做到同一个数据的访问一定落到同一个服务呢?

 

4能否做到同一个数据的访问落在同一个服务上?

 

上面分析了服务层service的上下游及内部结构,再一起看一下应用层上下游及内部结构


 

上图是一个业务应用的上下游及服务内部详细展开,细节如下:

(1)业务应用的上游不确定是啥,可能是直接是http请求,可能也是一个服务的上游调用

(2)业务应用的下游是多个服务service

(3)中间是业务应用,它又分为了这么几个部分

  ①最上层是任务队列【或许web-server例如tomcat帮你干了这个事情了】

  ②中间是工作线程【或许web-server的工作线程或者cgi工作线程帮你干了线程分派这个事情了】,每个工作线程完成实际的业务任务,典型的工作任务是通过服务连接池进行RPC调用

  ③最下层是服务连接池,所有的RPC调用都是通过服务连接池往下游服务去发包执行的

 
 

工作线程的典型工作流是这样的:


 

似曾相识吧?没错,只要对服务连接池进行少量改动:

获取Service连接的CPool.GetServiceConnection()【返回任何一个可用Service连接】改为

CPool.GetServiceConnection(longid)【返回id取模相关联的Service连接】

这样的话,就能够保证同一个数据例如uid的请求落到同一个服务Service上。

5总结

 

由于数据库层面的读写并发,引发的数据库与缓存数据不一致的问题(本质是后发生的读请求先返回了),可能通过两个小的改动解决:

(1)修改服务Service连接池,id取模选取服务连接,能够保证同一个数据的读写都落在同一个后端服务上;

(2)修改数据库DB连接池,id取模选取DB连接,能够保证同一个数据的读写在数据库层面是串行的。

 

6遗留问题

 

提问:取模访问服务是否会影响服务的可用性?

答:不会,当有下游服务挂掉的时候,服务连接池能够检测到连接的可用性,取模时要把不可用的服务连接排除掉。

 

提问:取模访问服务与 取模访问DB,是否会影响各连接上请求的负载均衡?

答:不会,只要数据访问id是均衡的,从全局来看,由id取模获取各连接的概率也是均等的,即负载是均衡的。

 

提问:要是数据库的架构做了主从同步,读写分离:写请求写主库,读请求读从库也有可能导致缓存中进入脏数据呀,这种情况怎么解决呢(读写请求根本不落在同一个DB上,并且读写DB有同步时延)?

答:下一篇文章和大家分享。

作者介绍   58沈剑

  • 在百度、58同城做过技术,现在在58到家负责一些后端的部门,本质是程序员,常在深夜写写技术文章。公众号“架构师之路”运营者,专注于分享来自一线的,可落地的,原创的架构技术文章。

作者:58沈剑


时间: 2024-08-01 09:59:12

缓存与数据库一致性保证的相关文章

微服务架构下的事务一致性保证

今天我给大家分享的题目是微服务架构下的事务一致性保证. 主要内容包括4部分: 传统分布式事务不是微服务中一致性的最佳选择 微服务架构中应满足数据最终一致性原则 微服务架构实现最终一致性的三种模式 对账是最后的终极防线. 我们先来看一下第一部分,传统使用本地事务和分布式事务保证一致性 传统单机应用一般都会使用一个关系型数据库,好处是应用可以使用 ACID transactions.为保证一致性我们只需要:开始一个事务,改变(插入,删除,更新)很多行,然后提交事务(如果有异常时回滚事务).更进一步,

ADO.NET访问数据-(2) DataSet本地缓存与数据库的交互过程,以及应用

大半夜的睡不着觉 ,被宿舍的一群狼给吵醒了,于是就继续写博客....       在前面一个 ADO.NET  数据库文章中  我说过  ADO.NET  允许用户在断网的情况下 对数据库进行 "操作" ,注意这里的操作 我带了 引号 !!!     其实在ADO.NET 中  我们可以先从数据库将 一个数据库的子集下载 到本地内存中,然后这个子集 会驻留在客户机中,这时候 我们对数据库的修改 实际上是对 本地缓存的    修改,等一切操作完成之后 ,我们再将位于本地缓存中经过修改后的

Win8系统IE浏览器“允许网站使用缓存和数据库”选项有什么作用

  win8操作系统自带IE浏览器,如果遇到IE故障问题,需要进行一些简单的设置,设置过程中发现有一个"允许网站使用缓存和数据库"的选项,好像勾或者不勾选这个选项都不影响IE使用.那么Win8系统IE浏览器"允许网站使用缓存和数据库"选项到底有什么作用呢?下面小编来告诉大家它具体的功能和作用. 1.比如说这个网站首页有2张图片及需要运算的XML或JSON格式的网页数据库,每次访问都要下载10秒钟,如果你开启允许缓存,下次进入后,如果网站的数据尚未更新,就能减少等候那

如何为goldengate的数据库中保证数据同步的情况下为表加列

问题描述 如何为goldengate的数据库中保证数据同步的情况下为表加列 数据库现在做GOLDENGATE,DML同步了部分表,现在在source端,需要同步的表中加一列,在target表中也加一列,该如何操作才能保证加列的过程中数据同步且不丢失

Win8.1如何更改浏览器缓存和数据库大小

1.在桌面模式下使用鼠标左键单击左下方的[IE]图标. 2.选择浏览器右上方的[工具]图标. 3.选择[Internet选项]. 4.在弹出的"Internet 属性"窗口中切换到[常规]选项. 5.选择"常规"选项右下方的[设置]. 6.在"网站数据设置"窗口中切换到[缓存和数据库]选项. 7.在"当某个网站缓存或数据库超出此大小时通知我"的选项框中输入一个数值,然后点击下方的[确定]即可.

数据库一致性备份

本文主要讲解数据库一致性备份的执行步骤,备份数据库是指备份数据库的所有数据文件和控制文件,另外还有参数文件和口令文件.注意:备份数据库时 不需备份重做日志文件. 数据库的一致性备份是指:数据库一致性备份是指关闭了数据库后备份所有数据文件和控制文件的方法.当使用SHUTDOWN 命令正常关闭了数据库之后,所有数据库文件的当前SCN 值完全一致,所以关闭后的数据库备份被称为数据库一致性备份或者冷备份.适用:ARCHIVELOG.NOARCHIVELOG 一 列出要备份的数据文件和控制文件. SQL>

故障分析:数据库一致性关闭缓慢问题诊断

想必我们大家都知道,Shutdown immediate即一致性关闭数据库,数据库下次启动不需要做实例恢复即可open数据库.那么当数据库一致性关闭出现缓慢等状况时,该怎么办呢?那我们就来一起分析一下,数据库一致性关闭缓慢问题. shutdown immediate在数据库中会做哪些操作? 从以上图得知在shutdownimmediate关闭数据库只需要在数据库中强制选择检查点并关闭文件,不需要等待当前事物处理结束,不需要等待当前会话结束,不允许新连接. 引发shutdown immediate

脏读和数据库一致性的分析

脏读 脏读:在业务中读取的数据出现不一致的错误. package demo; /* 脏读:数据不一致的错误. * * 在对一个对象的方法加锁的时候,需要考虑业务的整体性, * 在demo中为setUser/getUser方法同时加锁synchronized同步关键字, * 保证业务的原子性,不然会出现业务错误. * */ public class DirtyRead { private String userName = "lc"; private String password =

Oracle数据库一致性读原理简介

在Oracle数据库中,undo主要有三大作用:提供一致性读(Consistent Read).回滚事务(Rollback Transaction)以及实例恢复 (Instance Recovery). 一致性读是相对于脏读(Dirty Read)而言的.假设某个表T中有10000条记录,获取所有记录需要15分钟时间.当前时间为9点整,某用户A 发出一条查询语句:select * from T,该语句在9点15分时执行完毕.当用户A执行该SQL语句到9点10分的时候,另外一个用户B发出了一条 d