1
场景
分布式事务这个话题,开发者们一定都不陌生。电商系统最容易出现分布式事务的处理,比如用户在电商平台购买一个商品,用户首先下单,然后平台要扣减库存。创建订单和库存的扣减一般都在不同的服务器上(微服务架构)。而用户购买到商品的行为,必须要下单和扣减库存都成功,才算这次的交易成功,反之则失败。
微服务倡导将复杂的单体应用拆分为若干个功能简单、松耦合的服务,这样可以降低开发难度、增强扩展性、便于敏捷开发。当前被越来越多的开发者推崇,很多互联网行业巨头、开源社区等都开始了微服务的讨论和实践。
微服务落地存在的问题?
1)单体应用拆分为分布式系统后,进程间的通讯机制和故障处理措施变的更加复杂。
2)系统微服务化后,一个看似简单的功能,内部可能需要调用多个服务并操作多个数据库实现,服务调用的分布式事务问题变的非常突出。
3)微服务数量众多,其测试、部署、监控等都变的更加困难。
随着RPC框架的成熟,第一个问题已经逐渐得到解决。例如dubbo可以支持多种通讯协议,springcloud可以非常好的支持restful调用。对于第三个问题,随着docker、devops技术的发展以及各公有云paas平台自动化运维工具的推出,微服务的测试、部署与运维会变得越来越容易。
而对于第二个问题,现在还没有通用方案很好的解决微服务产生的事务问题。分布式事务已经成为微服务落地最大的阻碍,也是最具挑战性的一个技术难题。 为此,本文将深入和大家探讨微服务架构下,分布式事务的各种解决方案。
1
什么是分布式事务
事务(Transaction),一般是指要做的或所做的事情,由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
简单的讲就是,要么全部被执行,要么就全部失败。
那分布式事务,自然就是运行在分布式系统中的事务,是由多个不同的机器上的事务组合而成的。同上,只有分布式系统中所有事务执行了才能是成功,否则失败。
我们理解了分布式事务的基本含义,那想要深入理解分布式事务,就得先要搞清楚事务的基本特征ACID:
- 原子性(Atomicity)
- 一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
- 一致性
- 指事务执行前和执行后,数据是完整的。
- 隔离性
- 一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性
- 也称为永久性,一个事务一旦提交,它对数据库中数据的改变就应该是永久性的保存下来了。
1
如何实现分布式事务
首先,我们想一下分布式事务是为了解决分布式系统的什么问题?
- 它就是为了解决分布式环境下,多个独立事务一致性问题
现在业界内常用的有3种方案实现分布式事务:
- 二阶段提交协议形式
- 基于XA协议的,采取强一致性,遵从ACID
- 三阶段提交协议形式
- 采取强一致性,遵从ACID
- 基于消息的最终一致性形式
- 采取最终一致性,遵从BASE理论
1
二阶段提交协议形式
- 二阶段提交协议是协调所有分布式原子事务参与者,并决定提交或取消(回滚)的分布式算法。
- 二阶段提交中,系统内有两个角色:
- 协调者(老大),只有一个,由它来执行提交和回滚操作
- 参与者(小弟们),一般有多个,通常是由数据库来实现
- 为了保证数据的一致性,所以我们需要有一个协调者来管理所有的节点,来保证各个事务的正确提交,若是有提交失败的则放弃。
- 二阶段提交过程:(两个步骤)
- 投票(vote)
- 提交(commit)
- 投票阶段:
- 协调者会像所有参与者发送能提交(canCommit)的请求,然后,参与者收到请求后,会各自在本地执行自己的事务操作并记录行为,但自己并不真正提交,成功就会发送同意Yes指令给协调者,失败则发送终止No指令。
- 当协调者收到了所有参与者返回的指令yes或者no,则会进入真正提交阶段,这时候就会给协调者发送doCommit或者doAbort消息。
- 协调者收到参与者的全是yes消息,则会像参与者发送doCommit消息指令,此时参与者就会完成后续的操作真正的提交事务以及释放连接资源等,接着就会向协调者发送haveCommittd已经提交了的消息。
- 协调者收到的所有参与者中有no指令,那么之前yes的参与者会根据自己的日志进行回滚操作,然后就向协调者发送haveCommitted消息。
- 协调者收到了所有参与者的已经提交haveCommitted消息,则表明了整个分布式事务的结束。
- 案例分析:
- 我在我们平台商城里面购买一台Android手机,会涉及到我们的订单系统、库存系统两个主系统的协作。那么,我这样一个购买Android手机的行为怎么体现二阶段提交实现的分布式事务呢?
- 第一阶段,协调者会像参与者订单系统以及库存系统发送canCommit消息,然后订单系统就会进行订单的相关操作,如锁住订单库进行新增订单的操作,完成后就发送一个Yes消息给协调者;库存系统发现现在我的这款手机库存已经为0了,就会终止扣减库存方面的操作,发送No消息给协调者。
二阶段提交基本上满足了ACID特征,且实现的是数据的强一致性。但是它也是有缺点的,那就是单点故障,因为协调者只有一个。那还有有没有其他的缺点呢?肯定是有的。
- 单点故障:事务的发起、提交还是取消,均是由老大协调者管理的,只要老大宕机,那就凉凉了
- 同步阻塞缺点:从上面介绍以及例子可看出,我们的参与系统中在没收到老大的真正提交还是取消事务指令的时候,就是锁定当前的资源,并不真正的做些事务相关操作,所以,整个分布式系统环境就是阻塞的
- 数据不一致缺点:就是说在老大协调者像小弟们发送真正提交事务的时候,部分网路故障,造成部分系统没收到真正的指令,那么就会出现部分提交部分没提交,因此,这就会导致数据的不一致
1
三阶段提交形式
上面我们清楚了二阶段提交,也明白了它有哪些缺点。正是因为有些缺点,所以,就有了优化方案,那就是三阶段提交。那么,三阶段提交做出了哪些优化呢?
- 加入超时机制
- 加入预提交阶段
所以,现在三阶段提交就是有这三个主阶段,canCommit、preCommit、doCommit这三个阶段,下面我们来分析下:
第一阶段,canCommit
这第一阶段和上面二阶段提交中的第一阶段是类似的,那这个阶段中在分布式系统中事务的流程是怎样的?看下图:
第二阶段,preCommit
当所有参与者向老大协调者发送Yes消息的时候,协调者就会发送preCommit指令给他们,告诉他们可以准备提交了,参与者就会做一些本地事务的真正业务操作,但是不真的提交,自己做完了就返回ACK确认消息回去。
当参与者有返回No消息的时候,老大协调者就会告诉小弟们Abort消息,小弟们收到了老大的中断事务提交的指令就会停止真正的事务提交动作。
第三阶段,doCommit
到了这个阶段就是真正的事务提交部分了,然后宣告真个分布式事务结束。
1
基于分布式消息的最终一致性方案
不管是二阶段还是三阶段提交方式,都是满足了事务的ACID特性。但是,他们都有共同的缺点:同步阻塞,系统性能低以及没有解决数据不一致的问题,那么基于消息的形式是如何实现的?
在eBay分布式系统中,架构师们解决一致性问题的核心方法论是:将需要分布式处理的事务通过消息的方式进行异步处理,然后根据业务规则自行进行失败重试。现在,我们知道了需要有个核心组件,那就消息中间件,通过消息中间件进行分布式系统中各个消息的传递动作如下图:
现在我将我们用户电商购物下单怎么实现分布式最终一致性整个开发流程做个讲解,期间涉及到订单系统、支付系统、仓库系统,他们彼此的协作时序图如下:
然后,我们看看它基于消息的最终一致性方案,整个流程是这样的:
- 订单系统将要处理订单的消息发送到消息中间件MQ中,消息状态为“待确认”。
- 消息中间件MQ将订单系统发来的消息进行持久化存储操作,即在MQ中增加一个“待发送”的消息。
- 消息中间件MQ将持久化结果返回给订单系统,如果成功,订单系统则进行创建订单操作,失败则放弃本次创单操作。
- 订单系统完成订单的相关操作后,将结果(成功或者失败)再发给消息中间件MQ。
- 消息中间件收到上面消息后,则进行相应的处理,如果失败消息,则终止本次交易,删除MQ消息;如果是成功则更新MQ中消息状态为可发送,就会将消息发送到支付系统。
- 支付系统收到消息还是按照上面传递步骤进行同样的操作进行支付。
- 支付系统将支付消息发给MQ,然后MQ将消息发给订单系统,订单系统进行调用库存系统业务。
所以,基于分布式消息最终一致性方案,一样是依据分布式系统中所有事务均成功则整个交易流程才成功的原则。其实,在我们大部分互联网项目中在应对分布式事务的时候,都会先牺牲下少许的数据不一致,来做成最终数据一致性的方案,遵从的是BASE理论。
独立消息服务方案?
- 实现思路:
- 预发送消息:主动方应用系统预发送消息,由消息服务子系统存储消息,如果存储失败,那么也就无法进行业务操作。如果返回存储成功,然后执行业务操作。
- 执行业务操作:执行业务操作如果成功的时候,将业务操作执行成功的状态发送到消息服务子系统。消息服务子系统修改消息的标识为“可发送”状态。
- 发送消息到实时消息服务:当消息的状态发生改变的时候,立刻将消息发送到实时消息服务中。接下来,消息将会被消息业务的消费端监听到,然后被消费。
- 消息状态子系统:相当于定时任务系统,在消息服务子系统中定时查找确认超时的消息,在主动方应用系统中也去定时查找没有处理成功的任务,进行相应的处理。
- 消息消费:当消息被消费的时候,向实时消息服务发送ACK,然后实时消息服务删除消息。同时调用消息服务子系统修改消息为“被消费”状态。
- 消息恢复子系统:当消费方返回消息的时候,由于网络中断等其他原因导致消息没有及时确认,那么需要消息恢复子系统定时查找出在消息服务子系统中没有确认的消息。将没有被确认的消息放到实时消息服务中,进行重做,因为被动方应用系统的接口是幂等的。
- 优点:
- 消息服务独立部署,独立维护,独立伸缩。
- 消息存储可以按需选择不同的数据库来集成实现。
- 消息服务可以被相同的的使用场景使用,降低重复建设服务的成本。
- 从分布式服务应用设计开发角度实现了消息数据的可靠性,消息数据的可靠性不依赖于MQ中间件,弱化了对MQ中间件特性的依赖。
- 降低了业务系统与消息系统之间的耦合,有利于系统的扩展维护。
- 缺点:
- 一次消息发送需要两次请求。
- 主动方应用系统需要实现业务操作状态的校验与查询接口。
1
什么是BASE原理
既然说到了消息最终一致性遵从BASE理论,我觉得有必要将BASE理论科普下。
BASE:全称是,Basically Avaliable(基本可用),Soft state(软状态),Eventually consistent(最终一致性)三个短语的缩写,来自eBay的架构师提出。
- Basically Avaliable:就是在分布式系统环境中,允许牺牲掉部分不影响主流程的功能的不可用,将其降级以确保核心服务的正常可用。
- Soft state:就是指在事务中,我们允许系统存在中间状态,且并不影响我们这个系统。就拿数据库的主从复制来说,是完全允许复制的时候有延时的发生的。
- Eventually consistent:还是以数据库主从复制为例说,虽然主从复制有小延迟,但是很快最终就数据保持一致了。
1
总结
最后,我们再整体回顾下分布式事务的三种实现方式,整理个思维导图,帮助大家根据自己的业务进行合理的选择哪一种方案来实现自己公司的分布式事务: