热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

缓存与数据库一致性保证(二)

2019独角兽企业重金招聘Python工程师标准本文主要讨论这么几个点:(1)啥时候数据库和缓存中的数据会不一致(2&

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

本文主要讨论这么几个点:

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

(2)不一致优化思路

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

 

一、需求缘起

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


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

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

 

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

 

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

 

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

 

二、为什么数据会不一致

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

写流程:

(1)先淘汰cache

(2)再写db

读流程:

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

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

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

 

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

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

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

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

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

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

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

 

三、不一致优化思路

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

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

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

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

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

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

(3.1)最上层是任务队列

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

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

 

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

void work_thread_routine(){

Task t = TaskQueue.pop(); // 获取任务

// 任务逻辑处理,生成sql语句

DBConnection c = CPool.GetDBConnection(); // DB连接池获取一个DB连接

c.execSQL(sql); // 通过DB连接执行sql语句

CPool.PutDBConnection(c); // DB连接放回DB连接池

}

 

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

:不行,因为

(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层面的执行是串行化的,实际上服务是分布式部署的,在全局范围内的访问仍是并行的,怎么解决呢?能不能做到同一个数据的访问一定落到同一个服务呢?

 

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

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

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

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

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

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

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

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

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

 

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

voidwork_thread_routine(){

Task t = TaskQueue.pop(); // 获取任务

// 任务逻辑处理,组成一个网络包packet,调用下游RPC接口

ServiceConnection c = CPool.GetServiceConnection(); // Service连接池获取一个Service连接

c.Send(packet); // 通过Service连接发送报文执行RPC请求

CPool.PutServiceConnection(c); // Service连接放回Service连接池

}

 

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

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

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

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

                                                                                  

五、总结

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

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

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

 

六、遗留

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

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

 

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

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

 

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

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



转:https://my.oschina.net/snowing1990/blog/639467



推荐阅读
  • 本文介绍了OpenStack的逻辑概念以及其构成简介,包括了软件开源项目、基础设施资源管理平台、三大核心组件等内容。同时还介绍了Horizon(UI模块)等相关信息。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 云原生应用最佳开发实践之十二原则(12factor)
    目录简介一、基准代码二、依赖三、配置四、后端配置五、构建、发布、运行六、进程七、端口绑定八、并发九、易处理十、开发与线上环境等价十一、日志十二、进程管理当 ... [详细]
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
  • 本文总结了初学者在使用dubbo设计架构过程中遇到的问题,并提供了相应的解决方法。问题包括传输字节流限制、分布式事务、序列化、多点部署、zk端口冲突、服务失败请求3次机制以及启动时检查。通过解决这些问题,初学者能够更好地理解和应用dubbo设计架构。 ... [详细]
  • Hadoop2.6.0 + 云centos +伪分布式只谈部署
    3.0.3玩不好,现将2.6.0tar.gz上传到usr,chmod-Rhadoop:hadophadoop-2.6.0,rm掉3.0.32.在etcp ... [详细]
  • 基于分布式锁的防止重复请求解决方案
    一、前言关于重复请求,指的是我们服务端接收到很短的时间内的多个相同内容的重复请求。而这样的重复请求如果是幂等的(每次请求的结果都相同,如查 ... [详细]
  • 14亿人的大项目,腾讯云数据库拿下!
    全国人 ... [详细]
  • 浅析对象 VO、DTO、DO、PO 概念
    作者|CatQi链接|cnblogs.comqixuejiap4390086.html前言由于此订阅号换了个皮肤,导致用户接受文章不及时。读者可以打开订阅号「Web项 ... [详细]
  • 后台自动化测试与持续部署实践
    后台自动化测试与持续部署实践https:mp.weixin.qq.comslqwGUCKZM0AvEw_xh-7BDA后台自动化测试与持续部署实践原创 腾讯程序员 腾讯技术工程 2 ... [详细]
author-avatar
dx152
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有