《吊打面试官》亿级流量高并发场景下,如何解决一致性问题
今天咱们重点讨论一下如何解决一致性问题,一致性在分布式架构面试中会经常问道,在传统单体架构中,数据状态的处理都在同一个服务和数据库中,而具有**ACID特性**的数据库支持强一致性,就是说数据库本身是不会出现不一致的状态的,比如我们常用的关系型数据库MySQL就是通过多版本控制协议(MVCC)的实现来保证了强一致性。
但是随着互联网的发展,用户增多&服务也越来越多越来越复杂,数据量和请求的并发量都上来了。为了满足这一变化,越来越多的系统从单体架构投入到服务化或者微服务架构。然而,微服务是把双刃剑,虽然能通过对服务的有效拆分来实现敏捷开发和自动化部署,并提高系统的水兵伸缩能力;但是微服务模式下,服务之间通过网络来通信(网络并不是可靠的),那么当网络异常时,就很容易引起各个系统之间的不一致。
本节课主要四个知识点
- 如何处理缓存与数据库不一致
- 数据库主从不一致性怎么办
- 分布式系统session一致性问题
- 分布式事务解决方案
面试题
如何处理缓存与数据库不一致?
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
由于操作缓存与操作数据库不是原子的,非常有可能出现执行失败,你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何处理缓存与数据库不一致?
本知识点主要讨论这么几个问题:
(1)啥时候数据库和缓存中的数据会不一致
(2)如何保证数据库与缓存的一致性
什么情况下可能出现缓存和数据库中数据不一致呢?
- 数据库有数据,缓存没有数据;
- 数据库有数据,缓存也有数据,数据不相等;
- 数据库没有数据,缓存有数据。
看一个数据库有数据,缓存也有数据,数据不相等的场景:
假设先写数据库,再淘汰缓存:第一步写数据库操作成功,第二步淘汰缓存失败,则会出现数据库中是新数据,缓存中是旧数据,数据不一致。
在分布式环境下,数据的读写都是并发的,上游有多个应用,通过一个服务的多个部署(为了保证可用性,一定是部署多份的),对同一个数据进行读写,在数据库层面并发的读写并不能保证完成顺序,也就是说后发出的读请求很可能先完成(读出脏数据):
(a)发生了写请求A,A的第一步淘汰了缓存cache(如上图中的1)
(b)A的第二步写数据库,发出修改请求(如上图中的2)
(c)发生了读请求B,B的第一步读取cache,发现cache中是空的(如上图中的步骤3)
(d)B的第二步读取数据库,发出读取请求,此时A的第二步写数据还没完成,读出了一个脏数据放入缓存cache(如上图中的步骤4)
即在数据库层面,后发出的请求4比先发出的请求2先完成了,读出了脏数据,脏数据又入了缓存,缓存与数据库中的数据不一致出现了
如何保证数据库与缓存的一致性
- 对删除缓存进行重试,数据的一致性要求越高,我越是重试得快。
- 定期全量更新,简单地说,就是我定期把缓存全部清掉,然后再全量加载。
- 对同一数据的读取/写入请求串行,保证同一数据的请求消息,都会路由到同一个服务上。一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求 “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去。串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
- 给所有的缓存一个失效期。
第四种方案可以说是一个大杀器,任何不一致,都可以靠失效期解决,失效期越短,数据一致性越高。但是失效期越短,查数据库就会越频繁。因此失效期应该根据业务来定。
面试题
数据库主从不一致性怎么办?
面试官心理分析
你只要用数据库主从,就可能会涉及到由于写入主库的数据量大,导致数据库主库同步到从库延迟问题,如果主从延迟,就一定会有数据一致性的问题,由于主从延时导致读取到旧数据,那么你如何解决一致性问题?
本知识点主要讨论这么几个问题:
(1)啥时候数据库主从数据会不一致
(2)如何保证数据库主从的一致性
面试题剖析
大部分互联网的业务都是“读多写少”的场景,数据库层面,读性能往往成为瓶颈。业界通常采用“一主多从,读写分离,冗余多个读库”的数据库架构来提升数据库的读性能。
(1)系统先对DB-master进行了一个写操作,写主库
(2)很短的时间内并发进行了一个读操作,读从库,此时主从同步没有完成,故读取到了一个旧数据
(3)主从同步完成
有没有办法解决或者缓解这类“由于主从延时导致读取到旧数据”的问题呢,
如何保证数据库主从的一致性
方案一 数据库机制的强一致
主从同步有一个时间差,假设是1000ms,这个时间差,有读请求落到从库上,就会产生读从库的数据是旧数据,有没有办法做到,等主从同步完成之后,主库上的写请求再返回呢?答案是肯定的,就是大家常说的“半同步复制”
(1)系统先对DB-主库,进行了一个写操作,写主库
(2)等主从同步完成,写主库的请求才返回
(3)读从库,读到最新的数据(如果读请求先完成,写请求后完成,读取到的是“当时”最新的数据)
方案优点:利用数据库原生功能,比较简单
方案缺点:主库的写请求时延会增长,吞吐量会降低
方案二 强制读主库
如果不使用“增加从库”的方式来增加提升系统的读性能,完全可以读写都落到主库,这样就不会出现不一致了:
方案优点:“一致性”上不需要进行系统改造
方案缺点:只能通过缓存来提升系统的读性能
方案三 数据库中间件
(1)所有的读写都走数据库中间件,通常情况下,写请求路由到主库,读请求路由到从库
(2)记录所有路由到写库的key,在经验主从同步时间窗口内(假设是500ms),如果有读请求访问中间件,此时有可能从库还是旧数据,就把这个key上的读请求路由到主库
(3)经验主从同步时间过完后,对应key的读请求继续路由到从库
方案优点:能保证绝对一致
方案缺点:数据库中间件的成本比较高
方案四 缓存
既然数据库中间件的成本比较高,有没有更低成本的方案来记录某一个库的某一个key上发生了写请求呢?很容易想到使用缓存,当写请求发生的时候:
-
将某个库上的某个key要发生写操作,记录在cache里
- 先到缓存中查看,对应库的对应key有没有相关数据
- 如果缓存中存在,相关数据,说明这个key上刚发生过写操作,此时需要将请求路由到主库读最新的数据
- 如果不存在,说明这个key上近期没有发生过写操作,此时将请求路由到从库,继续读写分离
方案优点:相对数据库中间件,成本较低
方案缺点:为了保证“一致性”,引入了一个cache组件,并且读写数据库时都多了一步cache操作
为了解决主从数据库读取旧数据的问题,常用的方案有四种:
(1)利用数据库机制保持强一致
(2)强制读主
(3)数据库中间件
(4)利用缓存记录写key
分布式系统session一致性问题
面试题
分布式系统session一致性问题?
面试官心理分析
你只要系统是高可用,就会每次http短连接请求,不一定能路由到正确的session上,那么你如何解决session一致性问题?
面试题剖析
什么是session?
服务器为每个用户创建一个会话,存储用户的相关信息,以便多次请求能够定位到同一个上下文。
Web开发中,应用服务器 可以自动为同一个浏览器的访问用户自动创建session,提供数据存储功能。最常见的,会把用户的登录信息、用户信息存储在session中,以保持登录状态。
什么是session一致性问题?
只要用户不重启浏览器,每次http短连接请求,理论上服务端都能定位到session,保持会话。
当只有一台 应用服务器 提供服务时,每次http短连接请求,都能够正确路由到存储session对应的应用服务器(因为只有一台),此时的应用服务器是无法保证高可用的。
如上图,假设用户包含登录信息的session都记录在第一台 应用服务器 上,反向代理如果将请求路由到另一台应用服务器上,可能就找不到相关信息,而导致用户需要重新登录。在 应用服务器 高可用时,如何保证session路由的一致性,是今天将要讨论的问题。
方案一 session同步
思路:多个应用服务器之间相互同步session,这样每个应用服务器之间都包含全部的session
优点:应用服务器支持的功能,应用程序不需要修改代码
不足:
•session的同步需要数据传输,占内网带宽,有时延
•所有应用服务器都包含所有session数据,数据量受内存限制,无法水平扩展
方案二 客户端存储
思路:服务端存储所有用户的session,内存占用较大,可以将session存储到浏览器cookie中,每个端只要存储一个用户的数据了
优点:服务端不需要存储
缺点:
•每次http请求都携带session,占外网带宽
•数据存储在端上,并在网络传输,存在泄漏、篡改、窃取等安全隐患
•session存储的数据大小受cookie限制
方案三 服务端存储
思路:将session存储在应用服务器后端的存储层,数据库或者缓存
优点:
•没有安全隐患
•可以水平扩展,数据库/缓存水平切分即可
•应用服务器重启或者扩容都不会有session丢失
不足:增加了一次网络调用,并且需要修改应用代码
对于数据库存储还是缓存,个人推荐后者:session读取的频率会很高,数据库压力会比较大。如果有session高可用需求,cache可以做高可用,但大部分情况下session可以丢失,一般也不需要考虑高可用。
保证session一致性的架构设计常见方法:
•session同步法:多台应用服务器相互同步数据
•客户端存储法:一个用户只存储自己的数据
•后端统一存储:应用服务器重启和扩容,session也不会丢失
分布式事务解决方案
面试题
什么是分布式事务?
面试官心理分析
你只要在分布式环境下,每个节点都可以知晓自己操作的成功或者失败,却无法知道其他节点操作的成功或失败,当一个分布式事务跨多个节点时,如何保持事务的原子性与一致性?
面试题剖析
什么是两阶段提交?
二阶段提交2PC(Two phase Commit)是一种,在分布式环境下,所有节点进行事务提交,保持一致性的算法。
它通过引入一个协调者(Coordinator)来统一掌控所有参与者(Participant)的操作结果,并指示它们是否要把操作结果进行真正的提交(commit)或者回滚(rollback)。
为什么叫两阶段提交?
顾名思义,2PC分为两个阶段:
- 投票阶段(voting phase):参与者通知协调者,协调者反馈结果;
画外音:可以理解为单机事务的trx.exec()。
- 提交阶段(commit phase):收到参与者的反馈后,协调者再向参与者发出通知,根据反馈情况决定各参与者是否要提交还是回滚;
画外音:可以理解为单机事务的trx.commit() 或者 trx.rollback()。
举个栗子:
甲乙丙丁四人要组织一个会议,需要确定会议时间,不妨设甲是协调者,乙丙丁是参与者。
投票阶段
(1)甲发邮件给乙丙丁,通知明天十点开会,询问是否有时间;
(2)乙回复有时间;
(3)丙回复有时间;
(4)丁迟迟不回复,此时对于这个事务,甲乙丙均处于阻塞状态,算法无法继续进行;
提交阶段
(1)协调者甲将收集到的结果通知给乙丙丁;
(2)乙收到通知,并回复协调者;
(3)丙收到通知,并回复协调者;
(4)丁收到通知,并回复协调者;
如果甲没有收到所有回复,则分布式事务迟迟不会结束,下一轮投票则迟迟不会开展。
两阶段提交的缺陷?
2PC在执行过程中,所有节点都处于阻塞状态,所有节点所持有的资源(例如数据库数据,本地文件等)都处于封锁状态。
典型情况为:
(1)某一个参与者回复消息之前,所有参与者以及协调者都处于阻塞状态;
(2)在协调者发出消息之前,所有参与者都处于阻塞状态;
另外,如有协调者或者某个参与者出现了崩溃,为了避免整个算法处于一个完全阻塞状态,往往需要借助超时机制来将算法继续向前推进。
总的来说,2PC是一种比较保守并且低效的算法,分布式事务真的很难做。
事务的方案会有什么潜在问题?
答:互联网的业务特点,数据量较大,并发量较大,经常使用拆库的方式提升系统的性能。如果进行了拆库,余额、订单、流水可能分布在不同的数据库上,甚至不同的数据库实例上,此时就不能用数据库原生事务来保证数据的一致性了。
高并发易落地的分布式事务,是行业没有很好解决的难题,那怎么办呢?
答:补偿事务是一种常见的实践。
什么是补偿事务?
答:补偿事务,是一种在业务端实施业务逆向操作事务。
分布式系统非常关注三个指标:
- 数据一致性
- 系统可用性
- 节点连通性与扩展性
关于一致性
数据“强一致性”,是希望系统只读到最新写入的数据,例如:通过单点串行化的方式,就能够达到这个效果。
关于session一致性,DB主从一致性,DB双主一致性,DB与Cache一致性,分布式事务一致性
关于可用性
如果系统每运行100个时间单位,会有1个时间单位无法提供服务,则说系统的可用性是99%。
可用性和可靠性是比较容易搞混的两个指标,以一台取款机为例:
- 正确的输入,能够取到正确的钱,表示系统可靠
- 取款机7*24小时提供服务,表示系统可用
保证系统高可用的方法是:
- 冗余
- 故障自动转移
关于连通性与扩展性
分布式系统,往往有多个节点,每个节点之间,都不是完全独立的,需要相互通信,当发生节点无法联通时,数据是否还能保持一致,系统要如何进行容错处理,是需要考虑的。
什么是CAP定理?
CAP定理,是对上述分布式系统的三个特性,进行了归纳:
- 一致性(Consistency)
- 可用性(Availability)
- 分区容忍性(Partition Tolerance)
并且,定理指出,在系统实现时,这三者最多兼顾两点。
CP(一致性+分区容忍性)
牺牲可用性,保证一致性,主要对于一些对一致性要求较高的场景。
比如支付,抢红包、用户数据记录等场景,
AP(可用性+分区容忍性)
牺牲一致性保证可用性,主要能提高用户体验,提高性能等。
比如日志记录,数据投递、存储系统配置等场景。
CA(一致性+可用性)
如果不保证分区容忍性,那么只要产生断网宕机等意外情况,系统就会无法工作,这会极大影响用户体验,目前应该很少有场景会完全不考虑P。
总结
- CAP可以理解为一致性,可用性,联通与扩展性
- CAP三者只能取其二
- 最常见的实践是AP+最终一致性
好了,欢迎你跟我进入下一讲, “亿级流量高并发场景下,如何解决一致性问题”