跟事务杠上篇-分布式事务瞅你咋的(上)

Posted 还是那个徐东强

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了跟事务杠上篇-分布式事务瞅你咋的(上)相关的知识,希望对你有一定的参考价值。

经过了一个颓废的国庆长假,每天写不完的增删改查,感觉最近的自己有点丧丧的?!

No,虽然最近没咋学习,就简单以菜鸟的角度记录下零零碎碎看的事务。


话说我不会写目录

  • 事务概念

  • 一阶段提交

  • 两阶段提交

  • 消息中间件来解决分布式事务

  • TCC


啥叫事务?

不仅仅绝限于数据库,要不都成功,要不都失败,这种都可以叫做事务。

啥叫分布式事务?

一个业务分布于多个系统,要不都成功,要不都失败,这种都可以叫分布式事务。


一阶段提交(Best Efforts 1PC)

可以简单得理解为一个应用服务的多个本地事务。


实际应用的事例:cobar client

具体实现:

事务提交or回滚的时候,则同时提交or回滚所有的本地事务。

看MultipleDataSourcesTransactionManager源码还挺简单的,在服务启动的时候,想办法注入所有的数据源,通过数据源构造多个DataSourceTransactionManager,

然后在begin,commit,rollback的时候遍历上面构造的多个transactionManager,同时进行begin,commit,rollback。由于同时开始所有数据源的本地事务,默认情况下,自然会占用每一个数据源的物理链接,进而造成性能问题。所以在数据源上加了拦截LazyConnectionDataSourceProxy,这样将保证只有存在确切dao操作的时候,才会真正的从目标数据源获取真正的物理连接。

自己仿造写了一个MultipleDataSourcesTransactionManager,LazyConnectionDataSourceProxy果然是很有用啊,没加之前,在遇到被@Transactional注解方法的时候,会直接获取数据源,加了之后,在被@Transactional注解的方法中真正的dao之前才会去获取数据源,由于我们代码中已经对dao的方法做了拦截会根据方法名动态选择数据源,加了LazyConnectionDataSourceProxy,恰好是 先根据方法名选择数据源,然后获取真正的数据连接。


疑问:

虽然看起来可以多个本地事务,同时begin,commit,rollback,但是想想如果我有一个方法被@Transactional注解,方法中如下

数据源1.updatexxxx

数据源2.updateyyyy

数据源3.updatezzzz

由于构造的事务管理器本质上还是DataSourceTransactionManager,只有第一个dao的操作会去获取数据源,后面的dao操作会直接使用前面的获取connection,压根不会重新获取数据源吗,那第二个第三个dao操作数据源选择的就是错的啊,执行会报错的啊,那即使多个本地事务同时begin,commit,rollback又有什么意义?

后面继续研究下。。。。


分布式事务来啦。。。。


二阶段提交

将提交分成两阶段 1.准备阶段  2.提交阶段

准备阶段就是commit之前的所有动作。

提交阶段就是commit动作。


如何实现?

大概有这样两个角色,事务协调器和若干的事务执行者

1.事务协调器发消息给所有的事务执行者,让都预提交(可以理解成执行commit代码之前的所有代码)

2.事务执行者将自己预提交的结果上报给事务协调器

3.事务协调器根据获取的结果,向事务执行者发通知

-只要一个事务执行者回复的消息是失败,则发消息给所有事务执行者,all rollback吧

-所有事务执行者的消息都OK,则发消息给事务执行者,你们commit吧


二阶段提交能保证分布式事务原子性的关键是?

让事务在提交前尽可能地完成所有能完成的工作,这样,最后的提交阶段将是一个耗时极短的微小动作,这种操作在一个分布式系统中失败的概率是非常小的。


啥?竟然不适用于高并发的系统?

系统水平伸缩的死敌,基于两阶段提交的分布式事务在提交事务时需要在多个节点之间

进行协调,最大限度地推后了提交事务的时间点,客观上延长了事务的执行时间,这会导致事务在访问共享资源时发生冲突和死锁的概率增高,随着数据节点的增多,这种趋势会越来越严重,从而成为系统在数据库层面上水平伸缩的枷锁。


消息中间件来解决分布式事务

比如支付宝转账到余额宝,增加订单和去库存等等。

支付宝减去金额的同时,记下消息,凭此消息(类似于你去买东西时给的小票),保证了余额宝账户增加钱,依靠这个凭证消息,完成最终一致性。


如何可靠保存凭证消息,有两种方法:

第一种:业务与消息耦合的方式

在支付宝完成扣款的同时,记录消息数据。

本地事务保证了,只要支付宝账户里面被扣了钱,消息一定能保存下来。

当上述事务提交成功,通过实时消息服务,将消息通知余额宝,余额宝处理成功后发送回复成功消息,支付宝收到回复消息删除该条消息。


第二种:业务与消息解耦方式

生产端:保证支付宝扣减钱了,消息能发送到MQ上。

消费端:消息发送到MQ上,就保证铁定能被余额宝消费(启用错误重试)。

rocketmq是如何保证 支付宝扣钱和消息发送到MQ在一个事务中呢。

1.封装一个消息(支付宝扣减100),发送给MQ。此条消息对消费端不可见。

(说是rocketmq上存消息的分为两个文件,一个文件A存具体的消息,一个文件B存消息的位置,这个阶段不在文件B中记录消息的位置,消费端拉取消息是从文件B上拉取的,此时自然不可见)

2.发送成功,则回调执行支付宝的本地事务。

3.如果支付宝的本地事务执行成功,则发送确认消息给MQ,此时消息对消费端可见。

如果支付宝的本地事务执行失败,则发送失败消息给MQ,从MQ删除1发送的消息。


由于网络不可达,以上的任何一个步骤都可能失败了。

是如何保证支付宝扣减和消息发送到MQ在一个事务中的呢?

以上面的步骤3在发送确认消息的时候丢失了为例。rocketmq也提出了解决方案。

MQ会把上面这种未确认的preparemessage(仅仅在文件A中有的消息)发送给开发者确认。

代码的作法是:生产端监监听一个TransactionCheckListener, 入口参数就是那条消息,你可以根据这条消息,查看,本地事务是否处理成功了,如果成功了,返回COMMIT,发送确认消息给MQ

不过这块功能在 3.0.8以后已经去掉,纳入收费

以上是关于跟事务杠上篇-分布式事务瞅你咋的(上)的主要内容,如果未能解决你的问题,请参考以下文章

分布式事务科普(初识篇)

深度 | 为你解读 SOFA-DTX 分布式事务的设计演进路线上篇

Spring事务管理的实现方式:编程式事务与声明式事务

Spring事务管理的实现方式:编程式事务与声明式事务

原创分布式事务之TCC事务模型

分布式事务:三个概念