ᕕ( ᐛ )ᕗ xiaoli's blog

浅浅了解分布式事务

; 3355 words  

目录

关系型数据库中存在事务机制,可以保证每个独立节点的操作满足ACID。
分布式事务产生的原因主要是存储和服务的拆分。存储层拆分如数据库分库分表,服务层拆分即业务的服务化,因为解耦合,许多核心业务如服务、商品、订单、库存等变成了独立的服务,业务逻辑的执行散落在不同服务器上。
因此问题也会出现,下单操作同时依赖订单、库存、支付服务,这三个服务如果有一个失败,下单操作则失败,而这种事务性,就需要分布式事务,保证所有节点的数据写操作要么全都执行,要么全都不执行。

解决方案

分布式事务的解决方案有两阶段和三阶段提交协议、TCC分段提交、基于消息队列的最终一致性设计。
对于分布式一致性算法,都是通过leader进程进行协调,2PC和3PC也是同样的思想。

two-phase commit protocol

two-phase即Commit-request和Commit。
对于2PC算法,基于以下假设:

提交请求阶段

Coordinator通知Participants准备提交事务,进入表决过程。在表决过程中,Participants告诉Coordinator自己的决策:同意(Participants本地事务执行成功)或取消(Participants本地事务故障)

提交阶段

Coordinator基于第一阶段的投票结果进行决策:提交或取消事务,必需当且仅当所有Participants同意提交,Coordinator才会通知Participants提交事务,否则会取消事务。
Participants收到通知后,在本地进行Commit或Rollback。

问题

执行过程,所有参与节点都是事务独占状态,当参与者占用公共资源,第三房节点访问公共资源会被阻塞

一旦Coordinator出现故障,Participants会一直阻塞

提交阶段,Coordinator发送的事务通知如果由于网络问题仅被一部分Participants收到,没有收到的Participants会一直阻塞,数据出现不一致。

three-phase commit protocol

为了解决2PC的同步阻塞问题,3PC引入了超时机制,把2PC的提交请求阶段拆分为两步:询问、锁资源,然后进入提交阶段。
3PC的三个阶段分别是 CanCommit、PreCommit、DoCommit。

CanCommit

类似于2PC的请求提交阶段,Coordinator向Participants发送Can-Commit请求,Participants返回yes 或 no。

PreCommit

  1. 所有Participants的反馈都是yes,就会进行事务的预执行。
  1. 如果存在Participants向Coordinator发送no,或等待超时都没有接收到Participants的响应,中断事务。

DoCommit

  1. 执行提交
  1. 中断事务 Coordinator没有接收到Participants发送的 ACK 响应,可能是因为接受者发送的不是 ACK 响应,也有可能响应超时了,那么就会执行中断abort事务。

3.超时提交 Participants如果没有收到Coordinator的通知,超时之后会执行 Commit 操作。

改进

问题

在do-commit阶段,如果participants接收到pre-commit消息仍会进行事务的提交,导致数据不一致。

Try-Confirm-Cancel

TCC 的核心思想是针对每个业务操作,添加一个与其对应的确认和补偿操作,同时把相关的处理,从数据库转移到业务中,以此实现跨数据库的事务。

Try

Try操作一般都是锁定某个资源,设置预备状态,尝试执行业务,完成所有业务检查,预留业务资源。

Confirm/Cancel

两阶段是互斥的,只能进入其中一个阶段,并且阶段都满足幂等性,允许失败重试。

应用

TCC的本质是把数据库的二阶段提交上升到微服务,TCC对业务进行拆解。依然拿支付过程演示

订单服务添加预备状态,修改为 UPDATING,冻结当前订单的操作,而不是直接修改为支付成功。

库存服务设置冻结库存,可以扩展字段,也可以额外添加新的库存冻结表。积分服务和库存一样,添加一个预增加积分,比如本次订单积分是 100,添加一个额外的存储表示等待增加的积分,账户余额服务等也是一样的操作。

Confirm 操作就是把前边的 Try 操作锁定的资源提交,类比数据库事务中的 Commit 操作。在支付的场景中,包括订单状态从准备中更新为支付成功;库存数据扣减冻结库存,积分数据增加预增加积分。

Cancel 操作执行的是业务上的回滚处理,类比数据库事务中的 Rollback 操作。首先订单服务,撤销预备状态,还原为待支付状态或者已取消状态,库存服务删除冻结库存,添加到可销售库存中,积分服务也是一样,将预增加积分扣减掉。

业务请求过来,执行 Try 操作,如果 TCC 分布式事务框架感知到各个服务的 Try 阶段都成功了以后,就会执行各个服务的 Confirm 逻辑。如果 Try 阶段有操作不能正确执行,比如订单失效、库存不足等,就会执行 Cancel 的逻辑,取消事务提交。

对比XA

在 XA(分布式事务处理规范) 事务中,各个 RM (资源管理器)准备提交各自的事务分支,即准备提交资源的更新操作(insert、delete、update 等);而在 TCC 中,是主业务操作请求各个子业务服务预留资源。

XA 事务根据第一阶段每个 RM 是否都 prepare 成功,判断是要提交还是回滚。如果都 prepare 成功,那么就 commit 每个事务分支,反之则 rollback 每个事务分支。

在 TCC 中,如果在第一阶段所有业务资源都预留成功,那么进入 Confirm 步骤,提交各个子业务服务,完成实际的业务处理,否则进入 Cancel 步骤,取消资源预留请求。

基于消息补偿的最终一致性

由本地消息表和第三方可靠信息队列组成。本地消息表核心思想是将分布式事务拆分成本地事务处理,通过消息日志的方式异步执行。消息生产方即rpc上游,额外建立一个事务消息表,记录消息发送状态,消息消费方需要处理这个消息,并完成自己的业务逻辑,异步机制定期扫描未完成的消息,确保最终一致性。

如最初提出的下单减库存业务,简单模拟过程:

(1)系统收到下单请求,将订单业务数据存入到订单库中,同时存储该订单对应的消息数据,如商品ID 和数量等,消息数据与订单库为同一库,更新订单和存储消息为一个本地事务,要么都成功,要么都失败。

(2)库存服务通过消息中间件收到库存更新消息,调用库存服务进行业务操作,同时返回业务处理结果。

(3)消息生产方,即订单服务,收到处理结果后,将本地消息表的数据删除或者设置为已完成。

(4)设置异步任务,定时去扫描本地消息表,发现有未完成的任务则重试,保证最终一致性。

reference

2pc/3pc

tcc

#分布式