基于可靠消息方案的分布式事务:Lottor介绍

前言:笔者最近实现了基于可靠消息方案的分布式事务:Lottor。本文将会介绍Lottor的概况,在后续系列文章介绍具体的实现,欢迎关注。html

分布式事务

分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不一样的分布式系统的不一样节点之上。算法

首先,解释下事务的概念:一组操做要么都完成以后提交,要么所有回滚。分布式事务特指在分布式环境下,一次事务设计多个服务进程,说白了就是跨进程的事务,这样就不能控制事务组的一致性。数据库

分布式系统区别于传统的单体应用,单体应用的服务模块和数据都在一个服务中,使用Spring框架的事务管理器便可知足事务的属性。而分布式系统中,来自客户端的一次请求每每涉及多个服务,事务的一致性问题由此产生。服务器

CAP理论

CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务没法同时知足一下3个属性:微信

  • 一致性(Consistency) : 客户端知道一系列的操做都会同时发生(生效)
  • 可用性(Availability) : 每一个操做都必须以可预期的响应结束
  • 分区容错性(Partition tolerance) : 即便出现单个组件没法可用,操做依然能够完成

具体地讲在分布式系统中,在任何数据库设计中,一个Web应用至多只能同时支持上面的两个属性。网络

上面这句话的表述,不少人都用过,是的,这是一种误解。注意CAP定律的完整表述:Any networked shared-data system can have at most two of the three desired properties.架构

CAP 定律的前提是 P,当 P 决定后才有 CA 的抉择。所以,简单粗暴地说「三选二」是有必定误导性的。并发

BASE理论

在分布式系统中,咱们每每追求的是可用性,它的重要程序比一致性要高,那么如何实现高可用性呢? 前人已经给咱们提出来了另一个理论,就是BASE理论,它是用来对CAP定理进行进一步扩充的。BASE理论指的是:框架

  • Basically Available(基本可用)
  • Soft state(软状态)
  • Eventually consistent(最终一致性)

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

需求分析

功能需求

功能需求最主要的是知足分布式事务的一致性,涉及的事务组中的操做为多个写操做,当产生一个或多个写操做失败时,回滚整个事务组中的操做。

非功能需求

  • 性能:分布式事务对系统的性能必然是有影响的,须要寻找平衡的点。
  • 高可用:引入中间件或者协调者时,避免单点故障。分布式系统的高可用必然会牺牲部分一致性。
  • 可扩展:下降引入的业务耦合。
  • 伸缩性:系统可以弹性伸缩。

解决方案

强一致方案

X/Open 组织(即如今的 Open Group )定义了分布式事务处理模型。 X/Open DTP 模型( 1994 )包括应用程序( AP )、事务管理器( TM )、资源管理器( RM )、通讯资源管理器( CRM )四部分。

XA 就是 X/Open DTP 定义的交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。 XA 接口函数由数据库厂商提供。

2PC

二阶段提交的算法思路能够归纳为:参与者将操做成败通知协调者,再由协调者根据全部参与者的反馈情报决定各参与者是否要提交操做仍是停止操做。第一阶段:准备阶段(投票阶段)和第二阶段:提交阶段(执行阶段)。

2pc
2pc

  • 同步阻塞问题。执行过程当中,全部参与节点都是事务阻塞型的。当参与者占有公共资源时,其余第三方节点访问公共资源不得不处于阻塞状态。
  • 数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求以后,发生了局部网络异常或者在发送commit请求过程当中协调者发生了故障,这回致使只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求以后就会执行commit操做。可是其余部分未接到commit请求的机器则没法执行事务提交。因而整个分布式系统便出现了数据部一致性的现象。
  • 二阶段没法解决的问题:协调者在发出commit消息以后宕机,而惟一接收到这条消息的参与者同时也宕机了。那么即便协调者经过选举协议产生了新的协调者,这条事务的状态也是不肯定的,没人知道事务是否被已经提交。

3PC

三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。

3pc
3pc

若是由于协调者或网络问题,致使参与者迟迟不能收到来自协调者的commit或rollback请求,那么参与者将不会如两阶段提交中那样陷入阻塞,而是等待超时后继续commit。相对于两阶段提交虽然下降了同步阻塞,但仍然没法避免数据的不一致性。在分布式数据库中,若是指望达到数据的强一致性,那么服务基本没有可用性可言,这也是为何许多分布式数据库提供了跨库事务,但也只是个摆设的缘由,在实际应用中咱们更多追求的是数据的弱一致性或最终一致性,为了强一致性而丢弃可用性是不可取的。

柔性事务

根据BASE理论,系统并不保证续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功以后,不承诺当即能够读到最新写入的值,也不会具体的承诺多久以后能够读到。

弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操做的值。在没有故障发生的前提下,不一致窗口的时间主要受通讯延迟,系统负载和复制副本的个数影响。DNS 是一个典型的最终一致性系统。 在工程实践上,为了保障系统的可用性,互联网系统大多将强一致性需求转换成最终一致性的需求,并经过系统执行幂等性的保证,保证数据的最终一致性。但在电商等场景中,对于数据一致性的解决方法和常见的互联网系统(如 MySQL 主从同步)又有必定区别。

补偿机制:TCC

TCC 其实就是采用的补偿机制,其核心思想是:针对每一个操做,都要注册一个与其对应的确认和补偿(撤销)操做。它分为三个阶段:

  • Try 阶段主要是对业务系统作检测及资源预留
  • Confirm 阶段主要是对业务系统作确认提交,Try阶段执行成功并开始执行Confirm阶段时,默认 Confirm阶段是不会出错的。
  • Cancel 阶段主要是在业务执行错误,须要回滚的状态下执行的业务取消,预留资源释放。

TCC
TCC

TCC与2PC协议比较:

  • 位于业务服务层而非资源层
  • 没有单独的准备(Prepare)阶段,Try操做兼备资源操做与准备能力
  • Try操做能够灵活选择业务资源的锁定粒度(以业务定粒度)
  • 较高开发成本

本地消息表

相似于可靠消息方案。

本地消息表
本地消息表

消息生产方,须要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。而后消息会通过MQ发送到消息的消费方。若是消息发送失败,会进行重试发送。

消息消费方,须要处理这个消息,并完成本身的业务逻辑。此时若是本地事务处理成功,代表已经处理成功了,若是处理失败,那么就会重试执行。若是是业务上面的失败,能够给生产方发送一个业务补偿消息,通知生产方进行回滚等操做。

生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。若是有靠谱的自动对帐补帐逻辑,这种方案仍是很是实用的。

这种方案遵循BASE理论,采用的是最终一致性,即不会出现像2PC那样复杂的实现(当调用链很长的时候,2PC的可用性是很是低的),也不会像TCC那样可能出现确认或者回滚不了的状况。

优势: 一种很是经典的实现,避免了分布式事务,实现了最终一致性。

缺点: 消息表会耦合到业务系统中,若是没有封装好的解决方案,会有不少杂活须要处理。

事务消息

转帐流程
转帐流程

RocketMQ第一阶段发送Prepared消息时,会拿到消息的地址,第二阶段执行本地事物,第三阶段经过第一阶段拿到的地址去访问消息,并修改消息的状态。

若是确认消息发送失败了怎么办?RocketMQ会按期扫描消息集群中的事务消息,若是发现了Prepared消息,它会向消息发送端(生产者)确认,Bob的钱究竟是减了仍是没减呢?若是减了是回滚仍是继续发送确认消息呢?RocketMQ会根据发送端设置的策略来决定是回滚仍是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

若是endTransaction方法执行失败,数据没有发送到broker,致使事务消息的 状态更新失败,broker会有回查线程定时(默认1分钟)扫描每一个存储事务状态的表格文件,若是是已经提交或者回滚的消息直接跳过,若是是prepared状态则会向Producer发起CheckTransaction请求,Producer会调用DefaultMQProducerImpl.checkTransactionState()方法来处理broker的定时回调请求,而checkTransactionState会调用咱们的事务设置的决断方法来决定是回滚事务仍是继续执行,最后调用endTransactionOneway让broker来更新消息的最终状态。

  • 消费失败
    解决超时问题的思路就是一直重试,直到消费端消费消息成功

  • 消费超时
    消费失败怎么办?阿里提供给咱们的解决方法是:人工解决。你们能够考虑一下,按照事务的流程,由于某种缘由Smith加款失败,那么须要回滚整个流程。若是消息系统要实现这个回滚流程的话,系统复杂度将大大提高,且很容易出现Bug,估计出现Bug的几率会比消费失败的几率大不少。这也是RocketMQ目前暂时没有解决这个问题的缘由,在设计实现消息系统时,咱们须要衡量是否值得花这么大的代价来解决这样一个出现几率很是小的问题,这也是你们在解决疑难问题时须要多多思考的地方。

Lottor介绍

Lottor用于解决微服务架构下分布式事务的问题,基于可靠性消息事务模型实现。

Lottor的结构

Lottor由三部分组成:

  • Lottor Server
  • Lottor Client
  • Lottor UI

Lottor服务器与客户端之间的通讯使用的高性能通讯框架:Netty。全部的客户端(生产端和消费端)都会与服务器保持长链接。Lottor UI用于展现系统中的事务组详细信息,包括预提交的事务组、消费失败的事务消息,并支持页面操做失败的消息(如补偿或重试)。

Lottor的设计

功能介绍

生产方分为三步:

  • 预发送消息,首先会将消费方的事务组(一条或多条事务消息)组装好,并发送到Lottor Server,事务消息的状态为预发送
  • 执行本地事务:预发送以后,将会执行本地事务。
  • 发送确认消息:根据本地事务的执行结果,异步发送确认消息。若是本地事务出现异常,回滚本地事务,并将异常信息捕捉一块儿发送到Lottor Server。本地也会持久化该状态(按期删除)。

Lottor Server:

  • 接收预提交消息:收到预提交消息,将事务组中的事务消息分别保存,状态为pre-commit
  • 接收确认消息:状态为confirm,将更改相应的事务组状态,并将消息发送到对应的消费方(MQ异步实现),并标记事务消息的状态为unconsumed。不然,回滚状态只会修改事务组状态(按期删除)。
  • 回查预发送消息的状态:状态为pre-commit的事务组消息,Lottor Server将会按期回查生产方。
  • 回查事务消息的状态:状态为unconsumed(通常4h),Lottor Server将会按期回查消费方。

消费方:

  • 接收事务消息:订阅相关的主题,消费完成以后,将会异步发送ACK给Lottor Server,消费失败会将异常返回给Lottor Server。本地也会持久化消费的状态(按期删除)。

Lottor 客户端的持久化,提供了SPI接口,可经过配置动态指定。目前支持:JDBC、Redis、MongoDB和文件系统。

告警机制及消费补偿

这里所说的告警机制及消费补偿是针对消费端,可靠消息方案是保证了事务消息必定可以到达消费方,可是消费方可能由于某些缘由而没法成功消费,有些消费异常是能够经过重试解决的,而有些异常是须要告警以后人工干预的。好比消费方暂时不可用,或者是多个消费方消费的顺序问题,能够经过定时的重试机制完成。而若是是因为生产方发送的事务消息出错(参数构造错误),此时消费方已经提交了本地事务组,因此是没法经过重试实现成功消费,致使须要告警,人为解决脏数据的问题。

适用场景

对于分布式系统的吞吐量有较高的要求,以及可以知足最终一致性的场景。如上面提到的告警机制及消费补偿,分布式事务是对微服务系统的完善,可是并不能彻底保证一致性,可能须要经过告警等手段解决极端问题产生的不一致状况。

项目截图

项目结构

UI界面

首页

事务组信息

事务组状态

总结

本文主要介绍了分布式事务的相关概念以及业界一些经常使用的解决方案(参考了不少网上的博客),并提出了笔者基于可靠消息方案的实现:Lottor。后续文章将会详细介绍Lottor的实现,敬请期待。

订阅最新文章,欢迎关注个人公众号

微信公众号

参考

  1. 聊聊分布式事务,再说说解决方案
  2. 分布式事务 - 两阶段提交与三阶段提交
  3. 关于分布式事务、两阶段提交协议、三阶提交协议
  4. 分布式开放消息系统(RocketMQ)的原理与实践
  5. TCC型分布式事务原理和实现之:原理介绍
  6. 分布式事务之说说TCC事务
相关文章
相关标签/搜索