分布式事务,一直是实现分布式系统过程中最大的挑战。在只有单个数据源的单服务系统当中,只要这个数据源支持事务,例如大部分关系型数据库,和一些MQ服务,如activeMQ等,我们就可以很容易的实现事务。

  本地事物

  大家可能都知道什么是事务,但是我们还是再来看一下它的定义。事务的概念来自于数据库事务,在数据库事务定义中,事务是一个执行的逻辑单元,它需要提供一个一致、可靠的数据操作。它主要包括下面两个目标:

  当出现任何错误,包括系统宕机、部分失败等,都能保证左右的数据修改都恢复到未修改的状态。

  不同的事务并发放完相同的数据时,提供适当的隔离机制。

  我们常说的ACID其实,其实是某些数据库特有的事务的实现方式,也就是实现了原子性、一致性、隔离性和持久性。

  分布式系统的实现原则

  那么在分布式系统当中,我们应该怎么样去实现事务呢?这就需要从分布式系统的原则说起。分布式系统的实现原则有几种说法,如BASE原理、ACP原理。

  其中ACP是:

  A: 可用性(Availability)

  C: 一致性(Consistency)

  P: 分区容错性(Tolerance of network Partition)

  A和P没什么好说的,就是分布式系统的基本特性,C(一致性)就是指在分布式系统当中,多个节点之间数据的一致性,包括一个节点修改的数据,通过另一个节点访问的时候也能看到;以及当一个操作需要修改多个数据源的数据的时候,多个修改要都能够完成,或者都不完成。

  这里的一致性,我们可以看做是上面说的数据库事务的ACID特性中,原子性、一致性,甚至是隔离性的统一。如果以ACID这4个特性为要求来实现分布式系统,在现实当中是不可能的,其中原子性就没有办法实现。如果一个业务请求,要修改多个数据库中的数据,那么这多个数据库的操作,就无法实现原子性,势必会有一个先后,在第一个数据库上完成以后,再在第二个数据库上完成,那么这期间的一点点时间,就违反了原子性。

  所以,我们往往无法在分布式系统中实现完全的一致性,所以就有了BASE理论。BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,要求实现最终一致性即可。

  其中,Soft state(软状态)是指,在一个业务操作过程中,允许出现一个中间状态,也就是软状态,而不要求原子性那样,要么都完成,要么都不完成。例如在下单的时候,出现一个“正在处理”的状态。由于有这个软状态,那我的一致性,就不要求是强一致性,而是最终一致性,也就是说,只要最终这个请求能处理完,所有的数据状态都是处理完的状态;如果期间出错了,所有的数据也都一致,该失败的失败、该退钱的退钱、该重置的重置。

  分布式事务的实现

  所以,确定了分布式系统的实现原则是最终一致性以后,同时也明确了我们实现分布式事务的原则,也是最终一致性。

  其实,不管是数据库事务的ACID特性,还是分布式事务的最终一致性,其实,都是根据事务的定义和它的两个目标,所采取的不同的实现方式。

  那么我们应该怎么实现这个最终一致性呢?

  单服务的分布式事务

  首先,任何一个分布式系统,总是由一个个的系统组成,也就是一个个的服务,这些服务又可以部署多个。同时,我们的整个系统也需要一定的方式相互作用、相关通信。有时候,我们可以让一个服务直接调用另一个服务的接口(如果有提供的话);还有时候,我们可以让两个服务通过一个MQ之类的消息中间件通信,共同完成一些业务。但是,无论如何,大部分情况下,分布式系统的一个服务总是会访问多个数据源。最典型的例子就是通过MQ接受一个事件,然后出发一些操作,再把结果发送到另一个队列里。

  对于这种每个服务访问多个数据源的情况,其实就是一个最简单的分布式事务的场景。如果大家在网上搜“Spring分布式事务实现”,搜到的结果也都是在说这个场景下的分布式事务实现过程。

  要实现这个事务,首先需要对Spring的事物机制有一定了解。对于这种情况,最简单的就是使用Spring的JTA事务管理。但是,我们知道,JTA事务管理是通过两阶段提交实现的,在很多情况下,它的效率是很低的。因为它在多个数据源修改数据的时候,这些数据一直都处在被锁的状态,知道多个数据源的事务都提交完成,才会释放。

  如果不用JTA,Spring也给我们提供了几种方式,来近似的实现分布式事务(注意这里说的近似)。例如:

  事务同步,也就是提交一个事物的时候,通过Listener等方式通知另一个事务也提交。但是这种情况下,如果第二个事务提交的时候出错了,第一个事物就无法回滚,因为他已经提交完成了。

  链式事务,就是将多个事务,包装在一个链式事务管理器当中,在提交事务的时候,一次提交里面的事务。对于这种实现,也存在上面说的问题。

  还有其他的一些方式,就不过多说明。

  所以,使用Spring在单服务多数据源的情况下,实现分布式事务,实际上没办法完全实现事务的,因为出错的时候不能保证都会滚。那么这时候,就需要再通过其他机制来补充。

  首先就是重试,也就是在出错的时候,重试之前的操作。这在有MQ的时候比较常用,因为一般的MQ服务器,在你读消息以后,处理的时候如果出错了,那么这个读消息的操作不会被提交。那这个消息就会被重新读到,重新出发刚才的操作。这时候,我们就需要考虑这个方法的幂等性,保证在重复消息的时候不会重复处理数据。

  其次,我们需要自己处理一些错误。例如上面的情况,重试几次以后,一直没有成功,那么这时候就需要走失败逻辑。有时候,我们也可以通过一个定时器来检查一定时间内没有完成的失败操作。

  有些情况下,我们还需要考虑其他各种错误,如网络错误、超时,系统宕机等等。

  大家可以试想一下,分布式系统越复杂,它的各种出错的情况就越多,我们需要考虑的补救措施就越多。那这种修修补补的实现分布式事务的最终一致性的做法,始终不是一个好的办法。但是,使用Spring解决单服务的分布式系统,始终是分布式事务实现的基础。我们可以用其他的模式来方便我们解决分布式事务,但是在每个服务当中,我们还是要经常使用事务同步、链式事务等,来实现事务。我们用Spring来保证绝大多数情况下的事务问题,而对于特殊的错误情况,就采用其他的模式来解决。

  分布式事务实现的模式

  刚才说了我们用其他模式来觉得分布式事务问题,那么都有什么模式呢?

  消息驱动(Event Driven)模式

  消息驱动模式是,当某个业务请求需要由多个服务参与完成的时候,这些服务之前不直接通信,而是通过一个MQ中间件来通信。比如对于一个订单支付的请求,接收到支付完成的请求后,通过MQ,通知订单服务去完成订单,订单服务再去通知商品服务去减库存,再通知物流服务去发起物流流程。

  那么,对于每一个服务来说,都需要先从一个队列读取一个消息,完成自己的业务操作,再往另一个队列发送一个消息,这就需要操作一个数据和一个MQ服务器。这也就是上面说的单服务的分布式事务实现。对于这种模式而言,我们用事务同步保证在每个服务中,在大部分情况下都能保证事务。即使偶尔出现网络错误、系统错误等,通过重试就能解决大部分问题。如果重试一直不能解决,那就再处理失败逻辑。

  我们使用这种方式,最重要的,就是对这个消息、和他的处理流程的编排,其次,它也是一种响应式的编程思维。

  事件溯源(Event Sourcing)模式

  Event Sourcing在上面说的消息驱动的基础上,进一步提升事件(也就是之前的消息)的地位,让它成为系统的一等公民。也就是说,怎么的系统不是基于原先那些实体的,而是基于事件的,一个事件就代表一个业务操作和业务数据状态的更改。至于业务数据,我们不需要把它保存在数据库中,即使保存,也只是为了查询数据方便而保存。

  在Event Sourcing模式中,每个服务完成某个逻辑的方式,跟上面说的消息驱动模式差不多,就是对于用户的每个操作,会产生一个事件(可能多个),这个事件会被某个服务的某个处理方法处理,它也有可能再产生其他的事件,再由其他服务处理,直到完成整个业务流程。但是,它跟消息驱动的最大区别就是,在Event Sourcing的服务里,业务状态数据不一定要保存在数据库中,就算保存,出错了也没关系,反正它可以根据Event事件重新生成。所以这个地方的事务,我们只需要保证Event保存成功即可。当然,我们需要其他的机制,方便我们能够重新生成业务数据,而这,一般都是实现Event Sourcing的框架来提供。

  TCC(Try-Confirm-Cancel)模式

  除了上面说的通过一个中间价关联不同的服务,在有些分布式系统当中,我们的不同的服务可以直接通信,例如Spring Cloud微服务框架就提供Rest方式访问别的服务。那么这时候,就相当于,我的一个服务除了访问自己的数据库以外,还要访问别的服务,这里的这个服务就可以当做是一个数据库。我们可能要调用别的服务的某个接口完成一些业务。

  在这种情况下,一个服务提供的接口,不可能实现事务,也就是先操作数据,再Commit,如果出错了再Rollback。但是,我们可以借鉴事务的这种处理思路,来自己提供类似事务的方法,这就是TCC模式。一个事物是通过Do-Commit/Rollback来实现的,在TCC模式中,是通过给每一个服务间调用的操作接口,提供一套Try-Confirm/Cancel接口。

  还是举一个例子,就是用户下单以后支付完成。支付完成的时候由先订单服务处理,然后调用商品服务去减库存。大家用Spring Cloud的话,可能就在商品服务里写一个接口直接做减库存的操作,但是在TCC模式下,我们需要3个接口。首先是减库存的Try接口,在这里,我们要检查业务数据的状态、检查商品库存够不够,然后做资源的预留,也就是在某个字段上设置预留的状态。然后在Confirm接口里,完成库存减1的操作。在Cancel接口里,把之前预留的字段重置。

  这可能听着有点繁琐,感觉可以一次完成的事情,为什么要分成2步,首先这么做是为了能够在出错的时候正确的重置库存数据,其次这个预留操作跟Confirm操作是两个请求,中间可能会有其他并发请求。从理论上说,只要我们在Try接口里面预留资源的逻辑是正确的,那么,即使Confirm的时候出错了,我也可以通过重试Confirm请求来完成

  使用数据库保存事务状态

  这其实不是一种模式,只是一种方式。例如在TCC模式下,在准备调用Confirm接口的时候,目标服务突然宕机了,或者发起请求的服务突然宕机或出错了,导致这个Confirm请求一直没有被调用。那么,在系统恢复以后,我该怎么完成之前的事务呢?除了上面说的用定时器定期检查未完成的操作以外(需要能够通过某种数据状态判断业务没有执行完成后),我们还可以用数据库来记录事务的运行状态。

  例如在TCC模式中,每当一个服务A要使用TCC模式调用另一个服务B的时候,服务A将这个TCC的事务状态写到数据库中,根据具体实现,可能是在调用前记录当前事务的状态,调用完成再保存该调用的参数和结果状态,这个事务完成以后(也就是调用完Confirm,或Cancel以后),再更新成完成的状态。那么,通过合理的设计,我们就能在各种出错情况下,保证能继续完成这个事务,或取消这个事务。

  总结

  总之,对于分布式事务来说,没有一个简单的像本地事物一样的实现方式,我们总是需要根据分布式系统的设计,根据业务需求,选择某种方式来保证数据的一致性。而且,在实现分布式事务的过程中,业务流程的设计也至关重要,不管是用TCC、消息驱动、还是EventSourcing。分布式系统的业务流程,实际上就是一个完备的状态机,这个状态机是否包含了所有的事件,是否包含了所有的业务路径,包括正常的、异常的,合理的设计业务流程,才能更好地实现分布式事务。

Spring分布式事务实现概览的更多相关文章

  1. fescar分布式事务(概览)

    1. fescar分布式事务(概览) 1.1. 概述   Fescar 是 阿里巴巴 开源的 分布式事务中间件,以 高效 并且对业务0 侵入 的方式,解决 微服务 场景下面临的分布式事务问题. 1.2 ...

  2. Spring分布式事务

    [如何实现XA式.非XA式Spring分布式事务] [http://www.importnew.com/15812.html] 在JavaWorld大会上,来自SpringSource的David S ...

  3. Spring分布式事务实现

    分布式事务是指操作多个数据库之间的事务,spring的org.springframework.transaction.jta.JtaTransactionManager,提供了分布式事务支持.如果使用 ...

  4. spring分布式事务学习笔记

    最近项目中使用了分布式事务,本文及接下来两篇文章总结一下在项目中学到的知识. 分布式事务对性能有一定的影响,所以不是最佳的解决方案,能通过设计避免最好尽量避免. 分布式事务(Distributed t ...

  5. 如何实现XA式、非XA式Spring分布式事务

    Spring应用的几种事务处理机制 Java Transaction API和XA协议是Spring常用的分布式事务机制,不过你可以选择选择其他的实现方式.理想的实现取决于你的应用程序使用何种资源,你 ...

  6. 非XA式Spring分布式事务

    Spring应用的几种事务处理机制 Java Transaction API和XA协议是Spring常用的分布式事务机制,不过你可以选择选择其他的实现方式.理想的实现取决于你的应用程序使用何种资源,你 ...

  7. Spring分布式事务实现(适用于spring-tx 2.5)

    http://log-cd.iteye.com/blog/807607 分布式事务是指操作多个数据库之间的事务,spring的org.springframework.transaction.jta.J ...

  8. spring分布式事务控制

    应用场景问题描述解决方法多数据源配置单元测试第一种方法:最大努力一次提交模式第二种方法:最大努力一次提交模式 但使用ChainedTransactionManagerChainedTransactio ...

  9. spring分布式事务学习笔记(1)

    此文已由作者夏昀授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 分布式事务对性能有一定的影响,所以不是最佳的解决方案,能通过设计避免最好尽量避免. 分布式事务(Distrib ...

随机推荐

  1. FileReader 获取图片base64数据流 并 生成图片

    <?php if(isset($_GET['upload']) && $_GET['upload'] == 'img'){ if(isset($_GET['stream_type ...

  2. 安装虚拟环境virtualenvwrapper和django

    以下操作在windows平台进行 1.安装虚拟环境virtualenvwrapper 首先需要安装python管理工具pip,安装完python3.7之后自带了pip,可通过where pip查看管理 ...

  3. Java版 家政服务 社区服务 家装服务平台 源码 有案例 可定制

    产品说明: 家装服务平台.社区服务平台.服务类型的平台--公司成熟产品 包括工匠注册.资质认证.发布服务产品.会员注册.预约服务.工匠定价.执行服务.服务完毕填写工作日志上传现场照片.会员确认服务.返 ...

  4. Android之数据存储之SharedPreferences

    SharedPreferences是以键值对形式存储数据,主要用于记录系统的设置,如飞行模式是否开启,声音大小的值等.//SharedPreferences方式保存到xml文件SharedPrefer ...

  5. 更改 centos 7的源为 阿里源

    阿里源的网址在这里:http://mirrors.aliyun.com/repo/ 一.进入源文件存放目录 cd /etc/yum.repos.d 二.安装基本源: 1.如果要备份原来的源文件 sud ...

  6. vS+QT生成.pro文件

  7. 2015,3,10 2(南阳理工ACM)

    描述有一个整型偶数n(2<= n <=10000),你要做的是:先把1到n中的所有奇数从小到大输出,再把所有的偶数从小到大输出.   输入 第一行有一个整数i(2<=i<30) ...

  8. vim改善生活的几个插件

    vim改善生活的几个插件 http://www.cnblogs.com/lovesaber/archive/2012/01/06/2315343.html

  9. YII框架的使用

    YII框架的使用 spit: 吐痰,吐口水, 过去式: spat spat: 本身也可以作为一个单词, 意思是: "小打小闹""小的吵闹""小争吵&q ...

  10. 【做题】51NOD1753 相似子串——哈希

    题意:两个字符串相似定义为: 1.两个字符串长度相等 2.两个字符串对应位置上至多有一个位置所对应的字符不相同 给定一个字符串\(s\),\(T\)次询问两个子串在给定的规则下是否相似.给定的规则指每 ...