架构设计事务篇之分布式事务

概述

事务是一组读写操作,这些操作被当作一个独立的工作单元被执行,操作的执行结果要么全部成功,要么全部失败,不允许部分成功、部分失败的情况出现。
事务可以分为本地事物和分布式事物,本地事务的数据操作是在单个数据库上执行的,而分布式事务是在多个数据库上执行的,分布式事物由两个或以上的本地事物构成,它也被称之为全局事物。
无论是本地事物,还是分布式事物它们的作用都是为了保障数据的一致性。

实列

avatar

在采用微服务架构的应用程序中,需要使用分布式事务技术的典型实列是:当订单支付成功之后,需要同时跟新订单状态以及扣减库存,而订单数据和库存数据位于不同的数据库中。
此时,更新订单状态、扣减库存这两个操作同属一个事务,两个操作作为一个执行单元不能被分割,要么都执行成功,要么都执行事物,不允许出现一个成功另一个失败的情况。
但是,在分布式的架构中,网络抖动、超时、服务故障等异常都是不可避免的。
比如,当上面的事物执行时,可能订单状态的更新在订单数据库上执行成功,但库存的扣减因网络原因在库存数据库中执行失败,这样就造成了数据库中的数据和我们期望的业务数据状态不一致。

avatar

问题

分布式事务是一组操作序列,序列中的任意操作都有可能因故障导致该操作执行失败,从而导致数据不一致。
又因为,故障会发生在任意时刻而且是100%会发生,所以分布式的事务一定会出现事务不一致的情况,无论采用哪一种方案。
因此,我们的技术实现方案便面临两个核心问题:第一、如何减少事务执行时发生故障的概率,第二、故障发生后出现数据不一致的情况如何解决。

方案

针对上面的第一个问题,可行的策略是在执行事务之前,先确保网络和数据库都不存在故障,然后再开始执行事务。
2PC(两阶段)、3PC(三阶段)、TCC采用的都是这种策略,除了TCC在执行失败时会通过撤销操作恢复数据的一致性外,其它的方式在事务执行失败时都没有任何的恢复策略,但TCC也不是绝对能保障数据会被恢复。
针对上面的第二个问题,可行的策略便是在故障发生后,不断地重试直至成功为主,或最大限度的保障其操作执行成功;
基于消息的最终一致性方案便是采用这种策略,相较于上面"快速失败"式的方案这种方案最大的问题是:数据从不一致恢复到一致的状态时,中间间隔的时间可能会过长。
下面我们分别介绍一下上面的几种方案。

2PC(两阶段)
avatar

在两阶段事务中,有两个角色:事务协调者、资源管理者。
事务协调者负责控制全局事务中多个本地事务的执行流程,它会将本地事务分发给对应的资源管理器并触发资源管理器执行本地事务。

avatar

两阶段事务的执行流程分成准备和提交两个阶段:

第一阶段,主要是确保各个资源管理器(数据库)处于正常状态,所以,事务管理器会先"试探"资源管理器:"我开始事务了,你准备一下?",
如果资源管理器准备就绪,那么它会告知事务管理器:"我准备好了。",否则资源管理器便处于异常状态;等所有的资源管理器都有回应无论是准备就绪还是异常,事务处理流程便会进入第二个阶段。

第二阶段,主要是根据第一阶段事务的准备结果来控制事务的流程,如果第一阶段中所有的事务都准备就绪,那么事务管理器便会通知资源管理器提交事务,否则取消事务。

两阶段的实现方式存在以下主要缺点:

同步阻塞:各个本地事务的执行会占用数据库的连接资源,连接的占用时长相当于整个事务的执行时长而不是单个本地事务的时长。

协调者故障:协调者如果不是分布式的那么存在单点故障的问题,如果协调者在第二阶段故障,那么有可能导致数据不一致。

数据不一致:当各个事务管理器收到提交指令后,便会将本地事务提交,但可能出现有些资源管理器在第二阶段没有收到提交指令,那么此时就会造成数据不一致情况。

3PC(三阶段)
avatar

三阶段将事务的执行流程分成询问、准备、提交两个阶段,比两阶段多出来询问这个阶段,其它两个阶段都是相同的。
询问阶段主要作用在于检测网络,数据库是否正常,避免直接进入准备阶段因网络故障导致的后续的回滚操作。

TCC(柔性事务)
avatar

TCC也是一种两阶段事务,它由第一阶段的Try,以及第二阶段的Cancel、Commit操作构成。
TCC相较于2PC、3PC以及最终一致性来说,它的特点是:面向具体业务操作的事务,而其它的分布式事务方案是面向数据操作的。

假如,你有一笔订单支付成功了,现在需要同时更新订单库中订单的状态,以及扣减库存库中商品库存;
那么,你的订单服务、库存服务需要向事务协调者提供类似于下面的这些操作:

订单服务,Try:插入订单状态更新记录;Confirm:跟新订单状态;Cancel:取消订单跟新;

库存服务,Try:锁定库存;Confirm:扣减库存;Cancel:撤销库存更新;

TCC这种处理方式因为依赖业务操作来保障一致性,所以需要针对特定的事务编写业务接口。

最终一致性

最终一致性方案常见的方案有两种,分别是基于本地消息表的事务、基于消息中间件的事务,虽然它们在实现上略微有所差异但基本原理还是一样的,都采用异步非阻塞和重试技术来实现的,而且业务接口都需要支持幂等。

基于本地消息表
avatar

基于本地消息表的最终一致性方案,其实现原理是将分布式转化成本地事务——将外部的本地事务转化成一条消息,然后通过定时任务或消息中间件将消息发送出去。

基于消息中间件
avatar

基于消息中间件的最终一致性方案相较于基于本地消息表的事务,省去了本地消息表、定时任务、重试,这些操作都由消息中间实现了。

总结

无论那种分布式事务实现方案都无法绝对保障数据的一致性,实现上的差别主要在数据不一致的时间长短、强弱上。

扩展阅读

架构设计思维篇之结构

架构设计思维篇之概念

架构设计容错篇之重试

架构设计容错篇之熔断

架构设计容错篇之限流

架构设计事务篇之Mysql事务原理

架构设计事务篇之CAP定理

架构设计事务篇之分布式事务

架构设计消息篇之消息丢失

架构设计消息篇之保证消息顺序性

你可能感兴趣的:(架构设计事务篇之分布式事务)