分布式事务有哪些解决方案

分布式事务是分布式系统中非常重要的一部分,最典型的例子是银行转款和扣款,A和B的账户信息分别存在于不同的服务器上,A给B转账100元,要完成这个操作,需要两个步骤,从A的账户上扣款以及增加B的账户金额,两个操作步骤是必须要不执行成功的,就像绑定在一起了一样,一个操作失败,那么另一个操作也不能执行。

那么类似与转账扣款的例子,在业务中如何保证一致性

分布式事务是什么

分布式事务关注的是在分布式场景下如何处理事务,是指事物的参与者,支持事务操作的服务器、存储资源分别在分布式系统的不同节点上。

简单来说,分布式事务就是一个业务操作,是由多个细分操作完成的,而这些细分操作有分布在不同的服务器上;事务,就是这些操作要么全部执行,要么就一个也不执行,我们也称之为事务的原子性。

数据库事务

数据库事务的特性包括原子性(Atomicity)、一致性(Consistency)、隔离性(lsolation)和持久性(Durability),简称ACID

原子性:操作这些指令时,要么全部执行成功,要么全部不执行,只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。

一致性:事务的执行是数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。

隔离性:隔离性是当多个用户并发访问数据库是,比如操作用一张表时,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

持久性:当事务正确完成后,它对于数据的改变是永久性的。

在数据库执行中,多个并发执行的事务如果涉及到用一份数据的读写就容易出现数据不一致的情况,不一致的异常现像有以下几种。

脏读:是指一个事物访问到了另一个事务未提交的数据,例如事务T1中修改的数据尚未提交的情况下被其他事务T2读取到,如果T1事务又进行了回滚操作,则T2刚刚读取到的数据实际上是不存在的。

不可重复读:是指一个事务读取同一条记录2次,得到的结果却不一致,例如事务T1第一次读取数据,接下来T2事务又对其中的数据进行了更新或删除,并且成功提交,这时候事务T1再来读取这些数据,那么会得到T2修改后的数据,发现数据已经变更,这样T1在一个事务中的两次读取,返回的结果会不一致。

幻读:是指一个事物读取2次后,得到的记录条数不一致,例如事务T1查询获得一个结果集,T2插入了新的数据并且提交成功,T1再次执行同样的查询,此时得到的结果及记录数是不同的(表的结构已经改变)。

脏读、不可重复读和幻读有以下的包含关系,如果发生了脏读,那么幻读和不可重复读都有可能出现。

Cgq2xl6YKgWAFKVkAABZg_32mvY092

不同的隔离级别

SQL标准根据三种不一致的异常现象,将隔离性定义为四个隔离级别,隔离级别和数据库的性能呈反比,隔离级别越低,则数据库性能越高;反之隔离级别越高,数据性能越差

CgoCgV6ZEeeARjgZAACG1UhVyAQ853

Read uncommitted 读未提交

在该级别下,一个事务对数据修改的过程中,不允许另一个事务对该行数据进行修改,但允许另一个事务对该行数据进行读,不会出现更新丢失,但会出现脏读,不可重复读的情况。

Read committed读已提交

在该级别下,未提交的写事务不允许其他事务进行访问的,便不会出现脏读,但是读取数据的事务允许其他事务访问该行数据,因此会出现不可重复的情况。

Repeatable read 可重复读

在该级别下,在同一个事务内的查询都是和事务开始时刻一致的,保证对用一字段的多次读取结果都相同,除非数据是被本身事务自己所修改,不会出现同一事物读到两次不同数据的情况,因为没有约束其他事务的Insert操作,所以SQL标准中可重复读级别会出现幻读。

Serializable 序列化

该级别要求所有事务都必须串行执行,可以避免各种并发引起的问题,效率当然也是最低的。

对不同隔离级别的解释,其实是为了保持数据库事务中的隔离性(Isolation),目标是使并发事务的执行效果与串行一致,隔离级别的提升带来的是并发能力的下降,两者是负相关的关系。

这里值得提一下MySQL数据的事务隔离级别为读已提交级别,但是在*MySQL额外添加了间隙锁(Gap Lock),便可以防止幻读。而Oracle的隔离界别则是可重复读级别,同时二者都支持Serializable串行化事务级别,可实现最高级别。
MySQL中间隙锁(Gap Lock):间隙锁(Gap Lock)是Innodb在可重复的提交下为了解决幻读问题时引入的锁机制,幻读的问题存在是因为新增或者更新操作,这时如果进行范围查询的时候(加锁查询),会出现不一致的问题,这时使用不同的行锁已经没有办法满足要求,需要对一定范围内的数据进行加锁,间隙锁就是解决这类问题的。在可重复读隔离级别下,数据库是通过行锁和间隙锁共同组成和实现的(next-key lock)。

分布式事务产生的原因

分布式事务是伴随着系统拆分出现的,前面我们说过,分布式系统解决了海量数据服务对扩展性的要求,但是增加了架构上的复杂性,在这一点上,分布式事务就是典型的体现。

在实际开发中,分布式事务产生的原因主要来源于存储和服务的拆分。

存储层拆分

存储层拆分,最典型的就是数据库分库分表,一般来说,当单表的容量达到千万级,就要考虑数据库拆分,从单一数据库变成多个分库和多个分表,在业务中如果需要进行跨库或者跨表更新,同时要保证数据的一致性,就产生了分布式事务的问题。

CgoCgV6YKgWAC4JgAAB2eZ1XI1A491

服务层拆分

服务层拆分就是业务的服务化,系统架构的演进就是从集中式到分布式,业务功能之间越来越解耦合。

比如电商网站系统,业务初期可能是一个单体工程支撑整套服务,但随着系统规模进一步变大,参考海威定律(每一起严重事故的背后,必然有29次轻微事故和300起未遂先兆以及1000起事故隐患。法则强调两点:一是事故的发生是量的积累的结果;二是再好的技术,再完美的规章,在实际操作层面,也无法取代人自身的素质和责任心),大多数公司都会将公司的核心技术抽取出来,作为单独的服务器。而其他操作的服务器,则都有自己各自领域的服务,业务逻辑的执行散落在不同的服务器上。

用户如果在某网站上进行一个下单操作,那么会同时依赖订单服务、库存服务、支付扣款服务,这几个操作如果有一个失败了,那么下单操作也就完不成,这就需要分布式事务来保证了。

Ciqah16YM9CAZN3eAAEQmtX7AiM805

分布式事务解决方案

分布式事务的解决方案,典型的有两阶段和三阶段提交协议,TCC分段提交,和基于消息队列的最终一致性设计。

2PC两阶段提交

两阶段提交是非常经典的强一致性、中心化的原子提交协议,在各种事务和一致性的解决方案中,都能看到两阶段提交的应用。

3PC三阶段提交

三阶段提交协议,是在2PC上扩展的提交协议,主要是为了解决两阶段提交协议阻塞问题,从原来的两个阶段扩展为三个阶段,增加了超时机制。

TCC分段提交

TCC是一个分布式事务的处理模型,将食物过程拆分为Try、Commit、Cancel三个步骤,在保证强一致性的同时,最大限度提高系统的可伸缩性和可用性。

基于消息补偿的最终一致性

异步化在分布式系统设计中随处可见,基于消息队列的最终一致性就是一种异步事务机制,在业务中广泛应用。

在具体实现上,基于消息补偿的一致性主要有本地消息表和第三方可靠消息队列等。

下面介绍一下本地消息表,本地消息表的方案最初是由 e bay 的工程师提出,核心思想是将分布式事务拆分成本地事务进行处理,通过消息日志的方式来异步执行。

本地消息表是一种业务耦合的设计,消息生产方需要额外建一个事务消息表,并记录消息发送状态,消息消费方需要处理这个消息,并完成自己的业务逻辑,另外会有一个异步机制来定期扫描未完成的消息,确保最终一致性。

Cgq2xl6YKgaAVUwAAAFePtc8mmU340

(1)系统收到下单请求,将订单业务数据存入到订单库中,并且同时存储该订单对应的消息数据,比如购买商品的 ID 和数量,消息数据与订单库为同一库,更新订单和存储消息为一个本地事务,要么都成功,要么都失败。

(2)库存服务通过消息中间件收到库存更新消息,调用库存服务进行业务操作,同时返回业务处理结果。

(3)消息生产方,也就是订单服务收到处理结果后,将本地消息表的数据删除设置为已完成。

(4)设置异步任务,定时去扫描本地消息表,发现有未完成的任务则重试,保证最终一致性。

不要求最终一致性的柔性事务

除了上述几种,还有一种不保证最终一致性的柔性事务,也成为尽最大努力通知,这种方式适合可以接受部分不一致的业务场景

什么是事务?事务的四个特性以及事务的隔离级别

分布式技术原理与实战45讲》- 蒋越

MySQL(04)间隙锁详解