001 分布式事务实现 | 基本概念理论和协议

2019-03-04  本文已影响0人  __destory__

数据库事务的基本特性:

  1. 原子性(Atomicity )、
  2. 一致性( Consistency )、
  3. 隔离性或独立性( Isolation)、
  4. 持久性(Durabilily),

简称就是ACID

在一个分布式微服务的系统之上,如果一个业务操作,涉及到了多个服务一起完成,有可能涉及到多个数据库的数据变更,这种情况下,需要分布式事务的解决方案。

分布式理论

集群环境下,再想保证集群的ACID几乎是很难达到,这时我们就需要引入一个新的理论原则来适应这种集群的情况,就是 CAP 原则或者叫CAP定理。

CAP理论

BASE理论

BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)

分布式事务解决方案

列出以下几个常用的解决方案,每个方案适用的场景不同。

两阶段提交2PC[分布式一致性理论]

系统一般包含两类机器(或节点):一类为协调者(coordinator),通常一个系统中只有一个;另一类为事务参与者(participants,cohorts或workers),一般包含多个。

两阶段:

2PC执行流程.png

XA协议

XA协议是 2PC在数据库分布式事务方面( 理解成分布式一致性问题2PC方案的一种实际应用,分布式事务其实也是一致性问题 ) 的定义,又叫做XA Transactions,XA规范主要定义了(全局)事务管理器(Transaction Manager,TM)和(局部)资源管理器(Resource Manager,RM)之间的接口,接口是双向互通的,可以彼此完成数据交换。

JTA

Java Transaction API,定义了Java平台的XA协议的规范和接口,JTA 中,事务管理器抽象为javax.transaction.TransactionManager接口,并通过底层事务服务(即Java Transaction Service)实现。像很多其他的Java规范一样,JTA仅仅定义了接口,具体的实现则是由供应商(如J2EE厂商)负责提供。

JTA定义了接口,需要不同的J2EE厂商和数据库厂商共同完成,即,需要支持事务管理器以及事务的提交和撤回。

目前,框架Atomikos可以实现JTA,通过和Spring等配合,实现JTA功能。

2PC的优缺点

三阶段提交3PC[分布式一致性理论]

在协调者和参与者引入超时机制,将2PC的第一个阶段拆分,询问和锁资源,最后是真正提交。如下图,


3PC流程图

执行流程解释,

  1. can-commit阶段:协调者向参与者发送commit请求,参与者如果可以提交,就返回yes,反之,no,之后,协调者计时等待(等待协调者返回yes还是no,没有及时返回,就是no)
  2. pre-commit阶段:协调者根据参与者返回来判断是否执行pre-commit阶段

三阶段3PC协议缺点

相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit,因此pre-Commit之后,如果协调者发送abort请求,但是有部分协调者未收到abort请求,那么在他们等待超时后,会继续执行commit操作,导致数据不一致。

补偿事务TCC

全称 try-confirm-cannel,则是将业务逻辑分成try、confirm/cancel两个阶段执行。原理性质的,可以参考中华石杉的笔记,https://mp.weixin.qq.com/s?__biz=MzU0OTk3ODQ3Ng==&mid=2247483862&idx=1&sn=f94857a050ae0e98521a70f331fe5420&chksm=fba6e9d5ccd160c3c39b2a474f2e0a636465a79446d71822148e0129164cb91dfb11e61e7555&mpshare=1&scene=1&srcid=03044xUUHt8UlgTcU1ZscXeH#rd

这里说下和2PC的区别和关系,

2PC是begin -> 业务逻辑 -> prepare -> commit, 而TCC是begin -> 业务逻辑(try业务) -> commit(comfirm业务)。这里TCC的try业务在中华石杉笔记中得到体现。

本地消息列表

该方法是业界使用最多的,其实在我们日常的开发中,使用过类似的方式,只是我们不知道这种方式叫做本地消息列表而已,核心思想,是将分布式的业务,拆分成本地的事务处理,看如下图,


本地消息列表处理机制

含义是,将后续跨库的操作,以消息的形式,落在本地统一库中,这样就可以控制本地事务。而落地的消息会通过kafka等队列,在其他库中消费者会获取到,进行更改相应的数据。

思路如下,

  1. 消息生产方,建立额外消息表,记录消息的发送状态(消息是对后续业务数据的更改),后续消息操作表和当前消息发送方在一个数据库,如果消息生成成功,表明消息已经记录,且数据已经发送到mq中。
  2. 消费方,监听队列,提取消息,根据消息指定的操作完成业务,后,更改消息生产方中的消息表中对应的记录,如果最终事务执行成功,则完成。如果事务出现异常,则捕获异常,通过mq发送业务补偿的消息给生产方。
  3. 生产方和消费方,都需要定期扫描本地的消息表,将未成功的记录,根据异常类型,判断是否再次发送。

优缺点

事务消息队列

本地消息队列的严重限制是消息表和业务表耦合度过大,而且当业务库出现问题,导致消息表也失去功能,因此,将消息表独立出来做好,这就诞生了MQ事务消息队列的应用。

这里我们使用RocketMQ为例子,

RocketMQ使用过程

业务需要向消息队列中发送两次请求,一次消息发送和一次确认消息,过程如下,

  1. 发送Prepared消息
  2. update DB
  3. 根据update DB结果成功或失败,Confirm或者取消Prepared消息。

最后1步失败了怎么办?这里就涉及到了RocketMQ的关键点:RocketMQ会定期(默认是1分钟)扫描所有的Prepared消息,询问发送方,到底是要确认这条消息发出去?还是取消此条消息?

通过RocketMQ的checkListener来实现,RocketMQ会回调此Listener,从而实现上面所说的方案。

RocketMQ最大的改变,其实就是把“扫描消息表”这个事情,不让业务方做,而是消息中间件帮着做了。

至于消息表,其实还是没有省掉。因为消息中间件要询问发送方,事物是否执行成功,还是需要一个“变相的本地消息表”,记录事物执行状态。

如果消费者失败了如何?虽然如图上所示,会一直循环,但是终有到头的时候,此时,需要人工介入。从工程实践角度讲,这种整个流程自动回滚的代价是非常巨大的,不但实现复杂,还会引入新的问题。比如自动回滚失败,又怎么处理?对应这种极低概率的case,采取人工处理,会比实现一个高复杂的自动化回滚系统,更加可靠,也更加简单

上一篇 下一篇

猜你喜欢

热点阅读