所谓事务,它是一个操做集合,这些操做要么都执行,要么都不执行,它是一个不可分割的工做单位。好比网上订票,要么你定票成功,余票减一张; 要么你定票失败,余票的数量不变。这就要求购票和余票减小这两个不一样的操做必须放在一块儿,成为一个完整的逻辑链,这样就构成了一个事务。 数据库
事务提及来很简单,就是BEGIN TRAN,COMMIT TRAN,ROLLBACK TRAN 三个语句,可是事务的内部实现原理是很是复杂的,而分布式事务因为须要跨越多个服务器,操做多个数据库,复杂度比单机事务要复杂的多,本文尝试经过锁和并发控制的讲解,逐层抽丝剥茧,由浅及深将分布式事务的实现原理呈现给你们。 服务器
1、事务简介 网络
一、事务的本质并发
事务的核心是锁和并发,采用同步控制的方式保证并发的状况下性能尽量高,且容易理解。 app
计算机能够简单的理解为一个标准的打字机,尽管看起来计算机能够并行处理不少事情,但实际上每一个CPU单位时间内只能作一件事,要么读取数据、要么计算数据、要么写入数据,全部的任务均可以当作这三件事的集合。 分布式
计算机的这种特性引出了一个问题:当多我的去读、算、写操做时,若是不加访问控制,系统势必会产生冲突。而事务至关于在读、算、写操做以外增长了同步的模块,进而保证只有一个线程进入事务当中,而其余线程不会进入。这样的方法其实就是咱们提的事务。高并发
二、事务单元 性能
事务单元是经过Begin-Traction,而后Commit(Begin-Traction、Commit和Rollback之间全部针对数据的写入、读取的操做都应该添加同步访问),Begin和Commit之间就是一个同步的事务单元。例如,Bob给Smith 100块钱就是一个事务单元,这个过程当中有不少步操做,具体以下图所示;但对业务来讲,仅是一个转帐的操做。 优化
当三个帐户都在进行转帐操做时,每一个操做都涉及Smith帐户,全部的事务都会排队,造成一组事务单元。
事务单元之间的Happen-Before关系中的四种可能性:读写、写读、读读、写写。全部事务之间的关系均可以抽象成这四种之一,来对应如今全部的业务逻辑处理。在此基础之上,须要用最快的速度处理多个事务单元之间的关系,同时还能保障这四种操做的逻辑顺序。 spa
三、两阶段锁协议
Two Phase Lock(2PL)是数据库中很是重要的一个概念。数据库操做Insert、Update、Delete都是先读再写的操做。数据库利用这些操做的特性,在每一次查询过程当中,只要查到数据,就会在该数据上加锁。理论上,全部被读取的数据都已加锁,不会再被其余人读到,也就是说对数据进行的中间操做状态对全部人都不可见,当全部中间状态完成后,提交操做时,解开锁,此时数据对全部系统可见,
例如在转帐过程当中,全部人只能看到两种状态:开始时,A有钱,B没钱;结束时,B有钱,A没钱,而中间A减掉钱,B还没有加上钱的状态被锁隐藏掉了,这个操做就是数据库中处理事务的最标准的方式。如上图所示:事务中的Trx2(JoeLock)与其余事务不相关,所以能够并行执行;Trx1须要Lock两个数据Boblock和Smithlock,而Trx3一样须要Lock这两个数据,所以Trx3必须等待,且等待在Boblock上;Joe事务会先结束,Trx3会等到Trx1完成后才会开始。
2、单机事务常见处理方法
单机事务的常见处理方法有排队法、排他锁、读写锁、MVCC等方式,下面来一一解析。
一、排队法
事务处理中最重要也是最简单的方案是排队法,单线程地处理一堆数据。
在排队法中,在全部事务单元的全部的读事务,读写事务都是串行的方式来进行,这种方案的实现简单,可是并发性最差。
二、排它锁
排它锁是针对同一个事务单元的数据进行访问控制。在上述讲解的排队法中,全部事务单元均以单线程的方式进行操做,可是有些场景不适合用单线程操做,能够利用排他锁的方式来快速隔离并发读写事务。数据库中有一些事务单元是共享的,如图中的事务单元1是共享的,事务单元2/3共享数据;针对事务单元2/3共享数据的全部读写Block住,事务单元1单独用一个锁来控制,用这种方式完成系统的访问控制。
三、读写锁
若是是一个只读的事务,例如只对数据进行查询操做,在该过程当中数据必定不被修改,所以多个查询操做能够并行执行,所以一种针对读读场景的优化天然而然产生——读写锁。读写锁的核心是在屡次读的操做中,同时容许多个读者来访问共享资源,提升并发性。
四、MVCC(多版本并发控制)
MVCC的本质是Copy On Write,也就是每次写都是以从新开始一个新的版本的方式写入数据,所以,数据库中也就包含了以前的全部版本。在数据读的过程当中,先申请一个版本号,若是该版本号小于正在写入的版本号,则数据必定能够查询到,无需等到新版本彻底写完便可返回查询结果。这种方式能够在读读不阻塞的前提下,实现读写/写读不阻塞,尽量保证全部的读操做并行,而写操做串行。
MVCC的两种不一样实现方式:
第一种实现方式是将数据记录的多个版本保存在数据库中,当这些不一样版本数据再也不须要时,垃圾收集器回收这些记录。这个方式被PostgreSQL和Firebird/Interbase采用,SQL Server使用的相似机制,所不一样的是旧版本数据不是保存在数据库中,而保存在不一样于主数据库的另一个数据库tempdb中/
第二种实现方式只在数据库保存最新版本的数据,可是会在使用undo时动态重构旧版本数据,这种方式被Oracle和MySQL/InnoDB使用
3、分布式事务的处理方案
分布式事务是指会涉及到操做多个数据库的事务。其实就是将对同一库事务的概念扩大到了对多个库的事务。目的是为了保证分布式系统中的数据一致性。分布式事务处理的关键是必须有一种方法能够知道事务在任何地方所作的全部动做,提交或回滚事务的决定必须产生统一的结果(所有提交或所有回滚),要想理解分布式事务,咱们须要先介绍一下两阶段提交协议。
一、2PC(两阶段提交)
两阶段提交协议(Two-phase Commit,2PC)常常被用来实现分布式事务。通常分为协调器和若干事务执行者两种角色。这里的事务执行者就是具体的数据库,抽象点能够说是能够控制给数据库的程序。 协调器能够和事务执行器在一台机器上。
在分布式系统中,每一个节点虽然能够知晓本身的操做的成功或者失败,却没法知道其余节点的操做的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,须要引入一个做为协调者的组件来统一掌控全部节点(称做参与者)。
所谓的两个阶段是指:第一阶段:准备阶段(投票阶段)和第二阶段:提交阶段(执行阶段)。
1.准备阶段:事务协调者(事务管理器)给每一个参与者(资源管理器)发送Prepare消息,每一个参与者要么直接返回失败,要么在本地执行事务,写本地的redo和undo日志,但不提交
2.提交阶段:若是协调者收到了参与者的失败消息或者超时,直接给每一个参与者发送回滚(Rollback)消息;不然,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操做,释放全部事务处理过程当中使用的锁资源。(注意:必须在最后阶段释放锁资源)。
二阶段提交看起来确实可以提供原子性的操做,可是不幸的是,二阶段提交仍是有几个缺点的:
一、同步阻塞问题。执行过程当中,全部参与节点都是事务阻塞型的。当参与者占有公共资源时,其余第三方节点访问公共资源不得不处于阻塞状态
二、单点故障。因为协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤为在第二阶段,协调者发生故障,那么全部的参与者还都处于锁定事务资源的状态中,而没法继续完成事务操做。(若是是协调者挂掉,能够从新选举一个协调者,可是没法解决由于协调者宕机致使的参与者处于阻塞状态的问题)
三、数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求以后,发生了局部网络异常或者在发送commit请求过程当中协调者发生了故障,这回致使只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求以后就会执行commit操做。可是其余部分未接到commit请求的机器则没法执行事务提交。因而整个分布式系统便出现了数据部一致性的现象。
四、二阶段没法解决的问题:协调者再发出commit消息以后宕机,而惟一接收到这条消息的参与者同时也宕机了。那么即便协调者经过选举协议产生了新的协调者,这条事务的状态也是不肯定的,没人知道事务是否被已经提交。
因为二阶段提交存在着诸如同步阻塞、单点问题等缺陷,因此,研究者们在二阶段提交的基础上作了改进,提出了三阶段提交。
二、3PC(三阶段提交)
3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
CanCommit阶段:3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者若是能够提交就返回Yes响应,不然返回No响应。
PreCommit阶段:协调者根据参与者的反应状况来决定是否能够记性事务的PreCommit操做。根据响应状况,有如下两种可能:
doCommit阶段:该阶段进行真正的事务提交
相对于2PC,3PC主要解决的单点故障问题,并减小阻塞,由于一旦参与者没法及时收到来自协调者的信息以后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。可是这种机制也会致使数据一致性问题,由于,因为网络缘由,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时以后执行了commit操做。这样就和其余接到abort命令并执行回滚的参与者之间存在数据不一致的状况。