《吊打面试官》亿级流量高并发场景下,如何解决一致性问题

今天咱们重点讨论一下如何解决一致性问题,一致性在分布式架构面试中会经常问道,在传统单体架构中,数据状态的处理都在同一个服务和数据库中,而具有**ACID特性**的数据库支持强一致性,就是说数据库本身是不会出现不一致的状态的,比如我们常用的关系型数据库MySQL就是通过多版本控制协议(MVCC)的实现来保证了强一致性。

但是随着互联网的发展,用户增多&服务也越来越多越来越复杂,数据量和请求的并发量都上来了。为了满足这一变化,越来越多的系统从单体架构投入到服务化或者微服务架构。然而,微服务是把双刃剑,虽然能通过对服务的有效拆分来实现敏捷开发和自动化部署,并提高系统的水兵伸缩能力;但是微服务模式下,服务之间通过网络来通信(网络并不是可靠的),那么当网络异常时,就很容易引起各个系统之间的不一致。

本节课主要四个知识点

  1. 如何处理缓存与数据库不一致
  2. 数据库主从不一致性怎么办
  3. 分布式系统session一致性问题
  4. 分布式事务解决方案

 

面试题

如何处理缓存与数据库不一致

面试官心理分析

你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?

面试题剖析

由于操作缓存与操作数据库不是原子的,非常有可能出现执行失败,你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何处理缓存与数据库不一致?

本知识点主要讨论这么几个问题:

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

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

 

 

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

 

  1. 数据库有数据,缓存没有数据;
  2. 数据库有数据,缓存也有数据,数据不相等;
  3. 数据库没有数据,缓存有数据。

看一个数据库有数据,缓存也有数据,数据不相等的场景:

 

《吊打面试官》亿级流量高并发场景下,如何解决一致性问题

假设先写数据库,再淘汰缓存:第一步写数据库操作成功,第二步淘汰缓存失败,则会出现数据库中是新数据,缓存中是旧数据,数据不一致。

《吊打面试官》亿级流量高并发场景下,如何解决一致性问题

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

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

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

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

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

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

 

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

  1. 对删除缓存进行重试,数据的一致性要求越高,我越是重试得快。
  2. 定期全量更新,简单地说,就是我定期把缓存全部清掉,然后再全量加载。
  3. 对同一数据的读取/写入请求串行,保证同一数据的请求消息,都会路由到同一个服务上。一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求 “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去。串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
  4. 给所有的缓存一个失效期。

第四种方案可以说是一个大杀器,任何不一致,都可以靠失效期解决,失效期越短,数据一致性越高。但是失效期越短,查数据库就会越频繁。因此失效期应该根据业务来定。

 

面试题

数据库主从不一致性怎么办

面试官心理分析

你只要用数据库主从,就可能会涉及到由于写入主库的数据量大,导致数据库主库同步到从库延迟问题,如果主从延迟,就一定会有数据一致性的问题,由于主从延时导致读取到旧数据,那么你如何解决一致性问题?

本知识点主要讨论这么几个问题:

(1)啥时候数据库主从数据会不一致

(2)如何保证数据库主从的一致性

 

面试题剖析

大部分互联网的业务都是“读多写少”的场景,数据库层面,读性能往往成为瓶颈。业界通常采用“一主多从,读写分离,冗余多个读库”的数据库架构来提升数据库的读性能。

《吊打面试官》亿级流量高并发场景下,如何解决一致性问题

(1)系统先对DB-master进行了一个写操作,写主库

(2)很短的时间内并发进行了一个读操作,读从库,此时主从同步没有完成,故读取到了一个旧数据

(3)主从同步完成

有没有办法解决或者缓解这类“由于主从延时导致读取到旧数据”的问题呢,

 

如何保证数据库主从的一致性

方案一 数据库机制的强一致

《吊打面试官》亿级流量高并发场景下,如何解决一致性问题

主从同步有一个时间差,假设是1000ms,这个时间差,有读请求落到从库上就会产生读从库的数据是旧数据,有没有办法做到,等主从同步完成之后,主库上的写请求再返回呢?答案是肯定的,就是大家常说的“半同步复制”

(1)系统先对DB-主库,进行了一个写操作,写主库

(2)等主从同步完成,写主库的请求才返回

(3)读从库,读到最新的数据(如果读请求先完成,写请求后完成,读取到的是“当时”最新的数据)

方案优点:利用数据库原生功能,比较简单

方案缺点:主库的写请求时延会增长,吞吐量会降低

方案二 强制读主库

《吊打面试官》亿级流量高并发场景下,如何解决一致性问题

 

如果不使用“增加从库”的方式来增加提升系统的读性能,完全可以读写都落到主库,这样就不会出现不一致了:

方案优点:“一致性”上不需要进行系统改造

方案缺点:只能通过缓存来提升系统的读性能

 

 

方案三 数据库中间件

《吊打面试官》亿级流量高并发场景下,如何解决一致性问题

(1)所有的读写都走数据库中间件,通常情况下,写请求路由到主库,读请求路由到从库

(2)记录所有路由到写库的key,在经验主从同步时间窗口内(假设是500ms),如果有读请求访问中间件,此时有可能从库还是旧数据,就把这个key上的读请求路由到主库

(3)经验主从同步时间过完后,对应key的读请求继续路由到从库

方案优点:能保证绝对一致

方案缺点:数据库中间件的成本比较高

 

方案四  缓存

既然数据库中间件的成本比较高,有没有更低成本的方案来记录某一个库的某一个key上发生了写请求呢?很容易想到使用缓存,当写请求发生的时候:

  1. 将某个库上的某个key要发生写操作,记录在cache里《吊打面试官》亿级流量高并发场景下,如何解决一致性问题
  2. 先到缓存中查看,对应库的对应key有没有相关数据
  3. 如果缓存中存在,相关数据,说明这个key上刚发生过写操作,此时需要将请求路由到主库读最新的数据
  4. 如果不存在,说明这个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+最终一致性

 

好了,欢迎你跟我进入下一讲, “亿级流量高并发场景下,如何解决一致性问题”