首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

分布式的事务该怎么做?

分布式八大坑

分布式就是魔鬼啊!

张大胖最近十分感慨,他所在的公司原来有个电商系统,后来随着用户量越来越大,对系统的可用性要求越来越高。 CTO要求把系统进行拆分, 从一个单体的应用,拆分成微服务组成的应用。

微服务听起来很美好,但是其中的苦只有做过的人才知道。

在原来的单体应用中,订单模块想要调用库存和支付,只要调用相关的类或者接口就可以了,只有一个数据库,轻轻松松就可以把所有操作放到一个事务当中,保证不会出现扣了库存但是支付失败的情况。

(单体应用)

现在好了,系统成了分布式,原来的进程间调用变成了跨越网络的HTTP调用,这数据库也从单个数据库变成了多个独立的数据库,原来的事务肯定是不起作用了!

大神Bill告诉张大胖分布式有八个大坑, 千万别跳到坑里去:

当你在构建一个分布式系统的时候,可能会不由自主地做一些假设,这些假设从长期来看,都是错误的,都会导致大麻烦:

1、网络是可靠的

2、(调用)没有延迟

3、无限的带宽

4、网络是安全的

5、(系统)拓扑结构不会改变

6、有个管理员在管理这个系统

7、(数据)传输代价为零

8. 网络是同质的(同类的)

这第一条就很要命,网络是不可靠的, 网络调用失败的可能性是非常高的,很有可能出现扣减了库存,但是没有支付的情况。

分布式的事务

怎么样让扣减库存和支付服务能在一个类似数据库事务中完成,要么都做,要么都不做呢? 张大胖觉得十分头疼。

张大胖首先想到了两阶段的提交(2PC),但是2PC是针对底层的数据资源层实现的,现在要做的是业务层的事情, 况且这2PC也很不好用啊。

(码农翻身注: 2PC的故事参见《

Java帝国之宫廷内斗

》)

他觉得必须要有一个协调人居中协调各个微服务,让他们处于一个“事务”中, 可是这协调者该如何实现?

Bill 递给他一篇文章:“你要实现的就是分布式事务了!看看这篇文章吧!”

张大胖接过打印的文章,标题是:《Distributed Atomic Transactions over RESTful Services》 ,他的头嗡地一声就变大了,哀叹道:“英文的啊,你还是给我讲讲吧!”

“英文很重要啊,大胖同学!” Bill说道,“其实这个分布式事务的原理很简单,它的精华就是冻结资源幂等性。”

(此处应该插入一个英语广告,哈哈。)

张大胖说:“这幂等性我知道,就是一个操作不管是执行一千次,一万次,效果和执行一次是一样的。 这冻结资源是怎么回事?”

“拿咱们的系统举个例子吧,订单服务要调用库存服务扣减库存(假设数量为2),还要调用支付服务从用户余额扣钱(假设为100), 那订单服务第一步就告诉库存服务,给我冻结2个库存; 告诉支付服务,给我冻结100块钱。在这一步,两个服务要做业务检查,看看库存余额够不够,如果足够,就冻结他们,防止其他调用也进行了扣减操作,导致本次调用余额不足。 这一步,我们称之为尝试(Try)。 ”

(注: 这里也可以对库存数量和用户余额做扣减)

库存服务和支付服务操作的都是自己的表,冻结操作可以放到一个本地事务中,保证原子性。

“明白, 接下来呢?” 张大胖问道。

“这一步如果成功完成,订单服务就可以进入第二步,告诉这两个服务真正地执行扣减操作,这一步叫做Confirm。”

(注:如果在第一步已经做了扣减,这里只需要修改相关状态即可,大家可自行脑补。 )

“慢着,如果调用支付服务进行Confirm时出错怎么办? ” 张大胖问道。

“很简单,那就告诉库存服务和订单服务,都进行Cancel操作, 把冻结的数量进行恢复。”

Bill说道。 “我们把这套机制叫做Try - Confirm -Cancel,简称TCC。对于每个每个微服务来讲,都要提供try , confirm , cancel这三个接口。” Bill接着说,“另外每个微服务也得有一个专门用来管理TCC的组件。”

异常场景

张大胖心想,你说得简单,这都是所谓Happy Path, 在分布式环境中出错是必然的,他很快找到了第一个场景:

场景1:库存服务的Try操作完成, 支付服务的Try操作没有完成, 怎么办?

Bill说:“这很好处理,订单服务可以尝试去调用库存的Cancel操作(这应该是个幂等操作,可以多次调用),把冻结的库存释放。”

张大胖说:“那如果出现网络问题,订单服务无法联系库存服务了呢?”

“不用担心,库存服务的TCC组件能够发现冻结的时间已经超时,会自动把冻结的库存给释放。”

场景2:两个微服务的Try操作都完成, 然后发生网络故障,导致两个Confirm都无法进行

Bill说: “和第一种情况一下,TCC组件会发现超时,释放冻结的资源, 当然,冻结的这部分资源在释放前的一段时间内不可以被使用。”

“可是,如果库存服务所在的机器已经挂掉了呢?怎么计算超时?” 张大胖问道。

“这是个好问题,所以TCC系统必须得记录日志,把那些没有完成的事情记录下来,持久化到硬盘上。这样下次重启就可以接着执行了。”

场景3:Try操作都已完成,资源已经冻结,在第三步中库存服务Confirm成功,库存做了扣减, 但是支付服务挂掉了,余额还处于冻结状态, 怎么办?

Bill 说道:“那可以多尝试几次, 让支付服务做Confirm操作(很明显,这个Confirm操作必须得是一个幂等操作才行)。如果实在是无法成功,那就可以让库存服务做Cancel操作。 如果还是不行,只有让人工介入了。”

怪不得Bill一直在强调幂等性,原来真正的作用是这样啊。

转向BASE?

张大胖想了想,似乎各种情况都能覆盖了, 但是还有实现层面的大问题:

(1) 就是对于try (冻结资源), confrim , cancel(恢复资源)这样的操作都需要程序员去写代码实现。

比如对于支付服务, 至少的实现三个方法:

tryPayment(......)

confirmPayment(......)

cancelPayment(......)

这样TCC框架才可以去调用。

(2) 还得自己搞个TCC框架。

Bill 笑道: “那没办法,分布式就是这么烦人。TCC框架倒是有一些现成的,比如Atomikos,tcc-transaction,Hmily等, 但是那些try,confrim, cancel是业务方法,程序员必须得写, 跑不掉的。”

“就没有别的办法了吗?”

“有啊,也可以尝试下另外一个最终一致性的模型,叫做BASE。” Bill随手又递过来一篇论文,名字是《BASE: An Acid Alternative》

“有没有搞错 ! 又是英文的!”

“你要是不想看英文的,就去看看老刘写的《Java帝国之宫廷内斗》吧!”

●编号479,输入编号直达本文

●输入m获取文章目录

推荐↓↓↓

黑客技术与网络安全

更多推荐《25个技术类微信公众号》

涵盖:程序人生、算法与数据结构、黑客技术与网络安全、大数据技术、前端开发、Java、Python、Web开发、安卓开发、iOS开发、C/C++、.NET、Linux、数据库、运维等

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181224B0QK5Y00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券