首页 > 经验记录 > java > 探秘分布式解决方案: 分布式事务——从核心思想之2PC(两阶段提交)开始

探秘分布式解决方案: 分布式事务——从核心思想之2PC(两阶段提交)开始

为什么要有分布式事务?

本地单机事务,有点工作经验的肯定不陌生,主要用于处理操作量大,复杂度高的数据,一般都由数据库自己实现。

开启一个事务,进行了多个对数据库进行更新(增删改)的语句后,可以自由的选择 commit rollback 来结束事务。

可以从根源上保证多个数据集合的同步。

 

比如一个订单系统,用户支付订单后,订单表的订单状态被改为已支付,同时,库存表中商品剩余数量减一。

若是没有事务机制,在订单库状态修改成功后,由于库存不够减、网络波动、程序bug、数据库挂掉等原因导致数据库中库存减一的SQL语句执行失败; 那这样到最后就会出现实际库存早就没有了,但用户却支付成功的重大事件,即超卖了。

所幸。只要你开了事物,那么事务的原子性确保让你多个语句作为一个原子操作来执行,要么同时成功要么同时失败,在库存-1 失败的同时你订单修改也失败了。所以在单机简单应用中一般不存在这种问题。

 

可是,在稍微复杂一点的场景中,数据库自带的事务机制也有着无能为力的时候。

仍然是一个订单系统,这回场景为由于系统用户激增,入驻商家也越来越多。在日渐庞大的数据量下出于性能考虑决定对数据库进行拆分,由原来的一个库,拆分为一个订单库、一个库存库

你的程序里边,一个下单方法中会执行两个库的SQL语句,在这样的逻辑下,普通的事务还有用么?

就像这样:

订单服务-1

 

那肯定是没有用的。

这种场景中必须要实现跨库事务才能够满足企业级生产需求。

 

要如何实现这种跨库事务? 这里引出分布式事务中的重点思想:阶段提交

 

两阶段提交协议(2PC)

2PC 引入一个事务管理器来作为事务的协调者。

在程序执行复杂数据操作时,开启事务、SQL语句执行完后,先不进行commit。先将执行的结果告知给事务管理器

这个时候,再执行其他数据库的SQL语句。当这个事务中,所有的SQL语句全部执行完毕后,即事务管理器已经得到所有此次事务中SQL语句的执行结果后。

再集体通知所有的数据库进行commit 或 rollback。

 

以下单流程来模拟的话。

前置条件:已引入事务管理器

此时,用户一个支付请求过来。先执行订单库的修改语句,将用户的订单改为已支付。

执行成功后,不提交事务。再开启库存库的事务、执行库存库的库存修改语句,将库存减一。注意,这是两个不同数据库的不同事务。

这个时候数据库的事务隔离机制就将数据锁住了,包括订单库的订单、库存的数量。但是还没有提交。

在每个SQL执行完成之后,都会将执行完成的信息提交给事务管理器。

那么,当订单、库存都修改成功,事务管理器之中都收到了执行成功信息,确认好了本次事务中的所有语句都执行完成后、立即通知所有数据库进行 commit 操作。

任何一个语句执行失败,事务管理器就会通知所有数据库进行rollback。

 

活动图如下所示:

2PC-01

 

2PC中的底层实现思想,就是将原先的单次提交操作分为两个阶段,即 1、预提交 2、实际提交

看起来是不是很简单。大道至简,实际上只要理解了两阶段提交的思想内核,那么任何分布式事务的解决方案都难不倒你了。

因为TCC、消息事务等事务解决方案,都是从2PC衍生的。

 

 

那么问题来了,2PC凭什么可以解决分布式事务问题?

有人可能也有疑问。2PC好像不是那么严谨?

比如: 事务管理器的通知失败了咋搞,假设事务管理器通知订单库的commit成功了,但是通知库存库去commit的时候失败了。那不还是有问题。

这里就必须说明一下分布式事务解决方案的重点。 那就是: 分布式事务不可能100%解决

不信你去问阿里巴巴的员工、Google的员工,去问他们:”你们公司的分布式事务能不能百分百成功呀?” 肯定不可能,起码现在9102年在这个地球上是没有哪家公司敢说自己的分布式事务是百分百成功的。

虽说分布式事务不可能百分百解决,但是可以尽量提高成功概率,比如让你的成功概率接近99.9999%,即让你的系统尽可能的高可用。

 

 

而2PC就是及其有效的提高成功率的方法

这里要先说明,这个事务管理器并不是一个单独的服务,而是内嵌在你程序里的组件。这玩意挂了你程序也挂了,所以不要操心事务管理器挂掉的问题。

以订单为例,其实最容易出现的问题主要是 库存不够减、数据库挂了、网络波动

而在使用2PC的情况下,你的所有SQL都执行成功了,但是事务管理器commit失败了,是个什么概念啊?

要知道在程序的世界里,你别说分两个阶段,你分再多也就几毫秒的时间。

在下单这里预提交成功后,可以显而易见的得出几个结论:

1、所有SQL都执行成功了,所以数据库在执行SQL语句时状态是正常的、服务器和数据库之间的网络也是正常的

2、由于库存库的SQL执行成功,所以库存够减,不可能出现库存不够的问题

 

你跟我说你预提交完,这个时候事务管理器刚通知完订单库commit成功,在提交库存库减库存commit时数据库挂掉了? 或者网络波动导致通知失败了?明明前1ms还好好的?

wdnmd

咳咳,不否认有这种概率啊。可是对于2PC来说,你通知commit失败后,是会重试的。基本上都不会有问题。

那么,要是一直重试都不成功呢?比如挖掘机在你提交库存库commit时,将网线挖断了。emmmmmm,有这种疑问也实属正常,毕竟啥情况都可能发生是不。

这种情况出现确实没有别的办法。一般来说都会记日志打log,之后要么后台通过定时任务补偿,要么人工介入

像一线互联网公司啊,都有大量的一些人工团队专门处理这种极端情况下出现的数据不一致问题,还会让研发工程师写一些什么脚本之类的去回滚、回复数据。

 

 

综上所述。回过头再品一品两阶段提交的思想。为什么说2PC可以提高分布式事务的成功概率,相信已经了然于心了。

而像是这种2PC的具体实现,那肯定也是不用你自己操心的。在java的世界中这种框架已经有不少了。比如开源框架 atomikos,就可以帮你解决这种跨库事务问题,当然,底层就是用的2PC思想做的。

至于如何具体在代码里实现,这里就不贴了。搜索引擎搜一下atomikos可以搜到一大堆。基本上在Spring里边也就是引入依赖后配置一下,然后编程式事务/声明式事务轻松搞定。还可以找一下、了解一下我这篇博客没说明(因为感觉没什么必要)的XA/JTA 接口规范等。

           


1 COMMENT

EA PLAYER &

历史记录 [ 注意:部分数据仅限于当前浏览器 ]清空

      00:00/00:00