天天看点

分布式系统一致性问题

作者:Java学习笔记整理

什么是一致性

一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。

  • 强一致性:这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大。
  • 弱一致性:这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态
  • 最终一致性:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型

一致性问题

下订单和扣库存

电商系统中有一个经典的案例 , 即下订单和扣库存如何保持一致。 如果先下订单,扣库存失败,那么将会导致超卖;如果下订单不成功,扣库存成功,那么会导致少卖。 这两种情况都会导致运营成本增加,在严重情况下需要赔付。

同步调用超时

系统 A 同步调用系统 B 超时,系统 A 可以明确得到超时反馈,但是无法确定系统 B 是否已经完成了预设的功能。 于是,系统 A 不知道应该继续做什么,如何反馈给使用方。

异步回调超时

系统 A 同步调用系统 B 发起指令,系统 B 采用受理模式,受理后则返回成功信息 ,然后系统 B 处理后异步通知系统 A 处理结果。在这个过程中,如果系统 A 由于某种原因迟迟没有收到回调结果,那么这两个系统间的状态就不一致。

解决一致性问题的模式和思路

事务分类

  • 本地事务:
  1. 同一数据库和服务器,称为本地事务
分布式系统一致性问题

本地事务

  • 分布式事务:

分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,且属于不同的应用,分布式事务需要保证这些操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

分布式系统一致性问题

分布式事务

事务的ACID

  • A(Atomic):原子性,构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成功部分失 败的情况。
  • C(Consistency):一致性,在事务执行前后,数据库的一致性约束没有被破坏。比如:张三向李四转100元, 转账前和转账后的数据是正确状态这叫一致性,如果出现张三转出100元,李四账户没有增加100元这就出现了数 据错误,就没有达到一致性。
  • I(Isolation):隔离性,数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互不干扰,一个事 务不能看到其他事务运行过程的中间状态。通过配置事务隔离级别可以避脏读、重复读等问题。
  • D(Durability):持久性,事务完成之后,该事务对数据的更改会被持久化到数据库,且不会被回滚。

具有 ACID 特性的数据库支持强一致性,强一致性代表数据库本身不会出现不一致, 每个事务都是原子的,或者成功或者失败,事物间是隔离的,互相完全不受影响,而且最终状态是持久落盘的。

CAP定律

这个定理的内容是指的是在一个分布式系统中、Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。

  • 一致性(C)

在分布式系统中的所有数据备份,在同一时刻是否同样的值。

  • 可用性(A)

在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。

  • 分区容错性(P)

以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择

例子:

  1. CP:当库存服务减库存以后,那么需要将数据同步到其他的服务上,这是为了保证数据一致性C,但是网络是不可靠的,所以我们系统就需要保证分区容错性P,也就是我们必须容忍网络所带来的的一些问题,此时如果我们想保证C那么就需要舍弃A,也就是说我们在保证C的情况下,就必须舍弃A,也就是CP无法保证高可用。
  2. AP: 如果为了保证A,高可用的情况下,也就是必须在限定时间内给出响应,同样由于网络不可靠P,订单服务就有可能无法拿到新的数据,但是也要给用户作出响应,那么也就无法保证C一致性。所以AP是无法保证强一致性的。
  3. CA: 如果我们想保证CA,也就是高可用和一致性,也就是必须保证网络良好才能实现,那么也就是说我们需要将库存、订单、用户放到一起,但是这种情况也就丧失了P这个保证,这个时候系统也就不是分布式系统了。

故:在分布式系统中,p是必然的存在的,所以我们只能在C和A之间进行取舍,在这种条件下就诞生了BASE理论

BASE理论

BASE是Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结, 是基于CAP定理逐步演化而来的。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。

  • 基本可用

基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性—-注意,这绝不等价于系统不可用。比如:

(1)响应时间上的损失。正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒

(2)系统功能上的损失:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面

  • 软状态

软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时

  • 最终一致性

最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

保证最终一致性模式

查询模式

任何服务操作都需要提供一个查询接口,用来向外部输出操作执行的状态。 服务操作的使用方可以通过查询接口得知服务操作执行的状态,然后根据不同的状态来做不同的处理操作。

补偿模式

有了上面的查询模式,在任何情况下,我们都能得知具体的操作所处的状态,如果整个操作都处于不正常的状态,则我们需要修正操作中有问题的子操作,这可能需要重新执行未完成的子操作,后者取消已经完成的子操作,通过修复使整个分布式系统达到一致。 为了让系统最终达到一致状态而做的努力都叫作补偿。

补偿操作根据发起形式分为以下几种。

  • 自动恢复:程序根据发生不一致的环境,通过继续进行未完成的操作,或者回滚已经完成的操作,来自动达到一致状态。
  • 通知运营:如果程序无法自动恢复,并且设计时考虑到了不一致的场景,则可以提供运营功能,通过运营手工进行补偿。
  • 技术运营:如果很不巧,系统无法自动回复,又没有运营功能,那么必须通过技术手段来解决,技术手段包括进行数据库变更或者代码变更,这是最糟的一种场景,也是我们在生产中尽量避免的场景。

异步确保模式

异步确保模式是补偿模式的一个典型案例,经常应用到使用方对响应时间要求不太高的场景中,通常把这类操作从主流程中摘除,通过异步的方式进行处理,处理后把结果通过通知系统通知给使用方 。这个方案的最大好处是能够对高并发流量进行消峰,例如:电商系统中的物流、配迭,以及支付系统中的计费、入账等。

在实践中将要执行的异步操作封装后持久入库,然后通过定时捞取未完成的任务进行补偿操作来实现异步确保模式,只要定时系统足够健壮,则任何任务最终都会被成功执行。

分布式系统一致性问题

异步确保模式

定期校对模式

定期校对模式多应用于金融系统中。金融系统由于涉及资金安全, 需要保证准确性, 所以需要多重的一致性保证机制,包括商户交易对账、系统间的一致性对账、现金对账、账务对账、续费对账等,这些都属于定期校对模式。

可靠消息模式

在分布式系统中,对于主流程中优先级 比较低的操作,大多采用异步的方式执行,也就是前面提到的异步确保模型,为了让异步操作的调用方和被调用方充分解楠,也由于专业的消息队列本身具有可伸缩、可分片、可持久等功能,我们通常通过消息队列实现异步化。对于消息队列,我们需要建立特殊的设施来保证可靠的消息发送及处理机的幂等性。

  • 消息的可靠发送

在发送消息之前将消息持久到数据库,状态标记为待发送, 然后发送消息,如果发送成功,则将消息改为发送成功。定时任务定时从数据库捞取在一定时间内未发送的消息并将消息发送。

  • 消息处理器的明幂等性

如果我们要保证可靠地发送消息,简单来说就是要保证消息一定发送出去,那么需要有重试机制。有了重试机制后,消息就一定会重复,那么我们需要对重复的问题进行处理。

分布式一致性协议

两阶段提交协议(2PC)

  1. 准备阶段: 协调者向参与者发起指令,参与者评估自己的状态,如果参与者评估指令可以完成,则会写 redo 或者 undo 日在、( Write- Ahead Log 的一种),然后锁定资源,执行操作,但是并不提交。
  2. 提交阶段: 如果每个参与者明确返回准备成功,也就是预留资源和执行操作成功,则协调者向参与者发起提交指令,参与者提交资源变更的事务,释放锁定的资源;如果任何一个参与者明确返回准备失败, 也就是预留资源或者执行操作失败,则协调者向参与者发起中止指令,参与者取消己经变更的事务,执行 undo 日志,释放锁定的资源。
分布式系统一致性问题

两阶段提交协议(2PC)

我们看到两阶段提交协议在准备阶段锁定资源,这是一个重量级的操作 , 能保证强一致性,但是实现起来复杂、成本较高、不够灵活,更重要的是它有如下致命的问题。

  1. 阻塞:从上面的描述来看,对于任何一次指令都必须收到明确的响应,才会继续进行下一步,否则处于阻塞状态,占用的资源被一直锁定,不会被释放。
  2. 单点故障:如果协调者宕机,参与者没有协调者指挥,则会一直阻塞,尽管可以通过选举新的协调者替代原有协调者,但是如果协调者在发送一个提交指令后宕机,而提交指令仅仅被一个参与者接收,并且参与者接收后也宕机,则新上任的协调者无法处理这种情况。
  3. 数据不一致:协调者发送提交指令,有的参与者接收到并执行了事务,有的参与者没有接收到事务就没有执行事务,多个参与者之间是不一致的。

三阶段提交协议(3PC)

三阶段提交协议是两阶段提交协议的改进版本。它通过超时机制解决了阻塞的问题, 井且把两个阶段增加为以下三个阶段。

  1. 询问阶段:协调者询问参与者是否可以完成指令,协调者只需要回答是或不是,而不需要做真正的操作 ,这个阶段超时会导致中止。
  2. 准备阶段: 如果在询问阶段所有参与者都返回可以执行操作,则协调者向参与者发送预执行请求,然后参与者写 redo 和 undo 日志,执行操作但是不提交操作:如果在询问阶段任意参与者返回不能执行操作的结果,则协调者向参与者发送中止请求,这里的逻辑与两阶段提交协议的准备阶段是相似的。
  3. 提交阶段:如果每个参与者在准备阶段返回准备成功,也就是说预留资源和执行操作成功,则协调者向参与者发起提交指令,参与者提交资源变更的事务,释放锁定的资源:如果任何参与者返回准备失败,也就是说预留资源或者执行操作失败,则协调者向参与者发起中止指令,参与者取消已经变更的事务,执行 undo 日志,释放锁定的资源,这里的逻辑与两阶段提交协议的提交阶段一致。
分布式系统一致性问题

三阶段提交协议(3PC)

三阶段提交协议与两阶段提交协议主要有以下两个不同点。

  1. 增加了一个询问阶段,询问阶段可以确保尽可能早地发现无法执行操作而需要中止的行为,但是它并不能发现所有这种行为,只会减少这种情况的发生。
  2. 在准备阶段以后,协调者和参与者执行的任务中都增加了超时,一旦超时,则协调者和参与者都会继续提交事务 ,默认为成功,这也是根据概率统计超时后默认为成功的正确性最大。

三阶段提交协议与两阶段提交协议相比,具有如上优点,但是一旦发生超时,系统仍然会发生不一致,只不过这种情况很少见,好处是至少不会阻塞和永远锁定资源。

前两节讲解了两阶段提交协议和三阶段提交协议,实际上它们能解决前边的分布式事务的问题,但是遇到极端情况时,系统会产生阻塞或者不一致的问题,需要运营或者技术人员解决。两阶段及三阶段方案中都包含多个参与者、多个阶段实现一个事务,实现复杂,性能也是一个很大的问题,因此,在互联网的高并发系统中,鲜有使用两阶段提交和三阶段提交协议的场景。

TCC

TCC 协议将一个任务拆分成 Try、 Confirm 、 Cancel 三个步骤,正常的流程会先执行Try可,如果执行没有问题,则再执行 Confirm ,如果执行过程中出了问题,则执行操作的逆操作Cancel 。

分布式系统一致性问题

TCC

从正常的流程上讲,这仍然是一个两阶段提交协议,但是在执行出现问题时有一定的自我修复能力,如果任何参与者出现了问题,则协调者通过执行操作的逆操作来 Cancel 之前的操作,达到最终的一致状态。

如果遇到极端情况,则 TCC 会有很多问题,例如,如果在取消时一些参与者收到指令,而另一些参与者没有收到指令,则整个系统仍然是不一致的 。对于这种复杂的情况,系统首先会通过补偿的方式尝试自动修复,如果系统无法修复,则必须由人工参与解决。

以上的思路都是能很好解决分布式系统一致性问题,市面也有基于这些思路实现的优秀框架,比如Seata,后续也会介绍此框架的运用。

继续阅读