这篇文章咱们继续聊分布式相关的内容。数据库
提到分布式系统,就必定绕不开“一致性”,此次咱们说说:最终一致性。网络
最终一致性是如今大部分高可用的分布式系统的核心思路。异步
估计有人对最终一致性不太熟,先来个简单介绍:分布式
最终一致性指的是系统中的全部分散在不一样节点的数据,通过必定时间后,最终可以达到符合业务定义的一致的状态。性能
划重点:spa
- 是数据一致性,不是事务一致性(ACID 是事务一致性);
- 存在条件:多个节点/系统;
- 不一致多是暂时的,最终要一致(鬼知道“最终”是多久)
好,正文开始。中间件
莫看江面平如镜,要看水底万丈深
最终一致性,一言以蔽之,过程松,结果紧。无论中间过程如何,结果必须符合业务需求,知足数据一致性的要求。blog
虽然,在实现中,有各类花样百出的方案,可是本质的思想都是同样的。咱们如今就来忽略那些乱花迷眼的过程,仔细探讨下最终一致性的本质。接口
何事居穷道不穷,乱时还与净时同
在我刚入行不久的时候,能力有限,菜鸟一个,只能作一些小的功能模块。我印象最深的就是订单模块。队列
用户下单,订单模块收到下单请求后,执行对应的订单业务逻辑。最终,会把订单插入到订单表,并返回下单结果给用户。用户结算后,订单模块就会去根据支付状况去更新订单状态。
就这点事儿,对我这个技术渣渣来讲,开始也着实费了一番手脚,不过最终也成了熟手,维护起这个模块来也得心应手了。
这种简单的小日子过了一阵子后,新任务来了!
产品经理告诉我,数据审计部门想要我维护的这个订单模块在订单完成后,能及时分发一份订单数据给他们。他们提供了一个接口,让我直接传数据给他们。
两个问题出现了:
问题 1:用户等待时间变长
最简单的实现就是我更新完订单数据后,再顺序去调用数据审计部门给的接口,把订单数据传过去。
可是,从用户结算成功到更新订单状态这一系列的流程是同步的,假设这一系列流程所花费的时间是 n 毫秒。这就意味着,用户须要等待至少 n 毫秒。若是再加上传给数据审计部门的操做时间,假设为 m 毫秒,则整个用户就须要等待就 n+m 毫秒。
整个功能用户等待时间成本上升,体验降低。以下图:
问题 2:部分红功,部分失败
引入新的接口后,某些时候调用这个接口可能会失败,好比网络问题啊,验证问题啊,接口服务失败啊,不少缘由。那么问题来了,新接口失败的时候怎么处理?
若是订单更新成功,传给数据审计部门的时候失败了,这种状况会让订单模块的后续处理变得很尴尬。
首先你不可能返回给客户端说你此次结算失败了,请求就没失败,你凭什么说人家失败了?其次,你又不能说此次业务上就是成功的,由于数据审计其实还挺重要的,它是业务逻辑的重要组成部分。
真是进退两难。
这两个问题的解决方案其中之一就是最终一致性。
咱们之前谈到过 CAP,知道若是牺牲必定的一致性就能够保证分区容错性和可用性。而最终一致性则是不能保证同时让全部的数据当时都符合业务需求,可是咱们能保证任什么时候候服务在内部出现问题的时候都是可对外服务的。
四哥我平时喜欢玩游戏,那咱们就用一个淘宝买 Switch 的例子,来解释最终一致性:
若是你想在淘宝同时买一个 Switch 的数字版游戏和一台 Switch,那么你付完钱后,你就能够马上获得数字版的游戏,可是,对于那台购买的 Switch,你就要等几天,等到快递投递到家才能够拿到。
来梳理下这个例子的细节:
- 首先淘宝上确定得有个对顾客售卖 Switch 和数字游戏的商家去接受咱们下的订单,并给你一个单号。
- 你获得了一个数字版游戏,可是没拿到 Switch。
- 你不知道这个商家背后 Switch 是怎么给你准备的,是否是中间他没货了还得跑别的商家串货,又或者没货等了两天才发给你(延迟发货能够给出别的理由,再也不赘述)。这些不重要,重要的是你明确对方接单了他就要完成这笔单子。
- 你下单成功以后,你就有了保障,你最终会拿到你的 Switch,只是你可能不太确定何时收到。
过了几天,你终于收到货了,恩,恭喜你成功入坑 Switch。
上面的例子就是咱们说的最终一致性。可是,这里有个很是很是重要的东西尚未凸显出来,即究竟是什么样的缘由在驱使咱们使用最终一致性?
答案就是数据的分发。
纸上得来终觉浅,绝知此事要躬行
为何咱们会出现须要最终一致性的状况呢?
由于咱们须要把数据分发到不一样的地方上去,而因为分发数据到不一样的地方,就会致使,可能中间分发过程当中出现分发成功或者失败的不一致状况,就须要最终一致性这种思路来处理这些状况。
恩,分发数据……OK,你想到了吧?
没错,经过 MQ 分发消息就能够处理分发数据的状况,而这正是最终一致性最经常使用的实现手段。
咱们把要分发的数据打包成消息,再发送给 MQ 中间件。中间件会广播这些数据给全部想要收到这些消息的服务。这些收到消息的服务就根据本身的业务状况对数据进行独立的处理。
回到咱们订单模块的那个例子,咱们能够采用两种方式使用最终一致性。
- 先插入数据库,后发消息给数据审计
这个方式,订单模块先更新订单状态。而后,把订单数据打包成消息发送到 MQ 中,订单模块的任务就结束了。剩下的任务就是由数据审计部门根据本身的业务,从 MQ 中获取消息后进行对应的处理。
这个方法里,咱们既保证数据库更新成功也保证数据被发送到了 MQ 中。最终,当数据审计部门收到消息并根据消息内容作完对应的处理后,则总体数据达到最终一致的状态。
- 只插入到 MQ 中
这个方式,订单模块直接收到请求后,将数据打包成消息放入到 MQ 中。
而后,再由订单模块本身和数据审计部门的服务分别从 MQ 中拿到对应的消息,再各自根据本身的业务逻辑该更新数据库的更新数据库,该走本身的审计的走本身的审计,最终达到一致的状态。
小荷才露尖尖角,早有蜻蜓立上头
在以上的例子中,咱们描述了最终一致性的核心思路,不保证数据状态能实时知足业务要求,可是就像咱们在线购物同样,咱们能保证在间隔了一段时间窗口后确定能知足业务需求。
然而,虽说起来简单,可是世间上的事情又哪里那么容易呢?根据业务的不一样,最终一致性分化出了多种实现思路。好比,
重试 + 逆向模式
在咱们作支付时,须要记帐,当记帐不成功时,咱们可能但愿能尽量的重试。当重试达到某种限制后,甚至咱们还要通知上游系统去提供一个重试和取消接口,让下游能通知上游重发消息,或者先暂时取消操做。
补救任务模式
在咱们作支付记帐失败了,咱们又尝试了重试 + 逆向模式取消了操做,那么此时就能够建立一个补救任务,等到后期能够保证记帐成功的时候去执行这个任务。
异步消息模式
在咱们作转帐的时候,咱们确定是要保证 A 转出后 B 转入这种业务是强一致性的。然而,可能此时又须要跨服务。同时,咱们还想尽可能保证性能。那么,这个时候咱们就能够先把本地对数据库的写操做和要跨服务的消息作成事务,而后,后期再根据消息被处理的状态作总体事务的提交和回滚。
能够看到,最终一致性的实现方式是多种多样的,可是,它始终逃不过一个核心,经过消息队列分发数据。在明白了这个根本原则后,之后咱们理解各类各样的分布式事务,分布式共识等就会容易许多了。
完