文章很长,不断更新,建议收藏,慢慢阅读! 以下珍贵的学习资源:
- (加尼恩)
- (加尼恩领取)
- (加尼恩)
推荐:进入大厂 、做架构,大力提升Java 内功 的 精彩博文
入大厂 、做架构,大力提升Java 内功 必备的精彩博文
2021 秋招涨薪1W 必备的精彩博文
2: (图-秒理解-史上最完整的)
3: (必要的面试)
4: (史上最全)
5:
6:
7:
8:Feign原理 (图解)
9:DNS图解(秒懂 史上最全 高薪必备)
10:CDN图解(秒懂 史上最全 高薪必备)
10:
Java 面试题 30个专题 , 史上最全 , 面试必刷
阿里、京东、美团… 随意挑,横着走!
1: JVM面试题(史上最强,持续更新,推荐吐血)
2:Java基础面试题(史上最全、持续更新、吐血推荐
三、架构设计面试题 (史上最完整、最持续、最新)
4.设计模式面试题 (史上最完整、最持续、最新)
17.分布式事务面试问题 (史上最完整、最持续、最新)
(史上最全)
29、(史上最全)
30、HR经过五关斩六将后,小心阴沟翻船!
9.(史上最完整、最持续、最新)
更多专题, 请参见【 疯狂创客圈 高并发 总目录 】
SpringCloud 精彩博文
(史上最全)
sentinel (史上最全 入门教程)
(史上最全)
更多专题, 请参见【 疯狂创客圈 高并发 总目录 】
我问过无数次面试问题
现在Java在面试中,分布式系统和分布式事务几乎是标准的。分布式系统和分布式事务本身比较复杂,大家学习都很头疼。
面试问题:你知道分布式事务吗?您如何解决分布式事务问题? (标准答案:见末尾)
友情提示:
看完这篇文章,基本上可以在分布式事务中吊打面试官。
一图解读分布式事务
首先,给大家一张全网最牛逼的图片总结一下:

名词解释
- 事务:事务是由一组操作组成的可靠独立的工作单位,事务有ACID特性,即原子性、一致性、隔离性和持久性。
- 本地事务:当事务由资源管理器本地管理时,称为本地事务。本地事务的优势是严格支持ACID特点、高效、可靠,状态只能在资源管理器中维护,编程模型的应用简单。然而,当地事务没有处理分布式事务的能力,隔离的最小单位仅限于资源管理器。
- 全局事务:当事务由全局事务管理器进行全局管理时,事务管理器负责管理全局事务状态和参与资源,协同资源一致提交回滚。
- TX协议:应用或应用服务器与事务管理器的接口。
- XA协议:全局事务管理器与资源管理器的接口。XA是由X/Open组织提出的分布式事务规范。该规范主要定义了全局事务管理器和局部资源管理器之间的接口。实现了主流数据库产品XA接口。XA接口是在事务管理器和多个资源管理器之间作为通信桥梁的双向系统接口。之所以需要XA理论上讲,两台机器在分布式系统中无法达到一致性,因此引入了一个单点进行协调。由全局事务管理器管理和协调的事务可以跨越多个资源和过程。全局事务管理器一般使用XA协议阶段协议与数据库互动。
- AP:应用程序可以理解为使用DTP(Data Tools Platform)的程序。
- RM:这里可以是资源管理器DBMS或者消息服务器管理系统,应用程序通过资源管理器控制资源,必须实现资源XA定义的接口。资源管理器负责控制和管理实际的资源。
- TM:事务管理器负责协调和管理事务AP编程接口和管理资源管理器。事务管理器控制全局事务,管理事务生命周期,协调资源。
- 两阶段提交协议:XA协调全局事务中多种资源的机制。TM和RM采用两阶段提交的方案解决一致性问题。提交两个节点需要一个协调员(TM)来掌控所有参与者(RM)最终是否需要提交节点的操作结果并指导。两个阶段提交的限制在于协议成本、准备阶段的持久成本、整体事务状态的持久成本、潜在故障点带来的脆弱性。准备后,提交前的故障导致了一系列的隔离和恢复问题。
- BASE理论:BA指支持分区失败的基本业务可用性,S表示柔性状态,即允许在短时间内不同步,E数据最终是一致的,但实时是不一致的。必须从根本上保证原子性和耐久性。为了降低可用性、性能和服务降级的需要,只有降低一致性和隔离性的要求。
- CAP定理:共享数据系统最多只能同时拥有CAP其中两个,任何两个都有自己适应的场景,这通常是真正的业务系统ACID与CAP的混合体。分布式系统中最重要的是满足业务需求,而不是追求高度抽象和绝对的系统特性。C表示一致性,即所有用户都看到相同的数据。A表示可用性是指总能找到可用数据副本。P表示分区容错,能容忍网络中断等故障。
分布式锁解决了分布式资源抢占问题;分布式事务和本地事务是解决流程提交问题。
事务简介
事务(Transaction)是操作数据库中数据项的程序执行单元(unit)。
事务应具有原子性、一致性、隔离性和持久性四个属性。这四个属性通常被称为ACID特性。
事务的四个特点:
1、Atomic原子性
事务必须是原子操作序列单元。在执行过程中,事务中包含的所有操作要么成功执行,要么不执行。如果任何失败,整个事务都会回滚。只有全部成功执行,整个事务才算成功。
2、Consistency一致性
事务的执行不能破坏数据库数据的完整性和一致性,数据库必须在执行前后一致。
3、Isolation隔离性
在并发环境中,并发事务是相互隔离的,一个事务的执行不能受到其他事务的干扰。也就是说,当不同的事务并发操作相同的数据时,每个事务都有自己完整的数据空间,即事务内部的操作和使用的数据与其他并发事务隔离,并发执行的事务不能相互干扰。
SQL四个事务隔离级别:
(1)未提交阅读
允许脏读。如果一个事务正在处理某个数据并更新它,但它还没有完成,所以它没有提交,同时允许另一个事务访问它。例如,A将变量n从0累积到10,B可以读取n变量从0到10之间的所有中间值。 (2)已提交阅读
允许不要重复阅读。只允许阅读已提交的数据。也就是说,事务A正在将n从0累加到n0的过程中,B无法看到n的中间值,之中只能看到10。同时有事务C进行从10到20的累加,此时B在同一个事务内再次读时,读到的是20。
(3)可重复读
允许幻读。保证在事务处理过程中,多次读取同一个数据时,其值都和事务开始时刻时是一致的。禁止脏读、不可重复读。幻读即同样的事务操作,在前后两个时间段内执行对同一个数据项的读取,可能出现不一致的结果。保证B在同一个事务内,多次读取n的值,读到的都是初始值0。幻读,就是不同事务,读到的n的数据可能是0,可能10,可能是20
(4)串行化
最严格的事务,要求所有事务被串行执行,不能并发执行。
如果不对事务进行并发控制,我们看看数据库并发操作是会有那些异常情形
- (1)一类丢失更新:两个事物读同一数据,一个修改字段1,一个修改字段2,后提交的恢复了先提交修改的字段。
- (2)二类丢失更新:两个事物读同一数据,都修改同一字段,后提交的覆盖了先提交的修改。
- (3)脏读:读到了未提交的值,万一该事物回滚,则产生脏读。
- (4)不可重复读:两个查询之间,被另外一个事务修改了数据的内容,产生内容的不一致。
- (5)幻读:两个查询之间,被另外一个事务插入或删除了记录,产生结果集的不一致。
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),然后进行数据的修改。如果出现了错误或者用户执行了 Rollback 语句,系统可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。
具体的方式为:Redo Log 记录的是新数据的备份(和 Undo Log 相反)。在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可以根据 Redo Log 的内容,将所有数据恢复到崩溃之前的状态。
脏读、幻读、不可重复读
在多个事务并发操作时,数据库中会出现下面三种问题:。
事务A读到了事务B还未提交的数据:
事务A读取的数据,事务B对该数据进行修改还未提交数据之前,事务A再次读取数据会读到事务B已经修改后的数据,如果此时事务B进行回滚或再次修改该数据然后提交,事务A读到的数据就是脏数据,这个情况被称为脏读(Dirty Read)。
幻读(Phantom Read )
事务A进行范围查询时,事务B中新增了满足该范围条件的记录,当事务A再次按该条件进行范围查询,会查到在事务B中提交的新的满足条件的记录(
不可重复读(U nrepeatable Read**)**
事务A在读取某些数据后,再次读取该数据,发现读出的该数据已经在事务B中发生了变更或删除。
幻读和不可重复度的区别:
幻读 :在同一事务中,相同条件下,两次查询出来的记录数 不一样;不可重复读 :在同一事务中,相同条件下,两次查询出来的数据 不一样;
事务的隔离级别
为了解决数据库中事务并发所产生的问题,在标准SQL规范中,定义了四种事务隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。
低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
通过修改MySQL系统参数来控制事务的隔离级别,在MySQL8中该参数为
MySQL8:
-- 查看系统隔离级别:
SELECT @@global.transaction_isolation;
-- 查看当前会话隔离级别
SELECT @@transaction_isolation;
-- 设置当前会话事务隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 设置全局事务隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-
未提交读(READ UNCOMMITTED) :所有事务都可以看到其他事务未提交的修改。一般很少使用; -
提交读(READ COMMITTED) :Oracle默认隔离级别,事务之间只能看到彼此已提交的变更修改; -
可重复读(REPEATABLE READ) :MySQL默认隔离级别,同一事务中的多次查询会看到相同的数据行;可以解决不可重复读,但可能出现幻读; -
可串行化(SERIALIZABLE) :最高的隔离级别,事务串行的执行,前一个事务执行完,后面的事务会执行。读取每条数据都会加锁,会导致大量的超时和锁争用问题;
问:如何保证 REPEATABLE READ 级别绝对不产生幻读?
答 :在SQL中加入 for update (排他锁) 或 lock in share mode (共享锁)语句实现。就是锁住了可能造成幻读的数据,阻止数据的写入操作。
分布式事务的基本概念
分布式环境的事务复杂性
当本地事务要扩展到分布式时,它的复杂性进一步增加了。
首先就是存储端的多样性。本地事务的情况下,所有数据都会落到同一个DB中,但是,在分布式的情况下,就会出现数据可能要落到多个DB,或者还会落到Redis,落到MQ等中。
存储端多样性, 如下图所示:
本地事务的情况下,通常所有事务相关的业务操作,会被我们封装到一个Service方法中。而在分布式的情况下,请求链路被延展,拉长,一个操作会被拆分成多个服务,它们呈现线状或网状,依靠网络通信构建成一个整体。在这种情况下,事务无疑变得更复杂。
事务链路延展性, 如下图所示:
基于上述两个复杂性,期望有一个统一的分布式事务方案,能够像本地事务一样,以几乎无侵入的方式,满足各种存储介质,各种复杂链路,是不现实的。
至少,在当前,还没有一个十分成熟的解决方案。所以,一般情况下,在分布式下,事务会被拆分解决,并根据不同的情况,采用不同的解决方案。
什么是分布式事务?
对于分布式系统而言,需要保证分布式系统中的数据一致性,保证数据在子系统中始终保持一致,避免业务出现问题。分布式系统中对数要么一起成功,要么一起失败,必须是一个整体性的事务。
分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
简单的说,在分布式系统上一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务节点上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。
举个例子:在电商网站中,用户对商品进行下单,需要在订单表中创建一条订单数据,同时需要在库存表中修改当前商品的剩余库存数量,两步操作一个添加,一个修改,我们一定要保证这两步操作一定同时操作成功或失败,否则业务就会出现问题。
任何事务机制在实现时,都应该考虑事务的ACID特性,包括:本地事务、分布式事务。对于分布式事务而言,即使不能都很好的满足,也要考虑支持到什么程度。
典型的分布式事务场景:
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理论正式成为分布式计算领域的公认定理。
所以,CAP定理在迄今为止的分布式系统中都是适用的!
CAP的一致性、可用性、分区容错性 具体如下:
1、一致性
数据一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,不能存在中间状态。
分布式环境中,一致性是指多个副本之间能否保持一致的特性。在一致性的需求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处理一致的状态。
例如对于电商系统用户下单操作,库存减少、用户资金账户扣减、积分增加等操作必须在用户下单操作完成后必须是一致的。不能出现类似于库存已经减少,而用户资金账户尚未扣减,积分也未增加的情况。如果出现了这种情况,那么就认为是不一致的。
-
如果的确能像上面描述的那样时刻保证客户端看到的数据都是一致的,那么称之为强一致性。
-
如果允许存在中间状态,只要求经过一段时间后,数据最终是一致的,则称之为最终一致性。
-
此外,如果允许存在部分数据不一致,那么就称之为弱一致性。
面试题:什么是数据一致性? 现在知道怎么回答了吧!
2、可用性
系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
两个度量的维度:
(1)有限时间内 对于用户的一个操作请求,系统必须能够在指定的时间(响应时间)内返回对应的处理结果,
试想,如果一个下单操作,为了保证分布式事务的一致性,需要10分钟才能处理完,那么用户显然是无法忍受的。
(2)返回正常结果 要求系统在完成对用户请求的处理后,返回一个正常的响应结果。正常的响应结果通常能够明确地反映出对请求的处理结果,即成功或失败,而不是一个让用户感到困惑的返回结果。比如返回一个系统错误如OutOfMemory,则认为系统是不可用的。
“返回结果”是可用性的另一个非常重要的指标,它要求系统在完成对用户请求的处理后,返回一个正常的响应结果,不论这个结果是成功还是失败。
3、分区容错性
即分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
网络分区,是指分布式系统中,不同的节点分布在不同的子网络(机房/异地网络)中,由于一些特殊的原因导致这些子网络之间出现网络不连通的状态,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干孤立的区域。组成一个分布式系统的每个节点的加入与退出都可以看做是一个特殊的网络分区。
CAP的应用
1、放弃P
放弃分区容错性的话,则放弃了分布式,放弃了系统的可扩展性
2、放弃A
放弃可用性的话,则在遇到网络分区或其他故障时,受影响的服务需要等待一定的时间,再此期间无法对外提供政策的服务,即不可用
3、放弃C
放弃一致性的话(这里指强一致),则系统无法保证数据保持实时的一致性,在数据达到最终一致性时,有个时间窗口,在时间窗口内,数据是不一致的。
对于分布式系统来说,P是不能放弃的,因此架构师通常是在可用性和一致性之间权衡。
CAP 理论告诉我们:
目前很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。
基于 CAP理论,很多系统在设计之初就要对这三者做出取舍:
任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。在互联网领域的绝大多数的场景中,都需要
牺牲强一致性来换取系统的高可用性 ,系统往往只需要保证最终一致性。
问:为什么分布式系统中无法同时保证一致性和可用性? 答:首先一个前提,对于分布式系统而言,分区容错性是一个最基本的要求,因此基本上我们在设计分布式系统的时候只能从一致性(C)和可用性(A)之间进行取舍。
如果保证了一致性(C):对于节点N1和N2,当往N1里写数据时,N2上的操作必须被暂停,只有当N1同步数据到N2时才能对N2进行读写请求,在N2被暂停操作期间客户端提交的请求会收到失败或超时。显然,这与可用性是相悖的。
如果保证了可用性(A):那就不能暂停N2的读写操作,但同时N1在写数据的话,这就违背了一致性的要求。
CAP 权衡
通过 CAP 理论,我们知道无法同时满足一致性、可用性和分区容错性这三个特性,那要舍弃哪个呢?
对于多数大型互联网应用的场景,主机众多、部署分散,而且现在的集群规模越来越大,所以节点故障、网络故障是常态,而且要保证服务可用性达到 N 个 9,即保证 P 和 A,舍弃C(退而求其次保证最终一致性)。虽然某些地方会影响客户体验,但没达到造成用户流程的严重程度。
对于涉及到钱财这样不能有一丝让步的场景,C 必须保证。网络发生故障宁可停止服务,这是保证 CA,舍弃 P。貌似这几年国内银行业发生了不下 10 起事故,但影响面不大,报道也不多,广大群众知道的少。还有一种是保证 CP,舍弃 A。例如网络故障是只读不写。
CAP和ACID中的A和C是完全不一样的
A的区别:
- ACID中的A指的是原子性(Atomicity),是指事务被视为一个不可分割的最小工作单元,事务中的所有操作要么全部提交成功,要么全部失败回滚;
- CAP中的A指的是可用性(Availability),是指集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求;
C的区别:
- ACID一致性是有关数据库规则,数据库总是从一个一致性的状态转换到另外一个一致性的状态;
- CAP的一致性是分布式多服务器之间复制数据令这些服务器拥有同样的数据,由于网速限制,这种复制在不同的服务器上所消耗的时间是不固定的,集群通过组织客户端查看不同节点上还未同步的数据维持逻辑视图,这是一种分布式领域的一致性概念;
总之:
ACID里的一致性指的是事务执行前后,数据库完整性,而CAP的一致性,指的是分布式节点的数据的一致性。背景不同,无从可比
BASE定理
CAP是分布式系统设计理论,BASE是CAP理论中AP方案的延伸,对于C我们采用的方式和策略就是保证最终一致性;
eBay的架构师Dan Pritchett源于对大规模分布式系统的实践总结,在ACM上发表文章提出BASE理论,BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性(StrongConsistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。
BASE定理
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。BASE基于CAP定理演化而来,核心思想是即时无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
1、Basically Available(基本可用)
基本可用是指分布式系统在出现不可预知的故障的时候,允许损失部分可用性,但不等于系统不可用。
(1)响应时间上的损失
当出现故障时,响应时间增加
(2)功能上的损失
当流量高峰期时,屏蔽一些功能的使用以保证系统稳定性(服务降级)
2、Soft state(软状态)
指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性。
与硬状态相对,即是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
3、Eventually consistent(最终一致性)
强调系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。其本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
最终一致性可分为如下几种:
- (1)因果一致性(Causal consistency) 即进程A在更新完数据后通知进程B,那么之后进程B对该项数据的范围都是进程A更新后的最新值。
- (2)读己之所写(Read your writes) 进程A更新一项数据后,它自己总是能访问到自己更新过的最新值。
- (3)会话一致性(Session consistency) 将数据一致性框定在会话当中,在一个会话当中实现读己之所写的一致性。即执行更新后,客户端在同一个会话中始终能读到该项数据的最新值
- (4)单调读一致性(Monotonic read consistency) 如果一个进程从系统中读取出一个数据项的某个值后,那么系统对于该进程后续的任何数据访问都不应该返回更旧的值。
- (5)单调写一致性(Monotoic write consistency) 一个系统需要保证来自同一个进程的写操作被顺序执行。
BASE理论是提出通过牺牲一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。
BASE理论的特点:
BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的。
它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。
但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,ACID特性和BASE理论往往又会结合在一起。
BASE理论与CAP的关系
BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结, 是基于CAP定理逐步演化而来的。BASE理论的核心思想是:
BASE理论其实就是对CAP理论的延伸和补充,主要是对AP的补充。牺牲数据的强一致性,来保证数据的可用性,虽然存在中间装填,但数据最终一致。
ACID 和 BASE 的区别与联系
ACID 是传统数据库常用的设计理念,追求强一致性模型。BASE 支持的是大型分布式系统,提出通过牺牲强一致性获得高可用性。
ACID 和 BASE 代表了两种截然相反的设计哲学,在分布式系统设计的场景中,系统组件对一致性要求是不同的,因此 ACID 和 BASE 又会结合使用。
分布式事务分类:柔性事务和刚性事务
分布式场景下,多个服务同时对服务一个流程,比如电商下单场景,需要支付服务进行支付、库存服务扣减库存、订单服务进行订单生成、物流服务更新物流信息等。如果某一个服务执行失败,或者网络不通引起的请求丢失,那么整个系统可能出现数据不一致的原因。
上述场景就是分布式一致性问题,追根到底,分布式一致性的根本原因在于数据的分布式操作,引起的本地事务无法保障数据的原子性引起。
分布式一致性问题的解决思路有两种,一种是分布式事务,一种是尽量通过业务流程避免分布式事务。分布式事务是直接解决问题,而业务规避其实通过解决出问题的地方(解决提问题的人)。其实在真实业务场景中,如果业务规避不是很麻烦的前提,最优雅的解决方案就是业务规避。
分布式事务分类
分布式事务实现方案从类型上去分刚性事务、柔型事务:
-
刚性事务满足CAP的CP理论
-
柔性事务满足BASE理论(基本可用,最终一致)
刚性事务
刚性事务:通常无业务改造,强一致性,原生支持回滚/隔离性,低并发,适合短事务。
原则:刚性事务满足足CAP的CP理论
刚性事务指的是,要使分布式事务,达到像本地式事务一样,具备数据强一致性,从CAP来看,就是说,要达到CP状态。
刚性事务:XA 协议(2PC、JTA、JTS)、3PC,但由于同步阻塞,处理效率低,不适合大型网站分布式场景。
柔性事务
柔性事务指的是,不要求强一致性,而是要求最终一致性,允许有中间状态,也就是Base理论,换句话说,就是AP状态。
与刚性事务相比,柔性事务的特点为:有业务改造,最终一致性,实现补偿接口,实现资源锁定接口,高并发,适合长事务。
柔性事务分为:
- 补偿型
- 异步确保型
- 最大努力通知型。
柔型事务:TCC/FMT、Saga(状态机模式、Aop模式)、本地事务消息、消息事务(半消息)
刚性事务:XA模型、XA接口规范、XA实现
XA模型 或者 X/Open DTP模型
X/OPEN是一个组织.X/Open国际联盟有限公司是一个欧洲基金会,它的建立是为了向UNIX环境提供标准。它主要的目标是促进对UNIX语言、接口、网络和应用的开放式系统协议的制定。它还促进在不同的UNIX环境之间的应用程序的互操作性,以及支持对电气电子工程师协会(IEEE)对UNIX的可移植操作系统接口(POSIX)规范。
X/Open
在X/Open **DTP(Distributed Transaction Process)**模型里面,有三个角色:
AP: Application,应用程序。也就是业务层。哪些操作属于一个事务,就是AP定义的。
TM: Transaction Manager,事务管理器。接收AP的事务请求,对全局事务进行管理,管理事务分支状态,协调RM的处理,通知RM哪些操作属于哪些全局事务以及事务分支等等。这个也是整个事务调度模型的核心部分。
RM:Resource Manager,资源管理器。一般是数据库,也可以是其他的资源管理器,如消息队列(如JMS数据源),文件系统等。
XA把参与事务的角色分成AP,RM,TM。
AP,即应用,也就是我们的业务服务。
RM指的是资源管理器,即DB,MQ等。
TM则是事务管理器。
AP自己操作TM,当需要事务时,AP向TM请求发起事务,TM负责整个事务的提交,回滚等。
XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接口。XA接口是双向的系统接口,在事务管理器(Transaction Manager)以及一个或多个资源管理器(Resource Manager)之间形成通信桥梁。
XA之所以需要引入事务管理器是因为,在分布式系统中,从理论上讲(参考Fischer等的论文),两台机器理论上无法达到一致的状态,需要引入一个单点进行协调。事务管理器控制着全局事务,管理事务生命周期,并协调资源。资源管理器负责控制和管理实际资源(如数据库或JMS队列)
XA规范
什么是XA
用非常官方的话来说:
-
XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。
-
XA 规范 描述了全局的事务管理器与局部的资源管理器之间的接口。 XA规范 的目的是允许的多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使 ACID 属性跨越应用程序而保持有效。
-
XA 规范 使用两阶段提交(2PC,Two-Phase Commit)协议来保证所有资源同时提交或回滚任何特定的事务。
-
XA 规范 在上世纪 90 年代初就被提出。目前,几乎所有主流的数据库都对 XA 规范 提供了支持。
XA规范(XA Specification) 是X/OPEN 提出的分布式事务处理规范。XA则规范了TM与RM之间的通信接口,在TM与多个RM之间形成一个双向通信桥梁,从而在多个数据库资源下保证ACID四个特性。目前知名的数据库,如Oracle, DB2,mysql等,都是实现了XA接口的,都可以作为RM。
XA是数据库的分布式事务,强一致性,在整个过程中,数据一张锁住状态,即从prepare到commit、rollback的整个过程中,TM一直把持折数据库的锁,如果有其他人要修改数据库的该条数据,就必须等待锁的释放,存在长事务风险。
XA协议的实现
2PC/3PC协议
两阶段提交(2PC)协议是XA规范定义的 数据一致性协议。
三阶段提交(3PC)协议对 2PC协议的一种扩展。
Seata
Seata , 官网 , github , 1万多星
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式
在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。商业化产品GTS 先后在阿里云、金融云进行售卖
Jta规范
作为java平台上事务规范 JTA(Java Transaction API)也定义了对XA事务的支持,实际上,JTA是基于XA架构上建模的,在JTA 中,事务管理器抽象为javax.transaction.TransactionManager接口,并通过底层事务服务(即JTS)实现。像很多其他的java规范一样,JTA仅仅定义了接口,具体的实现则是由供应商(如J2EE厂商)负责提供,目前JTA的实现主要由以下几种: 1.J2EE容器所提供的JTA实现(JBoss) 2.独立的JTA实现:如JOTM,Atomikos.
这些实现可以应用在那些不使用J2EE应用服务器的环境里用以提供分布事事务保证。如Tomcat,Jetty以及普通的java应用。
JTS规范
事务是编程中必不可少的一项内容,基于此,为了规范事务开发,Java增加了关于事务的规范,即JTA和JTS
JTA定义了一套接口,其中约定了几种主要的角色:TransactionManager、UserTransaction、Transaction、XAResource,并定义了这些角色之间需要遵守的规范,如Transaction的委托给TransactionManager等。
JTS也是一组规范,上面提到JTA中需要角色之间的交互,那应该如何交互?JTS就是约定了交互细节的规范。
总体上来说JTA更多的是从框架的角度来约定程序角色的接口,而JTS则是从具体实现的角度来约定程序角色之间的接口,两者各司其职。
Atomikos分布式事务实现
Atomikos公司旗下有两款著名的分布事务产品:
- TransactionEssentials:开源的免费产品
- ExtremeTransactions:商业版,需要收费
这两个产品的关系如下图所示:
可以看到,在开源版本中支持JTA/XA、JDBC、JMS的事务。
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);
}
}
XA的主要限制
- 必须要拿到所有数据源,而且数据源还要支持XA协议。目前MySQL中只有InnoDB存储引擎支持XA协议。
- 性能比较差,要把所有涉及到的数据都要锁定,是强一致性的,会产生长事务。
Seata AT 模式
Seata AT 模式是增强型2pc模式。
AT 模式: 两阶段提交协议的演变,没有一直锁表
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源
- 二阶段:提交异步化,非常快速地完成。或回滚通过一阶段的回滚日志进行反向补偿
LCN(2pc)
TX-LCN , 官方文档 , github , 3千多星 , 5.0以后由于框架兼容了LCN(2pc)、TCC、TXC 三种事务模式,为了区分LCN模式,特此将LCN分布式事务改名为TX-LCN分布式事务框架。
TX-LCN定位于一款事务协调性框架,框架其本身并不生产事务,而是本地事务的协调者,从而达到事务一致性的效果。
TX-LCN 主要有两个模块,Tx-Client(TC) ,Tx-Manager.
- TM (Tx-Manager):是独立的服务,是分布式事务的控制方,协调分布式事务的提交,回滚
- TC(Tx-Client):由业务系统集成,事务发起方、参与方都由TxClient端来控制
2PC(标准XA模型)
2PC即Two-Phase Commit,二阶段提交。
详解:二个阶段
广泛应用在数据库领域,为了使得基于分布式架构的所有节点可以在进行事务处理时能够保持原子性和一致性。绝大部分关系型数据库,都是基于2PC完成分布式的事务处理。
顾名思义,2PC分为两个阶段处理,阶段一:提交事务请求、阶段二:执行事务提交;
如果阶段一超时或者出现异常,2PC的阶段二:中断事务
阶段一:提交事务请求
- 事务询问。协调者向所有参与者发送事务内容,询问是否可以执行提交操作,并开始等待各参与者进行响应;
- 执行事务。各参与者节点,执行事务操作,并将Undo和Redo操作计入本机事务日志;
- 各参与者向协调者反馈事务问询的响应。成功执行返回Yes,否则返回No。
阶段二:执行事务提交
协调者在阶段二决定是否最终执行事务提交操作。这一阶段包含两种情形:
- 发送提交请求。协调者向所有参与者发送Commit请求;
- 事务提交。参与者收到Commit请求后,会
正式执行事务提交操作 ,并在完成提交操作之后,释放在整个事务执行期间占用的资源; - 反馈事务提交结果。参与者在完成事务提交后,写协调者发送Ack消息确认;
- 完成事务。协调者在收到所有参与者的Ack后,完成事务。
阶段二:中断事务
事情总会出现意外,当存在某一参与者向协调者发送No响应,或者等待超时。协调者只要无法收到所有参与者的Yes响应,就会中断事务。
- 发送回滚请求。协调者向所有参与者发送Rollback请求;
- 回滚。参与者收到请求后,利用本机Undo信息,执行Rollback操作。并在回滚结束后释放该事务所占用的系统资源;
- 反馈回滚结果。参与者在完成回滚操作后,向协调者发送Ack消息;
- 中断事务。协调者收到所有参与者的回滚Ack消息后,完成事务中断。
2pc解决的是分布式数据强一致性问题
顾名思义,两阶段提交在处理分布式事务时分为两个阶段:voting(投票阶段,有的地方会叫做prepare阶段)和commit阶段。
2pc中存在两个角色,事务协调者(seata、atomikos、lcn)和事务参与者,事务参与者通常是指应用的数据库。
2PC二阶段提交的特点
2PC方案比较适合单体应用
2PC 方案中,有一个事务管理器的角色,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。
2PC 方案比较适合单体应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。
2PC 方案实际很少用,一般来说某个系统内部如果出现跨多个库的这么一个操作,是不合规的。我可以给大家介绍一下, 现在微服务,一个大的系统分成几百个服务,几十个服务。一般来说,我们的规定和规范,是要求每个服务只能操作自己对应的一个数据库。
如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务架构的规范,你随便交叉胡乱访问,几百个服务的话,全体乱套,这样的一套服务是没法管理的,没法治理的,可能会出现数据被别人改错,自己的库被别人写挂等情况。
如果你要操作别人的服务的库,你必须是通过调用别的服务的接口来实现,绝对不允许交叉访问别人的数据库。
2PC具有明显的优缺点:
优点主要体现在实现原理简单; 缺点比较多:
- 2PC的提交在执行过程中,所有参与事务操作的逻辑都处于阻塞状态,也就是说,各个参与者都在等待其他参与者响应,无法进行其他操作;
- 协调者是个单点,一旦出现问题,其他参与者将无法释放事务资源,也无法完成事务操作;
- 数据不一致。当执行事务提交过程中,如果协调者向所有参与者发送Commit请求后,发生局部网络异常或者协调者在尚未发送完Commit请求,即出现崩溃,最终导致只有部分参与者收到、执行请求。于是整个系统将会出现数据不一致的情形;
- 保守。2PC没有完善的容错机制,当参与者出现故障时,协调者无法快速得知这一失败,只能严格依赖超时设置来决定是否进一步的执行提交还是中断事务。
实际上分布式事务是一件非常复杂的事情,两阶段提交只是通过增加了事务协调者(Coordinator)的角色来通过2个阶段的处理流程来解决分布式系统中一个事务需要跨多个服务节点的数据一致性问题。但是从异常情况上考虑,这个流程也并不是那么的无懈可击。
假设如果在第二个阶段中Coordinator在接收到Partcipant的"Vote_Request"后挂掉了或者网络出现了异常,那么此时Partcipant节点就会一直处于本地事务挂起的状态,从而长时间地占用资源。当然这种情况只会出现在极端情况下,然而作为一套健壮的软件系统而言,异常Case的处理才是真正考验方案正确性的地方。
总结一下: XA-两阶段提交协议中会遇到的一些问题
从流程上我们可以看得出,其最大缺点就在于它的执行过程中间,节点都处于阻塞状态。各个操作数据库的节点此时都占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知进行全局提交,参与者进行本地事务提交后才会释放资源。这样的过程会比较漫长,对性能影响比较大。
事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,会导致参与者收不到提交或回滚的通知,从而导致参与者节点始终处于事务无法完成的中间状态。
在第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就会导致节点间数据的不一致问题。
3PC
针对2PC的缺点,研究者提出了3PC,即Three-Phase Commit。
作为2PC的改进版,3PC将原有的两阶段过程,重新划分为CanCommit、PreCommit和do Commit三个阶段。
详解:三个阶段
阶段一:CanCommit
- 事务询问。协调者向所有参与者发送包含事务内容的canCommit的请求,询问是否可以执行事务提交,并等待应答;
- 各参与者反馈事务询问。正常情况下,如果参与者认为可以顺利执行事务,则返回Yes,否则返回No。
阶段二:PreCommit
在本阶段,协调者会根据上一阶段的反馈情况来决定是否可以执行事务的PreCommit操作。有以下两种可能:
- 发送预提交请求。协调者向所有节点发出PreCommit请求,并进入prepared阶段;
- 事务预提交。参与者收到PreCommit请求后,会执行事务操作,并将Undo和Redo日志写入本机事务日志;
- 各参与者成功执行事务操作,同时将反馈以Ack响应形式发送给协调者,同事等待最终的Commit或Abort指令。
- 发送中断请求。 协调者向所有参与者发送Abort请求;
- 中断事务。无论是收到协调者的Abort请求,还是等待协调者请求过程中出现超时,参与者都会中断事务;
阶段三:doCommit
在这个阶段,会真正的进行事务提交,同样存在两种可能。
- 发送提交请求。假如协调者收到了所有参与者的Ack响应,那么将从预提交转换到提交状态,并向所有参与者,发送doCommit请求;
- 事务提交。参与者收到doCommit请求后,会正式执行事务提交操作,并在完成提交操作后释放占用资源;
- 反馈事务提交结果。参与者将在完成事务提交后,向协调者发送Ack消息;
- 完成事务。协调者接收到所有参与者的Ack消息后,完成事务。
-
发送中断请求。协调者向所有的参与者发送abort请求;
-
事务回滚。参与者收到abort请求后,会利用阶段二中的Undo消息执行事务回滚,并在完成回滚后释放占用资源;
-
反馈事务回滚结果。参与者在完成回滚后向协调者发送Ack消息;
-
中端事务。协调者接收到所有参与者反馈的Ack消息后,完成事务中断。
2PC和3PC的区别:
3PC有效降低了2PC带来的参与者阻塞范围,并且能够在出现单点故障后继续达成一致; 但3PC带来了新的问题,在参与者收到preCommit消息后,如果网络出现分区,协调者和参与者无法进行后续的通信,这种情况下,参与者在等待超时后