原创 2016-03-16 58沈剑 架构师之路web
本文主要讨论这么几个问题:sql
(1)啥时候数据库和缓存中的数据会不一致数据库
(2)不一致优化思路编程
(3)如何保证数据库与缓存的一致性后端
上一篇《缓存架构设计细节二三事》(点击查看)引发了普遍的讨论,其中有一个结论:当数据发生变化时,“先淘汰缓存,再修改数据库”这个点是你们讨论的最多的。缓存
上篇文章得出这个结论的依据是,因为操做缓存与操做数据库不是原子的,很是有可能出现执行失败。tomcat
假设先写数据库,再淘汰缓存:第一步写数据库操做成功,第二步淘汰缓存失败,则会出现DB中是新数据,Cache中是旧数据,数据不一致【如上图:db中是新数据,cache中是旧数据】。服务器
假设先淘汰缓存,再写数据库:第一步淘汰缓存成功,第二步写数据库失败,则只会引起一次Cache miss【如上图:cache中无数据,db中是旧数据】。微信
结论:先淘汰缓存,再写数据库。网络
引起你们热烈讨论的点是“先操做缓存,在写数据库成功以前,若是有读请求发生,可能致使旧数据入缓存,引起数据不一致”,这就是本文要讨论的主题。
回顾一下上一篇文章中对缓存、数据库进行读写操做的流程。
写流程:
(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先完成了,读出了脏数据,脏数据又入了缓存,缓存与数据库中的数据不一致出现了
可否作到先发出的请求必定先执行完成呢?常见的思路是“串行化”,今天将和你们一块儿探讨“串行化”这个点。
先一块儿细看一下,在一个服务中,并发的多个读写SQL通常是怎么执行的
上图是一个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的上下游及内部结构,再一块儿看一下应用层上下游及内部结构
上图是一个业务应用的上下游及服务内部详细展开,细节以下:
(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有同步时延)?
答:下一篇文章和你们分享。
若是你有烘培、摄影、绘画、音乐、舞蹈、健身、瑜伽、早教、编程培训、交友的需求或者技能,长按二维码关注“美好到家”,点击“达人报名”,成为达人,赚取外快。(很差意思,答应了小马哥帮他宣传的,大伙帮忙关注下,帮忙完成KPI哈)
==【完】==
回【58】58怎么玩数据库架构
回【微信】微信为啥这么省流量
回【秒杀】秒杀系统架构优化思路
回【百度】百度咋作长文本去重(一分钟系列)
回【id】细聊分布式ID生成方法
回【冗余】细聊冗余表数据一致性
回【招聘】入职58到家
回【缓存】缓存架构设计细节二三事
【小游戏:回大于10的整数,随机返回好文,试试看哟】
欢迎讨论,有问必回。
转发,只需3秒。