五分钟学会分布式事务

从概念开始

我们先从事务的定义开始。事务即一系列读存动作被当作一个执行单元,这些动作要么全成功,要么全失败,执行动作的过程中保证数据的隔离性和一致性。

我们抛离数据库这个特定场景,先假设一个数据存储设备,我们定义两个标准操作,一个读一个写。当写操作依赖于读到的数据时,执行的顺序决定了得到的结果。

当单线程时,任意读或写操作在这个数据容器上,他必然是符合上述所有的要求的。

当多线程的时候,任意读写,实际上就是导致标准的 race condition,大部分情况下我们是不知道执行结果的。对于单核cpu来说,多线程实际上是cpu模拟,可以理解成所有的操作是合并到一起顺序执行的。在我们不加以控制的前提下,合并的操作队列必然是相对之间无序的。要想多线程情况下,达到单线程一样的事务性。最简单粗暴的办法,就是保证所有请求串行。

保证所有请求串行执行的最简单粗暴的办法就是锁。任意线程操作的适合,上锁,只有这个线程的所有动作做完之后,才能开始下一线程提供的动作。这样一来不管多少事务并行过来,保证了组内的动作一定是串行的。多线程下的动作组的事务性也就保证了。实际上工程后来的进化也是这样的,把执行顺序会影响结果的操作锁住,强制线性执行。

对于数据库来说也是一样,要完成事务的特性,本质还是锁。数据库实际上把读锁和写锁是分开的,颗粒度更细。mvcc的本质也是锁,可以理解成利用 copy-on-write 让写锁独立出来,不影响其他的操作,数据库事务进化的本质就是对锁的优化,从表锁到行锁,不停的降低锁的颗粒度,针对不同的场景使用颗粒度更小的锁。

不一致性的由来

单数据库实例读写必然是高度一致性的。问题是,单实例,更确切来说是单实例MySQL,是很难扛住所有流量的。绝大部分web应用必然是读多写少的,针对这一系统,大部分业务都做了读写分离,主写从读。这样的情况下,主从实际上是有一个不一致窗口的。不去管这个一致性窗口有多么的小,只要经过网络这样的一个慢速设备,不一致窗口,实际上必然存在。所幸的事情是,大部分应用对这个不一致性是可以容忍的。

再随着业务的发展,单机甚至都可能扛不住所有流量。我们需要去分拆数据库。这时问题就更大了,这不仅仅是外部的问题了,核心的问题可能是,在在没有单机事务的庇护下,我们如何去实现一致性。

大部分人的第一个想法是,数据库 XA ,对于两个库,引入外部协调者,然后做2pc。这样真的可行么。我们仔细想一下,如果数据库扛不住流量分拆,那么必然是分拆到两个机器,那么原本在内存中进行的协调操作必须经过网络这个慢速设备,并且XA为了做2pc,必然加长锁, 系统正常性能一下子干掉9/10,还自带随机抽风。这样搞必然走不通。

另外一部分的高端解法是,CockroachDB, TiDB,各种基于F1的高精尖分布式数据库,彻底替换掉MySQL。公司最重要的部分,可能就是数据部分,完全替换数据库的做法,实际上容易踩坑,并且踩进去还得爬出来,最气的这坑是还是自己挖的。在笔者个人的看法里,一个文件系统的彻底成熟大概要经过5到10年的时间,ex4是差不多经过5年才成熟起来的。对于数据库系统想来也不会差太多。底层数据储存,可以尝试,不建议勇猛。不是这个方法不可行,只是可能需要再等上一段时间。但是从另一角度来说,长期来看,这种方案很可能是最优解法。

重新定义需要解决的问题

分布式环境的著名问题是强CAP不可兼得。我们可以这样想一下这句话,分布式环境下,要不等数据同步一致再提供服务,要么我保证服务,不去管数据同步的问题。因为数据同步必然有时间窗口,所以强CAP 不可兼得。又因为服务可用对互联网公司应该说是最基本的要求。大部分场景下,互联网公司的选择都是强A弱C,追求最终一致性,保证高可用。(这里无意去争辩CAP是否过时,只希望能够表达清楚场景,如果对这里有疑问的话,需要想一下,心中所列反例到底是A还是HA)

任意一个系统,想要达成最终一致性,场景无非是两种。

第一个场景,某动作发生后,后续动作保证完成。

第二个场景,某动作发生后,中间动作硬性无法完成,那么需要回滚操作,并清除之前操作的副作用。

第一个场景非常适合消息队列,消息队列在处理这个场景的时候非常合适,消息队列天然具有编排服务的能力,单事件触发后续的多个服务,后续操作异步完成,不降低系统的吞吐的下限。消息做 at least once 的投递,消息消费对消息幂等,消息的发送机制利用单机事务结合事务消息,是对于这个场景非常优雅的解法。

第二个场景,更接近标准的事务场景,只不过因为跨实例,跨机器,单机事务是不可依靠的。

更确切的说,第二个场景就是分布式事务组件需要解决的问题。

设计与实现

我们基于 SAGAS 来模拟长事务,从而解决来解决上述的第二个场景。

SAGA是上个世纪80年代诞生的思想,当时想解决的问题,更多的是长事务的问题,因为事务过长,实际资源锁的时限也过长,资源损耗严重 。在现有微服务演化的前提下,我们需求的不就是可模拟的类似长事务的行为么,于是SAGAS就非常的适用于我们的场景。

SAGAS简单来说,用短的单机事务拼接长跨机事务,这一组单机事务我们称为事务组。当事务正常时正常处理,事务组执行中间有异常case时,反向补偿整个事务组。我们把事务组想象成一个状态机,那么最终要么在完成状态,要么最终在补偿成功状态。补偿善后完成后,可以认为系统到达最终一致性的状态。

我们依据上述抽象,设计开发落地了我们的分布式事务组件。

系统架构如下图:

分布式事务组件流程如下,当用户请求进入上层服务的时候,在当前线程上下文生成一个唯一的事务ID做标示事务组,rpc组件在上层服务中调用原子层时,会把事务组内所有对远程的原子层的调用,记录为一个事务组,持久化到远端储存层。

事务补偿器运行状态机,找到需要补偿的事务组,多次尝试补偿调用,直至成功为止,事务组补偿组间并行,组内串行,补偿调用经过限流器,防止后端雪崩。

这样,原子层通过事务保证一致性,逻辑层通过记录和补偿服务保证每一次逻辑层作业都会到达最终一致的状态。事务组件请求的耗损仅为持久化远程调用的时间加上初始化事务组的时间。对于业务有即时强一致性的要求的场景,实际上是业务需要一个分布式锁来保证某一属性是不存在一致性窗口的。由于补偿服务本身不含有状态,水平扩展是非常容易的。另一方面来说,因为事务组实际上是通过事务ID标示,我们可以通过事务ID去把整个事务的补偿流程串起来,可视化分析也非常的容易。

总结

1.事务本质还是并发和锁的问题。

2.分布式环境下,一般情况一致性需要为可用性牺牲,保障最终一致性,两种场景,两种手段。

3.基于 sagapattern 模拟长事务来解决分布式事务问题。

4.转转分布式事务组件架构。

参考

1.https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf

2. http://www.bailis.org/papers/hat-vldb2014.pdf

3. Reactive Microservices Architecture Jonas Bonér

原文发布于微信公众号 - 架构之美(beautyArch)

原文发表时间:2016-12-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java面试笔试题

什么是中间件?

计 算机技术迅速发展。从硬件技术看,CPU速度越来越高,处理能力越来越强;从软件技术看,应用程序的规模不断扩大,特别是Internet及WWW的出 现,使计算机...

712
来自专栏架构师之路

秒杀系统架构优化思路

一、秒杀业务为什么难做 1)im系统,例如qq或者微博,每个人都读自己的数据(好友列表、群列表、个人信息); 2)微博系统,每个人读你关注的人的数据,一个人读多...

39410
来自专栏无所事事者爱嘲笑

优秀的前端需要做到什么?

1433
来自专栏WeTest质量开放平台团队的专栏

谷歌大开“吃”戒的产物,Andriod O全球发布

2017年3月26日,谷歌默默的发布了下一代OS的第一个开发者预览版:Android O,毫无悬念这应该就是安卓8.0了!发布之后,用户纷纷表示:我7.0都没升...

562
来自专栏Java技术交流群809340374

主流的消息队列MQ比较,详解MQ的4类应用场景

消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能,成为异步RPC的主要手段之一。

2233
来自专栏Golang语言社区

游戏服务端究竟解决了什么问题?

当讨论到游戏服务端的时候,我们首先想到的会是什么?要回答这个问题,我们需要从游戏服务端的需求起源说起。

1072
来自专栏刘勇刚的专栏

鸟瞰前端 , 再论性能优化

从事前端有 6 年+的时间了,我现在将自己这些年的一个心得体会来个系统性的梳理写成一篇关于性能优化的主题文章,希望对大家有点帮助,也欢迎大家提出各种意见和建议。

3601
来自专栏互扯程序

到底什么是分布式系统,该如何学习

现在是资源共享的时代,同样也是知识分享的时代,如果你觉得本文能学到知识,请把知识与别人分享。

945
来自专栏腾讯移动品质中心TMQ的专栏

完美组合:用例精简+精准测试

一、 为什么要做用例精简和精准测试 1、 测试用例越来越多,测试效率低下 这是因为在目前的快速迭代开发模式下,测试人员需要不停覆盖不断调整的产品逻辑需求,因此测...

19610
来自专栏不止思考

架构设计之「服务隔离」

那什么是「服务隔离」呢? 顾名思义,它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故障发生时,能将问题和影响隔离在某个模块...

903

扫码关注云+社区