背景
由于公司项目是使用dubbo进行开发的分布式服务,所以项目中有很多涉及到分布式事务问题的场景。比如有两个模块:用户模块和账户资金模块。有一个场景是用户被邀请成为系统的新用户,需要先初始化用户信息,然后再去账户资金模块初始化用户账户信息。两个不同的模块为两个不同的RPC服务,分别被调用然后插入数据,这时候如果账户资金插入失败,不加入分布式事务的话用户直接初始化成功。我们希望这种情况下用户插入的信息被回滚,所以需要引入分布式事务来进行业务处理。
使用的框架
经过调研,我们发现TX-LCN框架比较适合我们的业务场景,我们打算引入并使用LCN事务模式来进行服务中的分布式事务的业务处理。关于LCN、TCC、TXC几种事务模式的区别在下面整理。
在官网下载对应的服务,并引入项目或者单独启动:
引入依赖:
1 | <dependency> |
使用:
在服务的发起方使用注解@TxTransaction(isStart = true)
1 |
|
在服务的参与方使用注解@TxTransaction
标识即可
1 |
|
然后再启动项目之前,先启动tx-manager服务,作为协调者的角色存在,然后启动项目,调用接口的时候就可以使用分布式事务了。
其他关于分布式事务的总结整理
关于Tx-LCN - 官网文档
Tx-LCN早期是为了设计出LCN分布式事务而命名的,不过在5.0之后可以支持LCN、TXC、TCC三种分布式事务模式。LCN框架定位为不生产事务,只做事务的搬运工
。即TX-LCN是一款事务协调框架,本身并不操作事务,只是基于对事务的协调从而达到事务一致性的效果。
最新版本的TX-LCN同时支持多种事务模式。并支持多种不同的数据源同时使用分布式事务,完全达到拔插效果。以下是三种事务模式的介绍、区别及优缺点。
LCN事务模式
- 仅仅作为事务的协调者,本身不生产事务。原理是在事务发起方创建事务分组,并通过Http/Tcp协议调用的时候将事务分组传递到事务参与方。事务参与方的本地事务执行成功之后,根据事务分组ID通知事务的发起方提交所有事务。在全部事务成功通知之前,其各个本地事务均为假关闭,等待TxManager协调完成事务之后再关闭连接。
- LCN事务对于代码几乎没有嵌入性,只需要添加注解即可实现分布式事务。
- LCN的事务提交与回滚都有本地事务保障,更安全的确保数据一致性。
- 但是LCN仅限于存在本地事务并且可以通过连接对象控制事务模块的系统。
- LCN模式依赖于连接代理服务,事务的发起方与参与方要一起连接与释放,较耗性能。
TCC事务模式
- TCC事务模式不依赖于资源管理器对于XA的支持,而是通过业务系统提供的业务逻辑的调度来实现分布式事务。即编写三步操作:Try:尝试执行业务(先插入一条数据,状态为Try)、Confrim:确认执行业务(成功后状态改为Confrim)、Cancel:取消执行业务(需要回滚的话根据前两个步骤的id继续更改状态或者删除数据)。
- TCC模式对于代码的侵入性很高,基本上一个接口需要拆分成3个,对于程序员的编码要求很高,业务的正确性、数据一致性都由开发者来保证
- 优点是TCC模式对于有无本地事务的场景都适用,甚至可以跨数据源,针对MySQL、redis、Mongo等整合成一个大的事务
TXC事务模式
- 其来源于阿里巴巴的一个分布式事务中间件,可以通过极少量的代码侵入,实现分布式事务。原理是在执行SQL之前,先查询SQL的影响数据,然后保存执行的SQL快照信息并创建锁。当需要回滚的时候就采用这些记录回滚数据库。目前实现锁使用的是redis分布式锁控制。
- TXC事务模式同样对代码的侵入性低
- 该模式仅限于对支持SQL方式的模块支持
- 该模式由于每次执行SQL之前需要先查询影响数据,因此相比LCN模式更消耗性能
- TXC事务模式不会占用数据库资源
- 关于TXC事务的详细介绍可以查看这篇博客
CAP理论和BASE理论
CAP理论
CAP理论即数据一致性、服务可用性和分区容错性的简称。
- Consistency 数据一致性:一次完整的更新操作,所有节点在同一时刻访问到的数据是一致的
- Availability 服务可用性:服务一直可用,并且是正常响应时间
- Partition tolerance 分区容错性:分布式多节点在某几个节点挂掉后仍然可以对外提供正常的服务
事实上,CAP这三种并无法保证完全满足,一般只能满足其二。对于分布式系统来说,分区容错性是最基本需要满足的条件,否则不能称为分布式系统,只满足CA 其实是传统的单机服务。那么在满足P(分区容错)的时候,就需要在C和A之间权衡。事实上,分布式系统下一般会保证A(服务可用)P,放弃数据的强一致性,只保证数据的最终一致性。这样就衍生出了BASE理论。
BASE理论
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
- 基本可用:在分布式系统中,允许在出现不可预知故障的情况下损失部分可用性(并不是允许系统不可用),允许响应时间上的确实和部分系统功能的错误(并发场景下拒绝服务产生报错等)
- 软状态:即允许数据存在中间状态,允许不同节点的数据之间存在时间上的数据差异
- 最终一致性:强调所有数据副本能够最终一致,不需要保证数据实时强一致。
传统事务的ACID机制追求强一致性,而BASE理论为分布式系统而生,主张牺牲强一致性,使得服务达到高可用。不过在分布式系统中,不同场景下对于数据的一致性又是不同的,所以才会有分布式事务来保证数据的一致,即ACID与BASE理论结合使用。
2PC和3PC
2PC - 二段式提交
2PC,是Two-Phase Commit的缩写。过程如下:
阶段一:提交事务请求
- 协调者询问参与方是否可以执行提交操作并等待响应
- 参与者执行询问为止的所有事务操作
- 参与者响应协调者,返回事务执行成功与否
阶段二:执行事务提交
协调者收到所有第一阶段响应之后发起正式提交请求
参与者正式完成操作并释放整个事务期间占用的资源
反馈事务提交结果
协调者收到所有完成的消息后,完成事务。
如果任意参与者在第一阶段返回执行失败或者超时的消息,中断事务
参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
参与者节点向协调者节点发送”回滚完成”消息。
协调者节点受到所有参与者节点反馈的”回滚完成”消息后,取消事务。
不管最后结果如何,第二阶段都会结束当前事务
2PC的缺点: 执行过程中所有节点都是同步阻塞的;如果协调者是单点并且发生故障,参与者将一直阻塞;如果在第二阶段协调者正式向所有参与者发送正式提交事务的请求,其中部分因为网络问题没有收到,另一部分却已经提交无法回滚,出现数据不一致的情况;协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
由于二阶段提交存在着诸如同步阻塞、单点问题、脑裂等缺陷,所以,研究者们在二阶段提交的基础上做了改进,提出了三阶段提交。
3PC - 三段式提交
3PC将2PC的准备阶段又划分为两次准备,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。。并且在协调者与参与者中都引入超时机制。
CanCommit阶段
- 同2PC的准备阶段,协调者向参与者发送事务提交请求,询问并等待响应
- 得到响应之后进入预备提交阶段
PreCommit阶段
- 所有参与者第一阶段都返回成功状态,那么就会开始事务的预执行
- 进入PreCommit并执行事务操作,全部成功就返回给提交者
- 假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。发送中断请求协调者向所有参与者发送abort请求;中断事务参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。
doCommit阶段
发送提交请求协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
事务提交参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
响应反馈事务提交完之后,向协调者发送Ack响应。
完成事务协调者接收到所有参与者的ack响应之后,完成事务。
中断事务协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。
发送中断请求协调者向所有参与者发送abort请求
事务回滚参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
反馈结果参与者完成事务回滚之后,向协调者发送ACK消息
中断事务协调者接收到参与者反馈的ACK消息之后,执行事务的中断。
相比于2PC,3PC最关键要解决的就是协调者和参与者同时挂掉的问题,所以3PC把2PC的准备阶段再次一分为二。在第一阶段只是询问所有参与者是否都可以执行事务操作,并不在本阶段执行事务操作。当协调者收到所有的参与者都返回YES时,在第二阶段才执行事务操作,然后在第三阶段在执行commit或者rollback。
3PC存在的问题: 在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者abort请求时,会在等待超时之后,会继续进行事务的提交。这样与其他执行回滚的参与者就会出现数据不一致的情况。
参考: