前言:笔者最近实现了基于可靠消息方案的分布式事务:Lottor。本文将会介绍Lottor的概况,在后续系列文章介绍具体的实现,欢迎关注。html
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不一样的分布式系统的不一样节点之上。算法
首先,解释下事务的概念:一组操做要么都完成以后提交,要么所有回滚。分布式事务特指在分布式环境下,一次事务设计多个服务进程,说白了就是跨进程的事务,这样就不能控制事务组的一致性。数据库
分布式系统区别于传统的单体应用,单体应用的服务模块和数据都在一个服务中,使用Spring框架的事务管理器便可知足事务的属性。而分布式系统中,来自客户端的一次请求每每涉及多个服务,事务的一致性问题由此产生。服务器
CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务没法同时知足一下3个属性:微信
具体地讲在分布式系统中,在任何数据库设计中,一个Web应用至多只能同时支持上面的两个属性。网络
上面这句话的表述,不少人都用过,是的,这是一种误解。注意CAP定律的完整表述:Any networked shared-data system can have at most two of the three desired properties.架构
CAP 定律的前提是 P,当 P 决定后才有 CA 的抉择。所以,简单粗暴地说「三选二」是有必定误导性的。并发
在分布式系统中,咱们每每追求的是可用性,它的重要程序比一致性要高,那么如何实现高可用性呢? 前人已经给咱们提出来了另一个理论,就是BASE理论,它是用来对CAP定理进行进一步扩充的。BASE理论指的是:框架
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:咱们没法作到强一致,但每一个应用均可以根据自身的业务特色,采用适当的方式来使系统达到最终一致性(Eventual consistency)。异步
功能需求最主要的是知足分布式事务的一致性,涉及的事务组中的操做为多个写操做,当产生一个或多个写操做失败时,回滚整个事务组中的操做。
X/Open 组织(即如今的 Open Group )定义了分布式事务处理模型。 X/Open DTP 模型( 1994 )包括应用程序( AP )、事务管理器( TM )、资源管理器( RM )、通讯资源管理器( CRM )四部分。
XA 就是 X/Open DTP 定义的交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。 XA 接口函数由数据库厂商提供。
二阶段提交的算法思路能够归纳为:参与者将操做成败通知协调者,再由协调者根据全部参与者的反馈情报决定各参与者是否要提交操做仍是停止操做。第一阶段:准备阶段(投票阶段)和第二阶段:提交阶段(执行阶段)。
三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。
若是由于协调者或网络问题,致使参与者迟迟不能收到来自协调者的commit或rollback请求,那么参与者将不会如两阶段提交中那样陷入阻塞,而是等待超时后继续commit。相对于两阶段提交虽然下降了同步阻塞,但仍然没法避免数据的不一致性。在分布式数据库中,若是指望达到数据的强一致性,那么服务基本没有可用性可言,这也是为何许多分布式数据库提供了跨库事务,但也只是个摆设的缘由,在实际应用中咱们更多追求的是数据的弱一致性或最终一致性,为了强一致性而丢弃可用性是不可取的。
根据BASE理论,系统并不保证续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功以后,不承诺当即能够读到最新写入的值,也不会具体的承诺多久以后能够读到。
弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操做的值。在没有故障发生的前提下,不一致窗口的时间主要受通讯延迟,系统负载和复制副本的个数影响。DNS 是一个典型的最终一致性系统。 在工程实践上,为了保障系统的可用性,互联网系统大多将强一致性需求转换成最终一致性的需求,并经过系统执行幂等性的保证,保证数据的最终一致性。但在电商等场景中,对于数据一致性的解决方法和常见的互联网系统(如 MySQL 主从同步)又有必定区别。
TCC 其实就是采用的补偿机制,其核心思想是:针对每一个操做,都要注册一个与其对应的确认和补偿(撤销)操做。它分为三个阶段:
TCC与2PC协议比较:
相似于可靠消息方案。
消息生产方,须要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。而后消息会通过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服务器与客户端之间的通讯使用的高性能通讯框架:Netty。全部的客户端(生产端和消费端)都会与服务器保持长链接。Lottor UI用于展现系统中的事务组详细信息,包括预提交的事务组、消费失败的事务消息,并支持页面操做失败的消息(如补偿或重试)。
生产方分为三步:
预发送
。Lottor Server:
pre-commit
。unconsumed
。不然,回滚状态只会修改事务组状态(按期删除)。pre-commit
的事务组消息,Lottor Server将会按期回查生产方。unconsumed
(通常4h),Lottor Server将会按期回查消费方。消费方:
Lottor 客户端的持久化,提供了SPI接口,可经过配置动态指定。目前支持:JDBC、Redis、MongoDB和文件系统。
这里所说的告警机制及消费补偿是针对消费端,可靠消息方案是保证了事务消息必定可以到达消费方,可是消费方可能由于某些缘由而没法成功消费,有些消费异常是能够经过重试解决的,而有些异常是须要告警以后人工干预的。好比消费方暂时不可用,或者是多个消费方消费的顺序问题,能够经过定时的重试机制完成。而若是是因为生产方发送的事务消息出错(参数构造错误),此时消费方已经提交了本地事务组,因此是没法经过重试实现成功消费,致使须要告警,人为解决脏数据的问题。
对于分布式系统的吞吐量有较高的要求,以及可以知足最终一致性的场景。如上面提到的告警机制及消费补偿,分布式事务是对微服务系统的完善,可是并不能彻底保证一致性,可能须要经过告警等手段解决极端问题产生的不一致状况。
本文主要介绍了分布式事务的相关概念以及业界一些经常使用的解决方案(参考了不少网上的博客),并提出了笔者基于可靠消息方案的实现:Lottor。后续文章将会详细介绍Lottor的实现,敬请期待。