分布式事务浅析与应用
- 分布式基础理论 CAP & BASE
- 两个实际问题思考
当应用达到一定量级时,必定会涉及到分库分表以及DDBS的使用,但是,并不是所有的分库分表数据库 DDBS都支持分布式事务的,因为这种场景的存在,导致如果在同一个表中需要基于两个维度查询时,我们便需要做冗余存储,即同一份数据存到两个表中,
问题1: 这两个表可能位于不同的存储实例,我们如何保证两张表的数据一致性?
电商业务支付中心通知支付场景,用户在购买商品时使用了优惠券(优惠券为合作伙伴提供,核销优惠券需要调用远程服务),在我们收到支付中心通知用户付款的回调逻辑里,我们需要处理:
a、修改订单状态为支付成功,给用户发货 (本地事务)
b、对优惠券进行核销(调用远程服务,校验将有效优惠券置为已使用)
问题2:我们如何保证 a 和 b 操作同时成功或失败,保证数据一致性?
- CAP 原则
对于CAP的理解,可以联系到相关的原则,CAP 原则又称 CAP 定理,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。
一致性(C):
在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
可用性(A):
在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
分区容错性(P):
以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在 C 和 A 之间做出选择。
- CAP 不可兼得
这里,可以定义N1和N2的数据库V之间的数据是否一样为一致性;外部对N1和N2的请求响应为可用行;N1和N2之间的网络环境为分区容错性。
假设N1和N2网络断开,N1更新为V1,N2依旧是V0;这时候,N2收到读取请求,没
办法立即给用户返回最新的数据V1,怎么办呢?有二种选择,
第一,牺牲数据一致性,保证可用性。响应旧的数据V0给用户;
第二,牺牲可用性,保证数据一致性。阻塞等待,直到网络连接恢复,再响应最新的数
据V1。
这个过程,证明了要满足分区容错性的分布式系统,且在发生了网络分区的情况下,只
能在一致性和可用性两者中,选择其中一个
- BASE理论
对于BASE 理论的理解,可以先看下如下图:
a、基本可用
指分布式系统在出现不可预知故障的时候,允许损失部分可用性(服务降级)
b、软状态
指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时
c、最终一致性
最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能够达到一个一致的状态
- 幂等操作
先了解下什么是幂等操作:
在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。
例如,支付流程中第三方支付系统告知系统中某个订单支付成功,接收该支付回调接口在网络正常的情况下无论操作多少次都应该返回成功,且如果涉及发货等操作仅仅发生一次,不会导致重复发货。
- 分布式事务解决方案
2.1、两阶段提交
a、正常流程
b、异常流程
c、两阶段提交优缺点
优点:
原理简单,易于实现
缺点:
协调者单点问题;同步阻塞,效率较低;数据不一致,仍然无法完全避免数据不一致
针对阻塞和数据不一致,可以引入超时机制和互询机制一定程度降低影响。
和两阶段提交类似的还有三阶段提交,主要是增加了预询阶段和超时机制来减少整个集群的阻塞时间,提升系统性能。
2.2、事务消息
事务消息的核心主要是结合队列消息来与事务关联,核心流程如下:
再来看一个具体的例子,以用户商品购买举例,如图
使用事务消息来保证订单和库存数据的一致性,当然这种方式不是严格一致性,而是最终一致性,而且这种模式将确认订单的流程异步化,需要前端用户体验的一些改变,配合产品设计流程。
2.3、TCC
TCC 是服务化的二阶段变异模型,每个相关的业务服务都必须实现 Try,Confirm,Calcel 三个方法。这三个方法可以对应到 SQL 事务中 Lock,Commit,Rollback:
Try 阶段:
Try 只是一个初步的操作,进行初步的确认,它的主要职责是完成所有业务的检查,预留业务资源。
Confirm 阶段:
Confirm 是在 Try 阶段检查执行完毕后,继续执行的确认操作,必须满足幂等性操作,如果 Confirm 中执行失败,会有事务协调器触发不断的执行,直到满足为止。
Cancel:
取消执行,在 Try 没通过并释放掉 Try 阶段预留的资源,也必须满足幂等性,跟 Confirm 一样有可能被重复执行。
仍然是以下单来举例,下单扣减库存的流程怎么加入 TCC?
在 Try 阶段,会让库存服务预留 n 个库存给这个订单使用,让订单服务产生一个“未确认”订单,同时产生这两个预留的资源。
在 Confirm 阶段,会使用在 Try 预留的资源
在 Try 的时候,有任务一方为执行失败,则会执行 Cancel 的接口操作,将在 Try 阶段预留的资源进行释放。
但这里有个问题,如果在 Confirm 或 Cancel 中,有一方的操作失败了,可能出现异常等情况该怎么解决。这个就涉及 TCC 的事务协调器了,事务协调器就 Confirm 或 Cancel 没有得到返回的时候,会启用定时器不断的进行 Confirm 或 Cancel 的重试。 这个也就是我们强调,Confirm,Cancel 接口必须是幂等性的一个原因了。
2.4、最大努力通知
最大努力通知最常见的场景就是支付回调,支付服务收到第三方服务支付成功通知后,先更新自己库中订单支付状态,然后同步通知订单服务支付成功。如果此次同步通知失败,会通过异步脚步不断重试地调用订单服务的接口
- 分布式事务应用场景
3.1、转账
转账是最经典的分布式事务场景,假设用户 A 使用银行 app 发起一笔跨行转账给用户 B,银行系统首先扣掉用户 A 的钱,然后增加用户 B 账户中的余额。此时就会出现 2 种异常情况:1. 用户 A 的账户扣款成功,用户 B 账户余额增加失败 2. 用户 A 账户扣款失败,用户 B 账户余额增加成功。对于银行系统来说,以上 2 种情况都是不允许发生,此时就需要分布式事务来保证转账操作的成功。
3.2、下单扣库存
在电商系统中,下单是用户最常见操作。在下单接口中必定会涉及生成订单 id, 扣减库存等操作,对于微服务架构系统,订单 id 与库存服务一般都是独立的服务,此时就需要分布式事务来保证整个下单接口的成功。
3.3、同步超时
在微服务体系架构下,我们的支付与订单经常是作为单独的系统存在的。订单的支付状态依赖支付系统的通知,假设一个场景:我们的支付系统收到来自第三方支付的通知,告知某个订单支付成功,接收通知接口需要同步调用订单服务变更订单状态接口,更新订单状态为成功。有两次调用,第三方支付调用支付服务,以及支付服务调用订单服务,这两步调用都可能出现调用超时的情况,此处如果没有分布式事务的保证,就会出现用户订单实际支付情况与最终用户看到的订单支付情况不一致的情况。