赞
踩
前言:笔者最近实现了基于可靠消息方案的分布式事务:Lottor。本文将会介绍Lottor的概况,在后续系列文章介绍具体的实现,欢迎关注。
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
首先,解释下事务的概念:一组操作要么都完成之后提交,要么全部回滚。分布式事务特指在分布式环境下,一次事务设计多个服务进程,说白了就是跨进程的事务,这样就不能控制事务组的一致性。
分布式系统区别于传统的单体应用,单体应用的服务模块和数据都在一个服务中,使用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的实现,敬请期待。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。