一、概述
1.1 定义
为了解决java 多个节点之间数据一致性
问题。产生的核心原因
是:资源存储的分布性
。比如多个数据库,或者Mysql和Redis的数据一致性等。
1.2 产生场景
- 跨JVM进程产生分布式事务。即服务A和服务B分别有对应的数据库
- 跨数据库实例产生分布式事务。即单体应用系统访问不同的数据库
- 多服务访问同一数据库。
1.3 基础理论模型
1.3.1 CAP理论
Consistence(一致性): 数据在多个副本中要保持强一致性。一般指的是写操作后的读操作可以读取到最新的
数据状态
具体实现:
1. 写入主数据库后将数据同步到从数据库;
2. 写入主数据库后, 在向从数据库同步期间要将数据库锁定,待同步完成后再释放锁,以免在数据库同步期间
读取到旧数据
特点:
1. 由于数据库存在同步过程,写操作的响应会存在一定的延迟;
2. 为了保证数据的强一致性,会锁住数据库资源一段时间
3. 如果请求数据同步失败的节点则会返回错误信息,一定不会返回旧数据
Availability(可用性): 系统对外提供的服务必须一致处于可用状态,在任何故障下,客户端都能在合理时间
内获取到服务端非错误响应, 即不会出现响应超时或响应错误
具体实现:
1. 写入数据库后将数据同步到从数据库;
2. 由于要保证系统可用性,不可将数据库资源进行锁定
3. 即使数据还没有同步过来,也需要返回一个数据,那怕是旧数据
特点:所有请求都有响应,且不会出现响应超时或响应错误
Partition tolerance(分区容错性): 在分布式系统中遇到任何网络分区故障,系统仍然能够对外提供服务
具体实现:添加从数据库节点
特点:分区容忍性是分布式系统具备的基本能力
CAP组合方式:
1. CP方式: zookeeper就是追求强一致性。例如: 跨行转账
2. AP方式: 用户可以接受查询到的数据在一定时间内不是最新的即可. 例如: 订单退款
1.3.2 Base理论
是对AP的一个扩展。核心思想通过牺牲数据的强一致性来获得高可用性。有如下三个特性:
1. Basically Available(基本可用): 分布式系统在出现故障时,允许损失一部分功能的可用性,保证核心
功能的可用性。例如: 电商网站中退款服务出现问题,但商品可以正常浏览
2. Soft stata(软状态): 允许系统中的数据存在中间状态,也就是允许系统中不同节点的数据副本之间的同步
存在延时。例如: 订单的支付中, 数据同步中等状态
3. Eventually Consistent(最终一致性): 中间状态的数据经过一定时间后,会达到最终数据的一致性。
例如: 订单的支付中 最终变为 支付成功或支付失败
二、解决方案
2.1 基于CP的分布式事务解决机制方案
2.1.1 XA方案
基本采用java 2PC理论模型
实现,分为准备Prepare阶段
和提交Commit阶段
。
准备阶段:
TM(事务管理器)给每个RM(资源管理器)发送Prepare消息,每个RM执行本地事务,并写本地的Undo日志和Redo日志,但不提交
,此时需要锁住资源;
提交阶段:
TM若收到RM发送的执行失败或超时信息,则给每个RM发送回滚信息;否则发送提交信息,并释放锁。
优缺点:
优点:
- 保证了数据的强一致性
缺点:
同步阻塞
。所有的事务都处于同步阻塞状态,占用的锁资源会一致被锁定;过于保守
。只要有一个节点出现问题,都会回滚;TM的单点故障
。若处于Commit阶段,那么RM将一直处于锁定状态;数据不一致问题【脑裂】
。若TM向所有RM发送完消息后,中间由于网络问题,部分RM没有收到Commit消息,会导致数据不一致问题;效率低下
。性能与本地事务相差10倍。
2.1.2 三阶段提交协议
是二阶段协议的优化版本, 利用超时机制
解决了同步阻塞问题。具体步骤为:
CanCommit(询问阶段):
事务协调者向参与者发送事务执行请求, 询问是否可以完成指令,参与者只需回答是或不是即可,不需要执行真正的事务操作,这个阶段会有超时中止机制。Prepare(准备阶段):
与两阶段一样,但增加了超时提交机制,一旦出现超时情况, 会继续提交事务。默认情况下认为成功的可能性很大DoCommit(提交或回滚阶段):
与两阶段一样。
2.2 基于Base理论的分布式事务解决方案
2.2.1 TCC事务补偿
将一个完整业务拆分为一下三个阶段:
Try阶段:
做业务检查和资源预留。主要完成业务逻辑Confirm阶段:
Try阶段所有分支事务执行成功后开始执行Confirm。若Confirm执行失败,需要引入重试机制或人工处理Cancel阶段:
在业务执行错误需要回滚下执行分支事务的业务取消。若Cancel执行失败,需要引入重试机制或人工处理
主要存在以下三种问题:
空回滚:
在没有调用Try情况下,直接调用Cancel方法,会产生空回滚。产生原因: 一个分支事务出现宕机或网络异常,导致没有执行Try阶段,分支事务调用记录为失败。当故障恢复后,会调用Cancel方法,形成空回滚。解决思路: 利用分支事务记录表,Cancel方法中判断Try阶段是否有执行记录
幂等:
二阶段Confirm或Cancel提交重试机制会引发数据不一致,因此,需要保证二阶段Confirm或Cancel的幂等型,这也被称为最终一致性事务的原因
悬挂:
二阶段Concel比Try阶段先执行。执行Try方时通过RPC调用,RPC调用产生阻塞,导致事务超时回滚执行Cancel,但过一段时间后RPC请求达到执行Try方法。解决方案: 执行Try方法时,通过事务记录表判断Cancel方法是否已经执行。
2.2.2 基于可靠性消息的最终一致性
核心: 指的是事务发起方完成本地事务并发出一条消息
,事务接收方(消息消费者) 一定能够接受消息并处理事务成功
主要面临以下三个问题:
- 本地事务和消息发送的原子性问题;
- 事务方接受消息的可靠性
- 消息重复消费的问题
2.2.2.1 基于本地消息表
核心: 利用本地事务保证业务数据和消息的一致性
基本原则:
- 采用Base原理,保证事务的最终一致性;
- 在一致性方面,允许一段时间内的不一致,但最终一致
- 在实际业务开发中,要根据实际业务来决定是否采用
主要流程:
- 基于本地消息表的方案,将本事务外的操作,记录在消息表中;
- 使用定时任务轮询本地消息表,将将未执行的消息发送给其他事务操作接口;
- 若操作成功,则定时任务会修改消息状态;否则,会重试
- 增加重试机制,设置重试次数,一旦重试次数超过阈值,则需人工介入处理。
2.2.2.2 基于MQ的最终一致性
1. producer向broker发送half消息,consumer不消费; -- 用于确认broker是正常的;
2. 收到broker返回的响应后,执行本地事务;
3. 若本地事务执行成功, producer将本地事务执行成功的消息发送给broker,此事允许consumer消费消息
4. 若producer出现网络故障\宕机\网络超时等, broker未收到二次确认消息,则broker发送请求给producer进行消息回查。根据消息回查结果进行事务提交或事务回滚操作
2.2.2.3 最大努力通知型事务
核心: 发起通知方通过一定机制最大努力
将业务处理结果通知到接收方。举例如下:充值,涉及账户系统(接受通知方)和充值系统(发送通知方)。
1. 账户系统记录充值信息,并将充值状态设为充值中;
2. 调用支付充值系统,充值系统完成充值后,向账户系统发送充值结果;
3. 若发送失败,则重复发送。发送一定次数后不发送数据;-- 消息重复通知机制
4. 若发送成功,接受通知方收到消息,将充值状态修改为充值成功,并返回一个处理状态给发送通知方。
5. 若接受通知方没有收到消息,则调用发送通知方提供的接口来查询充值情况。 -- 消息校对机制
可以发现,不需要任何的数据回滚。因为充值信息并不影响用户的金钱,只是增加了一条充值失败的信息,是可以接受的。
三、分布式事务对比
1. 2PC:需要阻塞,适用于强一致性且并发量低的系统。例如跨行转账;
2. TCC事务:相比于2PC,2PC通常是在跨库层面,TCC在应用层面,因此,TCC可以在应用层自己定义操作数据的粒度,降低锁冲突,提高吞吐量。但对代码的侵入性比较强,一方面每个事务分支都需要实现try\confirm\concel操作,同时根据不同的故障类型提供不同的回滚策略。
3. 可靠性消息:适合执行【周期长且实时性不高】的场景。引入消息机制后,同步的事务变成异步操作,并实现了两个服务的解耦。
4. 最大努力通知型:适用于一些【最终一致性且时间敏感度低】的业务,可用于外部系统。例如:银行通知、支持结果通知