聊聊分布式事务,再说说解决方案

前言

最近好久没有写博客了,一方面是由于公司事情最近比较忙,另一方面是由于在进行 CAP 的下一阶段的开发工做,不过目前已经告一段落了。html

接下来仍是开始咱们今天的话题,说说分布式事务,或者说是我眼中的分布式事务,由于每一个人可能对其的理解都不同。java

分布式事务是企业集成中的一个技术难点,也是每个分布式系统架构中都会涉及到的一个东西,特别是在微服务架构中,几乎能够说是没法避免,本文就分布式事务来简单聊一下。git

数据库事务

在说分布式事务以前,咱们先从数据库事务提及。 数据库事务可能你们都很熟悉,在开发过程当中也会常用到。可是即便如此,可能对于一些细节问题,不少人仍然不清楚。好比不少人都知道数据库事务的几个特性:原子性(Atomicity )、一致性( Consistency )、隔离性或独立性( Isolation)和持久性(Durabilily),简称就是ACID。可是再往下好比问到隔离性指的是什么的时候可能就不知道了,或者是知道隔离性是什么可是再问到数据库实现隔离的都有哪些级别,或者是每一个级别他们有什么区别的时候可能就不知道了。程序员

本文并不打算介绍这些数据库事务的这些东西,有兴趣能够搜索一下相关资料。不过有一个知识点咱们须要了解,就是假如数据库在提交事务的时候忽然断电,那么它是怎么样恢复的呢? 为何要提到这个知识点呢? 由于分布式系统的核心就是处理各类异常状况,这也是分布式系统复杂的地方,由于分布式的网络环境很复杂,这种“断电”故障要比单机多不少,因此咱们在作分布式系统的时候,最早考虑的就是这种状况。这些异常可能有 机器宕机、网络异常、消息丢失、消息乱序、数据错误、不可靠的TCP、存储数据丢失、其余异常等等...github

咱们接着说本地事务数据库断电的这种状况,它是怎么保证数据一致性的呢?咱们使用SQL Server来举例,咱们知道咱们在使用 SQL Server 数据库是由两个文件组成的,一个数据库文件和一个日志文件,一般状况下,日志文件都要比数据库文件大不少。数据库进行任何写入操做的时候都是要先写日志的,一样的道理,咱们在执行事务的时候数据库首先会记录下这个事务的redo操做日志,而后才开始真正操做数据库,在操做以前首先会把日志文件写入磁盘,那么当忽然断电的时候,即便操做没有完成,在从新启动数据库时候,数据库会根据当前数据的状况进行undo回滚或者是redo前滚,这样就保证了数据的强一致性。数据库

接着,咱们就说一下分布式事务。编程

分布式理论

当咱们的单个数据库的性能产生瓶颈的时候,咱们可能会对数据库进行分区,这里所说的分区指的是物理分区,分区以后可能不一样的库就处于不一样的服务器上了,这个时候单个数据库的ACID已经不能适应这种状况了,而在这种ACID的集群环境下,再想保证集群的ACID几乎是很难达到,或者即便能达到那么效率和性能会大幅降低,最为关键的是再很难扩展新的分区了,这个时候若是再追求集群的ACID会致使咱们的系统变得不好,这时咱们就须要引入一个新的理论原则来适应这种集群的状况,就是 CAP 原则或者叫CAP定理,那么CAP定理指的是什么呢?服务器

CAP定理

CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务没法同时知足一下3个属性:网络

  • 一致性(Consistency) : 客户端知道一系列的操做都会同时发生(生效)
  • 可用性(Availability) : 每一个操做都必须以可预期的响应结束
  • 分区容错性(Partition tolerance) : 即便出现单个组件没法可用,操做依然能够完成

具体地讲在分布式系统中,在任何数据库设计中,一个Web应用至多只能同时支持上面的两个属性。显然,任何横向扩展策略都要依赖于数据分区。所以,设计人员必须在一致性与可用性之间作出选择。架构

这个定理在迄今为止的分布式系统中都是适用的! 为何这么说呢?

这个时候有同窗可能会把数据库的2PC(两阶段提交)搬出来讲话了。OK,咱们就来看一下数据库的两阶段提交。

对数据库分布式事务有了解的同窗必定知道数据库支持的2PC,又叫作 XA Transactions。

MySQL从5.5版本开始支持,SQL Server 2005 开始支持,Oracle 7 开始支持。

其中,XA 是一个两阶段提交协议,该协议分为如下两个阶段:

  • 第一阶段:事务协调器要求每一个涉及到事务的数据库预提交(precommit)此操做,并反映是否能够提交.
  • 第二阶段:事务协调器要求每一个数据库提交数据。

其中,若是有任何一个数据库否决这次提交,那么全部数据库都会被要求回滚它们在此事务中的那部分信息。这样作的缺陷是什么呢? 咋看之下咱们能够在数据库分区之间得到一致性。

若是CAP 定理是对的,那么它必定会影响到可用性。

若是说系统的可用性表明的是执行某项操做相关全部组件的可用性的和。那么在两阶段提交的过程当中,可用性就表明了涉及到的每个数据库中可用性的和。咱们假设两阶段提交的过程当中每个数据库都具备99.9%的可用性,那么若是两阶段提交涉及到两个数据库,这个结果就是99.8%。根据系统可用性计算公式,假设每月43200分钟,99.9%的可用性就是43157分钟, 99.8%的可用性就是43114分钟,至关于每月的宕机时间增长了43分钟。

以上,能够验证出来,CAP定理从理论上来说是正确的,CAP咱们先看到这里,等会再接着说。

BASE理论

在分布式系统中,咱们每每追求的是可用性,它的重要程序比一致性要高,那么如何实现高可用性呢? 前人已经给咱们提出来了另一个理论,就是BASE理论,它是用来对CAP定理进行进一步扩充的。BASE理论指的是:

  • Basically Available(基本可用)
  • Soft state(软状态)
  • Eventually consistent(最终一致性)

BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:咱们没法作到强一致,但每一个应用均可以根据自身的业务特色,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

有了以上理论以后,咱们来看一下分布式事务的问题。

分布式事务

在分布式系统中,要实现分布式事务,无外乎那几种解决方案。

1、两阶段提交(2PC)

和上一节中提到的数据库XA事务同样,两阶段提交就是使用XA协议的原理,咱们能够从下面这个图的流程来很容易的看出中间的一些好比commit和abort的细节。

两阶段提交这种解决方案属于牺牲了一部分可用性来换取的一致性。在实现方面,在 .NET 中,能够借助 TransactionScop 提供的 API 来编程实现分布式系统中的两阶段提交,好比WCF中就有实现这部分功能。不过在多服务器之间,须要依赖于DTC来完成事务一致性,Windows下微软搞的有MSDTC服务,Linux下就比较悲剧了。

另外说一句,TransactionScop 默认不能用于异步方法之间事务一致,由于事务上下文是存储于当前线程中的,因此若是是在异步方法,须要显式的传递事务上下文。

优势: 尽可能保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能100%保证强一致)

缺点: 实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景,若是分布式系统跨接口调用,目前 .NET 界尚未实现方案。

2、补偿事务(TCC)

TCC 其实就是采用的补偿机制,其核心思想是:针对每一个操做,都要注册一个与其对应的确认和补偿(撤销)操做。它分为三个阶段:

  • Try 阶段主要是对业务系统作检测及资源预留

  • Confirm 阶段主要是对业务系统作确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm必定成功。

  • Cancel 阶段主要是在业务执行错误,须要回滚的状态下执行的业务取消,预留资源释放。

举个例子,假入 Bob 要向 Smith 转帐,思路大概是:
咱们有一个本地方法,里面依次调用
一、首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。
二、在 Confirm 阶段,执行远程调用的转帐的操做,转帐成功进行解冻。
三、若是第2步执行成功,那么转帐成功,若是第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。

优势: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些

缺点: 缺点仍是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,因此须要程序员在实现的时候多写不少补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。

3、本地消息表(异步确保)

本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分红本地事务进行处理,这种思路是来源于ebay。咱们能够从下面的流程图中看出其中的一些细节:

基本思路就是:

消息生产方,须要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。而后消息会通过MQ发送到消息的消费方。若是消息发送失败,会进行重试发送。

消息消费方,须要处理这个消息,并完成本身的业务逻辑。此时若是本地事务处理成功,代表已经处理成功了,若是处理失败,那么就会重试执行。若是是业务上面的失败,能够给生产方发送一个业务补偿消息,通知生产方进行回滚等操做。

生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。若是有靠谱的自动对帐补帐逻辑,这种方案仍是很是实用的。

这种方案遵循BASE理论,采用的是最终一致性,笔者认为是这几种方案里面比较适合实际业务场景的,即不会出现像2PC那样复杂的实现(当调用链很长的时候,2PC的可用性是很是低的),也不会像TCC那样可能出现确认或者回滚不了的状况。

优势: 一种很是经典的实现,避免了分布式事务,实现了最终一致性。在 .NET中 有现成的解决方案。

缺点: 消息表会耦合到业务系统中,若是没有封装好的解决方案,会有不少杂活须要处理。

4、MQ 事务消息

有一些第三方的MQ是支持事务消息的,好比RocketMQ,他们支持事务消息的方式也是相似于采用的二阶段提交,可是市面上一些主流的MQ都是不支持事务消息的,好比 RabbitMQ 和 Kafka 都不支持。

以阿里的 RocketMQ 中间件为例,其思路大体为:

第一阶段Prepared消息,会拿到消息的地址。
第二阶段执行本地事务,第三阶段经过第一阶段拿到的地址去访问消息,并修改状态。

也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。若是确认消息发送失败了RocketMQ会按期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,因此生产方须要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚仍是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

遗憾的是,RocketMQ并无 .NET 客户端。有关 RocketMQ的更多消息,你们能够查看这篇博客

优势: 实现了最终一致性,不须要依赖本地数据库事务。

缺点: 实现难度大,主流MQ不支持,没有.NET客户端,RocketMQ事务消息部分代码也未开源。

5、Sagas 事务模型

Saga事务模型又叫作长时间运行的事务(Long-running-transaction), 它是由普林斯顿大学的H.Garcia-Molina等人提出,它描述的是另一种在没有两阶段提交的的状况下解决分布式系统中复杂的业务事务问题。你能够在这里看到 Sagas 相关论文。

咱们这里说的是一种基于 Sagas 机制的工做流事务模型,这个模型的相关理论目前来讲仍是比较新的,以致于百度上几乎没有什么相关资料。

该模型其核心思想就是拆分分布式系统中的长事务为多个短事务,或者叫多个本地事务,而后由 Sagas 工做流引擎负责协调,若是整个流程正常结束,那么就算是业务成功完成,若是在这过程当中实现失败,那么Sagas工做流引擎就会以相反的顺序调用补偿操做,从新进行业务回滚。

好比咱们一次关于购买旅游套餐业务操做涉及到三个操做,他们分别是预约车辆,预约宾馆,预约机票,他们分别属于三个不一样的远程接口。可能从咱们程序的角度来讲他们不属于一个事务,可是从业务角度来讲是属于同一个事务的。

他们的执行顺序如上图所示,因此当发生失败时,会依次进行取消的补偿操做。

由于长事务被拆分了不少个业务流,因此 Sagas 事务模型最重要的一个部件就是工做流或者你也能够叫流程管理器(Process Manager),工做流引擎和Process Manager虽然不是同一个东西,可是在这里,他们的职责是相同的。在选择工做流引擎以后,最终的代码也许看起来是这样的

SagaBuilder saga = SagaBuilder.newSaga("trip")
        .activity("Reserve car", ReserveCarAdapter.class) 
        .compensationActivity("Cancel car", CancelCarAdapter.class) 
        .activity("Book hotel", BookHotelAdapter.class) 
        .compensationActivity("Cancel hotel", CancelHotelAdapter.class) 
        .activity("Book flight", BookFlightAdapter.class) 
        .compensationActivity("Cancel flight", CancelFlightAdapter.class) 
        .end()
        .triggerCompensationOnAnyError();

camunda.getRepositoryService().createDeployment() 
        .addModelInstance(saga.getModel()) 
        .deploy();

这里有一个 C# 相关示例,有兴趣的同窗能够看一下。

优缺点这里咱们就不说了,由于这个理论比较新,目前市面上尚未什么解决方案,即便是 Java 领域,我也没有搜索的太多有用的信息。

分布式事务解决方案:CAP

上面介绍的那些分布式事务的处理方案你在其余地方或许也能够看到,可是并无相关的实际代码或者是开源代码,因此算不上什么干货,下面就放干货了。

在 .NET 领域,彷佛没有什么现成的关于分布式事务的解决方案,或者说是有但未开源。具笔者了解,有一些公司内部实际上是有这种解决方案的,可是也是做为公司的一个核心产品之一,并未开源...

鉴于以上缘由,因此博主就打算本身写一个而且开源出来,因此从17年初就开始作这个事情,而后花了大半年的时间在一直不断完善,就是下面这个 CAP。

Github CAP:这里的 CAP 就不是 CAP 理论了,而是一个 .NET 分布式事务解决方案的名字。

详细介绍:
http://www.cnblogs.com/savorboard/p/cap.html
相关文档:
http://www.cnblogs.com/savorboard/p/cap-document.html

夸张的是,这个解决方案是具备可视化界面(Dashboard)的,你能够很方面的看到哪些消息执行成功,哪些消息执行失败,究竟是发送失败仍是处理失败,一眼便知。

最夸张的是,这个解决方案的可视化界面还提供了实时动态图表,这样不但能够看到实时的消息发送及处理状况,连当前的系统处理消息的速度均可以看到,还能够看到过去24小时内的历史消息吞吐量。

最最夸张的是,这个解决方案的还帮你集成了 Consul 作分布式节点发现和注册还有心跳检查,你随时能够看到其余的节点的情况。

最最最夸张的是,你觉得你看其余节点的数据要登陆到其余节点的Dashboard控制台看?错了,你随便打开其中任意一个节点的Dashboard,点一下就能够切换到你想看的节点的控制台界面了,就像你看本地的数据同样,他们是彻底去中心化的。

你觉得这些就够了?不,远远不止:

  • CAP 同时支持 RabbitMQ,Kafka 等消息队列
  • CAP 同时支持 SQL Server, MySql, PostgreSql 等数据库
  • CAP Dashboard 同时支持中文和英文界面双语言,妈妈不再用担忧我看不懂了
  • CAP 提供了丰富的接口能够供扩展,什么序列化了,自定义处理了,自定义发送了通通不在话下
  • CAP 基于MIT开源,你能够尽管拿去作二次开发。(记得保留MIT的License)

这下你觉得我说完了? 不!

你彻底能够把 CAP 当作一个 EventBus 来使用,CAP具备优秀的消息处理能力,不要担忧瓶颈会在CAP,那是永远不可能, 由于你随时能够在配置中指定CAP处理的消息使用的进程数, 只要你的数据库配置足够高...

说了这么多,口干舌燥的,你不 Star 一下给个精神上的支持说不过去吧? ^_^

2号传送门: https://github.com/dotnetcore/CAP

不 Star 也不要紧,我选择原谅你~

总结

经过本文咱们了解到两个分布式系统的理论,他们分别是CAP和BASE 理论,同时咱们也总结并对比了几种分布式分解方案的优缺点,分布式事务自己是一个技术难题,是没有一种完美的方案应对全部场景的,具体仍是要根据业务场景去抉择吧。 而后咱们介绍了一种基于本地消息的的分布式事务解决方案CAP。

若是你以为本篇文章对您有帮助的话,感谢您的【推荐】。

若是你对 .NET Core 有兴趣的话能够关注我,我会按期的在博客分享个人学习心得。


本文地址:http://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html
做者博客:Savorboard 欢迎转载,请在明显位置给出出处及连接

相关文章
相关标签/搜索