【微服务之分布式事务】分布式事务

34 篇文章 3 订阅
13 篇文章 1 订阅

分布式事务与分布式锁的区别:

分布式锁解决的是分布式资源抢占的问题;分布式事务和本地事务是解决流程化提交问题。

事务简介

事务(Transaction)是操作数据库中某个数据项的一个程序执行单元(unit)。
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
事务的四个特征:
1、Atomic原子性
事务必须是一个原子的操作序列单元,事务中包含的各项操作在一次执行过程中,要么全部执行成功,要么全部不执行,任何一项失败,整个事务回滚,只有全部都执行成功,整个事务才算成功。

2、Consistency一致性
事务的执行不能破坏数据库数据的完整性和一致性,事务在执行之前和之后,数据库都必须处于一致性状态。

3、Isolation隔离性
在并发环境中,并发的事务是相互隔离的,一个事务的执行不能被其他事务干扰。即不同的事务并发操纵相同的数据时,每个事务都有各自完整的数据空间,即一个事务内部的操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能相互干扰。

4、Durability持久性
持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中对应数据的状态变更就应该是永久性的。

即使发生系统崩溃或机器宕机,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束时的状态。

比方说:一个人买东西的时候需要记录在账本上,即使老板忘记了那也有据可查。

MySQL的本地事务实现方案

大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务(Local Transaction)。本地事务的ACID特性是数据库直接提供支持。

了解过MySQL事务的同学,就会知道,为了达成本地事务,MySQL做了很多的工作,比如回滚日志,重做日志,MVCC,读写锁等。

以MySQL 的InnoDB (InnoDB 是 MySQL 的一个存储引擎)为例,介绍一下单一数据库的事务实现原理。

InnoDB 是通过 日志和锁 来保证的事务的 ACID特性,具体如下:

(1)通过数据库锁的机制,保障事务的隔离性;
(2)通过 Redo Log(重做日志)来,保障事务的持久性;
(3)通过 Undo Log (撤销日志)来,保障事务的原子性;
(4)通过 Undo Log (撤销日志)来,保障事务的一致性;

Undo Log 如何保障事务的原子性呢?

具体的方式为:在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为 Undo Log),然后进行数据的修改。如果出现了错误或者用户执行了 Rollback 语句,系统可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。

Redo Log如何保障事务的持久性呢?

具体的方式为:Redo Log 记录的是新数据的备份(和 Undo Log 相反)。在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可以根据 Redo Log 的内容,将所有数据恢复到崩溃之前的状态。

脏读、幻读、不可重复读

在多个事务并发操作时,数据库中会出现下面三种问题:脏读,幻读,不可重复读。

脏读

事务A读到了事务B还未提交的数据:

幻读(Phantom Read)

事务A进行范围查询时,事务B中新增了满足该范围条件的记录,当事务A再次按该条件进行范围查询,会查到在事务B中提交的新的满足条件的记录(幻行 Phantom Row)。

不可重复读(Unrepeatable Read**)**

事务A在读取某些数据后,再次读取该数据,发现读出的该数据已经在事务B中发生了变更或删除。

幻读和不可重复度的区别:

幻读:在同一事务中,相同条件下,两次查询出来的 记录数 不一样; 不可重复读:在同一事务中,相同条件下,两次查询出来的 数据 不一样;

事务的隔离级别

MySQL事务隔离级别:https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html

事务的四个隔离级别:

未提交读(READ UNCOMMITTED):所有事务都可以看到其他事务未提交的修改。一般很少使用;
提交读(READ COMMITTED):Oracle默认隔离级别,事务之间只能看到彼此已提交的变更修改;
可重复读(REPEATABLE READ):MySQL默认隔离级别,同一事务中的多次查询会看到相同的数据行;可以解决不可重复读,但可能出现幻读;
可串行化(SERIALIZABLE):最高的隔离级别,事务串行的执行,前一个事务执行完,后面的事务会执行。读取每条数据都会加锁,会导致大量的超时和锁争用问题;
在这里插入图片描述

问:如何保证 REPEATABLE READ 级别绝对不产生幻读?

答:在SQL中加入 for update (排他锁) 或 lock in share mode (共享锁)语句实现。就是锁住了可能造成幻读的数据,阻止数据的写入操作。

分布式事务的基本概念

分布式环境的事务复杂性

当本地事务要扩展到分布式时,它的复杂性进一步增加了。

存储端的多样性。
首先就是存储端的多样性。本地事务的情况下,所有数据都会落到同一个DB中,但是,在分布式的情况下,就会出现数据可能要落到多个DB,或者还会落到Redis,落到MQ等中。

存储端多样性, 如下图所示:

在这里插入图片描述

事务链路的延展性

本地事务的情况下,通常所有事务相关的业务操作,会被我们封装到一个Service方法中。而在分布式的情况下,请求链路被延展,拉长,一个操作会被拆分成多个服务,它们呈现线状或网状,依靠网络通信构建成一个整体。在这种情况下,事务无疑变得更复杂。

事务链路延展性, 如下图所示:
在这里插入图片描述
基于上述两个复杂性,期望有一个统一的分布式事务方案,能够像本地事务一样,以几乎无侵入的方式,满足各种存储介质,各种复杂链路,是不现实的。
至少,在当前,还没有一个十分成熟的解决方案。所以,一般情况下,在分布式下,事务会被拆分解决,并根据不同的情况,采用不同的解决方案。

典型的分布式事务场景:

1. 跨库事务

跨库事务指的是,一个应用某个功能需要操作多个库,不同的库中存储不同的业务数据。笔者见过一个相对比较复杂的业务,一个业务中同时操作了9个库。

下图演示了一个服务同时操作2个库的情况:
在这里插入图片描述

2. 分库分表

通常一个库数据量比较大或者预期未来的数据量比较大,都会进行水平拆分,也就是分库分表。

如下图,将数据库B拆分成了2个库:
在这里插入图片描述
对于分库分表的情况,一般开发人员都会使用一些数据库中间件来降低sql操作的复杂性。

如,对于sql:insert into user(id,name) values (1,“tianshouzhi”),(2,“wangxiaoxiao”)。这条sql是操作单库的语法,单库情况下,可以保证事务的一致性。

但是由于现在进行了分库分表,开发人员希望将1号记录插入分库1,2号记录插入分库2。所以数据库中间件要将其改写为2条sql,分别插入两个不同的分库,此时要保证两个库要不都成功,要不都失败,因此基本上所有的数据库中间件都面临着分布式事务的问题。

3. 微服务化

微服务架构是目前一个比较一个比较火的概念。例如上面笔者提到的一个案例,某个应用同时操作了9个库,这样的应用业务逻辑必然非常复杂,对于开发人员是极大的挑战,应该拆分成不同的独立服务,以简化业务逻辑。拆分后,独立服务之间通过RPC框架来进行远程调用,实现彼此的通信。下图演示了一个3个服务之间彼此调用的架构:

在这里插入图片描述
Service A完成某个功能需要直接操作数据库,同时需要调用Service B和Service C,而Service B又同时操作了2个数据库,Service C也操作了一个库。需要保证这些跨服务的对多个数据库的操作要不都成功,要不都失败,实际上这可能是最典型的分布式事务场景。

分布式事务实现方案必须要考虑性能的问题,如果为了严格保证ACID特性,导致性能严重下降,那么对于一些要求快速响应的业务,是无法接受的。

CAP定理

分布式事务的理论基础

数据库事务ACID 四大特性,无法满足分布式事务的实际需求,这个时候又有一些新的大牛提出一些新的理论。

CAP定理

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

  • 一致性(Consistency) : 客户端知道一系列的操作都会同时发生(生效)
  • 可用性(Availability) : 每个操作都必须以可预期的响应结束
  • 分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成
    具体地讲在分布式系统中,一个Web应用至多只能同时支持上面的两个属性。因此,设计人员必须在一致性与可用性之间做出选择。
    2000年7月Eric Brewer教授仅仅提出来的是一个猜想,2年后,麻省理工学院的Seth Gilbert和Nancy Lynch从理论上证明了CAP理论,并且而一个分布式系统最多只能满足CAP中的2项。之后,CAP理论正式成为分布式计算领域的公认定理。
    在这里插入图片描述

1、一致性

数据一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,不能存在中间状态。
分布式环境中,一致性是指多个副本之间能否保持一致的特性。在一致性的需求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系的数据仍然处理一致的状态。
例如对于电商系统用户下单操作,库存减少、用户资金账户扣减、积分增加等操作必须在用户下单操作完成后必须是一致的。不能出现类似于库存已经减少,而用户资金账户尚未扣减,积分也未增加的情况。如果出现了这种情况,那么就认为是不一致的。
数据一致性分为强一致性、弱一致性、最终一致性。
如果的确能像上面描述的那样时刻保证客户端看到的数据都是一致的,那么称之为强一致性。
如果允许存在中间状态,只要求经过一段时间后,数据最终是一致的,则称之为最终一致性。
此外,如果允许存在部分数据不一致,那么就称之为弱一致性。

2、可用性

系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。

两个度量的维度:

(1)有限时间内
对于用户的一个操作请求,系统必须能够在指定的时间(响应时间)内返回对应的处理结果,如果超过了这个时间范围,那么系统就被认为是不可用的。即这个响应时间必须在一个合理的值内,不让用户感到失望。

试想,如果一个下单操作,为了保证分布式事务的一致性,需要10分钟才能处理完,那么用户显然是无法忍受的。

(2)返回正常结果
要求系统在完成对用户请求的处理后,返回一个正常的响应结果。正常的响应结果通常能够明确地反映出对请求的处理结果,即成功或失败,而不是一个让用户感到困惑的返回结果。比如返回一个系统错误如OutOfMemory,则认为系统是不可用的。

“返回结果”是可用性的另一个非常重要的指标,它要求系统在完成对用户请求的处理后,返回一个正常的响应结果,不论这个结果是成功还是失败。

3、分区容错性

即分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。

网络分区,是指分布式系统中,不同的节点分布在不同的子网络(机房/异地网络)中,由于一些特殊的原因导致这些子网络之间出现网络不连通的状态,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干孤立的区域。组成一个分布式系统的每个节点的加入与退出都可以看做是一个特殊的网络分区。

分布式事务解决方案

  • XA(我们常见的2PC/3PC协议,就是XA的一种实现)请添加图片描述

XA规范(XA Specification) 是X/OPEN 提出的分布式事务处理规范。XA则规范了TM与RM之间的通信接口,在TM与多个RM之间形成一个双向通信桥梁,从而在多个数据库资源下保证ACID四个特性。目前知名的数据库,如Oracle, DB2,mysql等,都是实现了XA接口的,都可以作为RM。XA是数据库的分布式事务,强一致性,在整个过程中,数据一张锁住状态,即从prepare到commit、rollback的整个过程中,TM一直把持折数据库的锁,如果有其他人要修改数据库的该条数据,就必须等待锁的释放,存在长事务风险。
2PC/3PC协议
两阶段提交(2PC)协议是XA规范定义的 数据一致性协议。
三阶段提交(3PC)协议对 2PC协议的一种扩展。
我们绝大多数java应用都是spring应用:
可以使用Atomikos分布式事务实现
spring事务管理器的顶级抽象是PlatformTransactionManager接口,其提供了个重要的实现类

  • DataSourceTransactionManager:用于实现本地事务
  • JTATransactionManager:用于实现分布式事务
    显然,在这里,我们需要配置的是JTATransactionManager。
public class JTAService {  
    @Autowired   
    private UserMapper userMapper;//操作db_user库   
    @Autowired  
    private AccountMapper accountMapper;//操作db_account库  

    @Transactional   
    public void insert() {    
        User user = new User();     
        user.setName("wangxiaoxiao");     
        userMapper.insert(user);  
        //模拟异常,spring回滚后,db_user库中user表中也不会插入记录     
        Account account = new Account();     
        account.setUserId(user.getId());    
        account.setMoney(123456789);    
        accountMapper.insert(account); 
    }
}

- SAGA模型:

SAGA模型的核心思想是,通过某种方案(一般是通知),将分布式事务转化为本地事务,从而降低问题的复杂性。本地消息表和半消息都属于SAGA的解决方案,但是还是依赖于消息系统的CAP。SAGA可以看做一个异步的、利用队列实现的补偿事务。这样的SAGA事务模型,是牺牲了一定的隔离性和一致性的,但是提高了long-running事务的可用性。

在DB业务表中插入数据。
在DB消息表中插入数据。
异步将消息表中的消息发送到MQ,收到ack后,删除消息表中的消息。
异步将消息表中的消息发送到MQ,收到ack后,删除消息表中的消息。
这种方案的缺陷是依赖于数据库磁盘IO,对于吞吐量很大的web应用不适合

  • 半消息/最终一致性(RocketMQ):
    在这里插入图片描述
    为什么要回调轮询呢?
    如果执行本地事务过程中,执行端挂掉,或者超时,MQ服务器端将不停的询问producer来获取事务状态;

如上,半消息机制的一个问题是:要求业务方提供查询消息状态接口,对业务方依然有较大的侵入性。适合互联网,比如阿里的 RocketMQ 中间件

- TCC(Try、Confirm、Cancel)

TCC(Try-Confirm-Cancel)的概念来源于 Pat Helland 发表的一篇名为“Life beyond Distributed Transactions:an Apostate’s Opinion”的论文。

TCC(Try-Confirm-Cancel)分布式事务模型相对于 XA 等传统模型,其特征在于它不依赖资源管理器(RM)对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务。
在这里插入图片描述
TCC与2PC(两阶段提交)协议的区别:TCC位于业务服务层而不是资源层,TCC没有单独准备阶段,Try操作兼备资源操作与准备的能力,TCC中Try操作可以灵活的选择业务资源,锁定粒度。TCC的开发成本比2PC高。实际上TCC也属于两阶段操作,但是TCC不等同于2PC操作。

最大努力通知型

最大努力通知事务主要用于外部系统,因为外部的网络环境更加复杂和不可信,所以只能尽最大努力去通知实现数据最终一致性,比如充值平台与运营商、支付对接、商户通知等等跨平台、跨企业的系统间业务交互场景;
而异步确保型事务主要适用于内部系统的数据最终一致性保障,因为内部相对比较可控,比如订单和购物车、收货与清算、支付与结算等等场景。
在这里插入图片描述

CAP的应用

1、放弃P

放弃分区容错性的话,则放弃了分布式,放弃了系统的可扩展性

2、放弃A

放弃可用性的话,则在遇到网络分区或其他故障时,受影响的服务需要等待一定的时间,再此期间无法对外提供政策的服务,即不可用

3、放弃C

放弃一致性的话(这里指强一致),则系统无法保证数据保持实时的一致性,在数据达到最终一致性时,有个时间窗口,在时间窗口内,数据是不一致的。

对于分布式系统来说,P是不能放弃的,因此架构师通常是在可用性和一致性之间权衡。

CAP 权衡

通过 CAP 理论,我们知道无法同时满足一致性、可用性和分区容错性这三个特性,那要舍弃哪个呢?

对于多数大型互联网应用的场景,主机众多、部署分散,而且现在的集群规模越来越大,所以节点故障、网络故障是常态,而且要保证服务可用性达到 N 个 9,即保证 P 和 A,舍弃C(退而求其次保证最终一致性)。虽然某些地方会影响客户体验,但没达到造成用户流程的严重程度。

对于涉及到钱财这样不能有一丝让步的场景,C 必须保证。网络发生故障宁可停止服务,这是保证 CA,舍弃 P。貌似这几年国内银行业发生了不下 10 起事故,但影响面不大,报道也不多,广大群众知道的少。还有一种是保证 CP,舍弃 A。例如网络故障是只读不写。

BASE定理

CAP是分布式系统设计理论,BASE是CAP理论中AP方案的延伸,对于C我们采用的方式和策略就是保证最终一致性;

eBay的架构师Dan Pritchett源于对大规模分布式系统的实践总结,在ACM上发表文章提出BASE理论,BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性(StrongConsistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。
分布式一致性问题的解决思路有两种,一种是分布式事务,一种是尽量通过业务流程避免分布式事务。分布式事务是直接解决问题,而业务规避其实通过解决出问题的地方(解决提问题的人)。其实在真实业务场景中,如果业务规避不是很麻烦的前提,最优雅的解决方案就是业务规避。

分布式事务分类

分布式事务实现方案从类型上去分刚性事务、柔型事务:

刚性事务满足CAP的CP理论

柔性事务满足BASE理论(基本可用,最终一致)

刚性事务

刚性事务:通常无业务改造,强一致性,原生支持回滚/隔离性,低并发,适合短事务。
原则:刚性事务满足足CAP的CP理论

刚性事务指的是,要使分布式事务,达到像本地式事务一样,具备数据强一致性,从CAP来看,就是说,要达到CP状态。

刚性事务:XA 协议(2PC、JTA、JTS)、3PC,但由于同步阻塞,处理效率低,不适合大型网站分布式场景。

柔性事务

柔性事务指的是,不要求强一致性,而是要求最终一致性,允许有中间状态,也就是Base理论,换句话说,就是AP状态。

与刚性事务相比,柔性事务的特点为:有业务改造,最终一致性,实现补偿接口,实现资源锁定接口,高并发,适合长事务。
柔性事务分为:
补偿型
异步确保型
最大努力通知型。
柔型事务:TCC/FMT、Saga(状态机模式、Aop模式)、本地事务消息、消息事务(半消息)

柔性事务的分类

在电商领域等互联网场景下,刚性事务在数据库性能和处理能力上都暴露出了瓶颈。

柔性事务有两个特性:基本可用和柔性状态。

基本可用是指分布式系统出现故障的时候允许损失一部分的可用性。
柔性状态是指允许系统存在中间状态,这个中间状态不会影响系统整体的可用性,比如数据库读写分离的主从同步延迟等。柔性事务的一致性指的是最终一致性。

柔性事务主要分为补偿型和通知型,

  • 补偿型事务又分:TCC、Saga;
  • 通知型事务分:MQ事务消息、最大努力通知型。
    总体的方案对比:
    请添加图片描述
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值