分布式事务工业界的解决方案

2023-12-10 08:38

本文主要是介绍分布式事务工业界的解决方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

数据库事务的四大特性ACID:A(Atomic)原子性、C(Consistency)一致性、I(Isolation)隔离性、D(Durability)持久性。

而分布式事务无法满足ACID,针对分布式事务有CAP理论和Base理论。

CAP是Consistency、Availability、Partition tolerance的缩写,分别表示一致性、可用性、分区容错性。分布式事务的P是必须要有的,但在所有分布式事务场景中不能同时具备CAP三个特性,因为在具备了P的前提下C和A是不能共存的。AP是很多分布式系统设计时的选择,而CP是涉及金额时所选用的方案.

BASE是Basically Available、Soft state和Eventually consistent的缩写。BASE理论是对CAP中AP的一个扩展。允许数据在一段时间内是不一致的,但最终达到一致状态。满足BASE理论的事务,我们称之为柔性事务.

分布式事务的解决方案:

1、使用seata实现2pc事务

(1)每个数据库都需要创建undo_log表,此表是seata保证本地事务一致性的关键

在各个库中创建undo_log表,此表为seata框架使用

 CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL, `log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

(2)启动TC(事务协调器)

[seata服务端解压路径]/bin/seata-server.bat -p 8888 -m file

(3)创建代理数据源

@Configuration
public class DatabaseConfiguration {@Bean@ConfigurationProperties(prefix = "spring.datasource.ds0") public DruidDataSource ds0() {DruidDataSource druidDataSource = new DruidDataSource(); return druidDataSource;}@Primary@Beanpublic DataSource dataSource(DruidDataSource ds0)  {DataSourceProxy pds0 = new DataSourceProxy(ds0);return pds0;}
}

(4)全局事务开始使用@GlobalTransactional标识,每个本地事务方案仍然使用@Transactional标识

下面是bank1的账户A向bank2的账户B转账
在这里插入图片描述
在这里插入图片描述

2、TCC方案

tcc即try-confirm-cancel。tcc即自己写代码来实现事务的提交与回滚,此方案需要理解业务逻辑,而且tcc实现难度大,业务逻辑不同,tcc的实现也不同,没有统一的实现方案。
TCC需要注意三种异常处理分别是空回滚、幂等、悬挂。

空回滚:在没有调用TCC资源try方法的情况下,调用了二阶段的cancel方法,cancel方法需要识别出这是一个空回滚,然后直接返回成功。出现原因是当一个分支事务所在服务宕机或网络异常,分支事务调用记录为失败,这时候其实没有执行try阶段,当故障恢复后,分布式事务进行回滚则会调用二阶段的cancel方法,从而形成空回滚。

幂等:为了保证tcc二阶段提交重试机制不会引发数据不一致,要求tcc的二阶段try、confirm和cancel接口保证幂等,这样不会重复使用或者释放资源,如果幂等控制没有做好,很可能导致数据不一致等严重问题

悬挂:悬挂就是对于一个分布式事务,其二阶段cancel接口比try接口先执行。出现原因是在rpc调用分支事务try时,先注册分支事务,再执行rpc调用,如果此时rpc调用的网络发生拥堵,通常rpc调用是有超时时间的,rpc超时后,tm就会通知rm回滚该分布式事务,可能回滚完成后,rpc请求才到达参与者真正执行,而一个try方法预留的业务资源,只有该分布式事务才能使用,该分布式事务第一阶段预留的业务资源就再也没有人能够处理,对于这种情况,我们称为悬挂,即业务资源预留后没法继续处理。解决思路是如果二阶段执行完成,那一阶段就不能再继续执行。

示例:账户A向账户B转账

账户A
try:检查余额是否够30元扣减30元
confirm:空
cancel:增加30元
========================================================================
账户B
try:增加30元
confirm:空
cancel:减少30元

上述方案分析:

  1. 如果账户A的try没有执行在cancel则多加30元
  2. 由于try,cancel,confirm都是由单独的线程去调用,且会出现重复调用,所以都需要实现幂等
  3. 账号B在try中增加30元,当try执行完成后可能会其它线程给消费了,如果分布式事务异常,需要回滚,导致cancel阶段,金额不足以扣30元
  4. 如果账户B的try没有执行在cancel则多减30元

问题解决:

  1. 账户A的cancel方法需要判断try方法是否执行,正常执行try后方可执行cancel
  2. try,cancel,confirm方法实现幂等
  3. 账号B在try方法中不允许更新账户金额,在confirm中更新账户金额
  4. 账户B的cancel方法需要判断try方法是否执行,正常执行try后方可执行cancel

优化方案:

账户A
try:try幂等校验try悬挂处理检查余额是否够30元扣减30元
confirm:空
cancel:cancel幂等校验cancel空回滚处理增加可用余额30元
=============================================================
账户B
try:空
confirm:confirm幂等校验正式增加30元
cancel:空

3、可靠消息最终一致性

可靠消息最终一致性方案是指当事务发起方执行完本地事务后发出一条消息,事务参与方(消息消费者)一定能够接收消息并处理事务成功,此方案强调的是只要消息发给事务参与方最终事务要达到一致。此方案一般通过消息中间件实现。
此方案要解决以下几个问题:

(1)本地事务与消息发送的原子性问题

事务发起方在本地事务执行成功后消息必须发出去,否则就丢弃消息,即实现本地事务和消息发送的原子性,要么都成功,要么都失败。本地事务与消息发送的原子性是实现可靠消息最终一致性方案的关键问题。

第一种方案:

begin transaction;// 1、发送MQ// 2、数据库操作
commit transaction;

上述方案,可能会出现消息发送成功,数据库操作失败,不可取

第二种方案

begin transaction;// 1、数据库操作// 2、发送MQ
commit transaction;

上述方案,MQ发送失败,抛出异常事务回滚,但如果是超时异常,数据库回滚,但MQ其实是已经发送了,同样会导致数据不一致

(2)事务参与方接收消息的可靠性

事务参与方必须能够从消息队列接收到消息,如果接收消息失败可以重复接收消息

(3)消息重复消费问题

若某一个消费节点超时但消费成功,此时消息中间件会重复投递消息,需要事务参与方的方法幂等性

解决方案1:本地消息表方案

通过本地事务保证数据业务操作和消息的一致性,然后通过定时任务将消息发送至消息中间件,待确认消息发送给消费方成功再将消息删除。
举例:
在这里插入图片描述
(1)将新增用户和增加积分消息日志处于同一事务,间接保证了新增用户和发送增加积分消息进MQ的原子性
(2)定时任务扫描日志,扫描日志表中的消息并发送到消息中间件,在消息中间件反馈发送成功后删除该消息日志,否则等待定时任务下一周期重试
(3)使用MQ的ack(即消息确认)机制来保证消费者一定能消费到消息。由于消息会重复投递,消费方需要实现幂等性

解决方案2:RocketMQ事务消息方案

RocketMQ事务消息设计则主要是为了解决Producer端的消息发送与本地事务执行的原子性问题,RocketMQ的设计中的broker与producer端双向通信能力,使得broker天生可以作为一个事务协调者存在。
在这里插入图片描述
(1)Producer(MQ发送方)发送事务至MQ Server,MQ Server将消息状态标记为Prepared状态,注意此时这条消息消费者(MQ订阅方)是无法消费到的

(2)MQ Server接收到Producer发送给的消息则回应发送成功表示MQ已接收到消息

(3)Producer端执行业务代码逻辑,通过本地数据库事务控制

(4)若Producer本地事务执行成功则自动向MQ Server发送commit消息,MQ Server接收到commit消息后将“增加积分消息"状态标记为可消费,此时MQ订阅方(积分服务)即正常消费消息。若Producer本地事务执行失败则自动向MQ Server发送rollback消息,MQ Server接收到rollback消息后将删除"增加积分消息"。
MQ订阅方消费消息,消费成功则向MQ回应ack,否则将重复接收消息,这里ack是自动回应,即程序执行正常则自动回应ack

(5)事务回查,如果Producer执行本地事务中,执行端挂掉或超时,MQ Server将会不停地询问同组的其它Producer来获取事务执行状态。MQ Server会根据事务回查结果来决定是否投递消息。

用户需要实现本地事务执行以及本地事务回查方法

public interface RocketMQLocalTransactionListener {/**
‐ 发送prepare消息成功此方法被回调,该方法用于执行本地事务
‐ @param msg 回传的消息,利用transactionId即可获取到该消息的唯一Id
‐ @param arg 调用send方法时传递的参数,当send时候若有额外的参数可以传递到send方法中,这里能获取到
‐ @return 返回事务状态,COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调
*/RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg);/**
‐ @param msg 通过获取transactionId来判断这条消息的本地事务执行状态
‐ @return 返回事务状态,COMMIT:提交 ROLLBACK:回滚 UNKNOW:回调
*/RocketMQLocalTransactionState checkLocalTransaction(Message msg);
}

4、最大努力通知

举例:
在这里插入图片描述
最大努力通知与可靠消息一致性有什么不同?
(1)解决方案思想不同
可靠消息一致性,发起通知方需要保证将消息发出去,并且将消息发送到接收通知方,消息的可靠性关键由发起通知方来保证
最大努力通知,发起通知方尽最大的努力将业务处理结果通知为接收通知方,但是可能消息接收不到,此时需要接收通知方主动调用发起通知方的接口查询业务处理结果,通知的可靠性在接收通知方

(2)两者的业务应用场景不同
可靠消息一致性关注的是交易过程中的事务一致,以异步方式完成交易
最大努力通知关注的是交易后的通知事务,即将交易结果可靠的通知出去

(3)技术解决方向不同
可靠消息一致性要解决消息从发出到接收的一致性,即消息发出并且被接收到
最大努力通知无法保证消息从发出到接收的一致性,只提供消息接收的可靠性机制。可靠性机制的是,最大努力的将消息通知给接收方,当消息无法被接收方接收时,由接收方主动查询消息(业务处理结果)

最大努力通知有两个方案
方案1:
在这里插入图片描述
方案2:
在这里插入图片描述
方案1中接收通知方与MQ接口,即接收通知方监听MQ,此方案主要应用于内部应用之间的通知
方案2中由通知程序与MQ接口,通知程序监听MQ,收到MQ的消息后由通知程序通过互联网接口协议调用接收通知方。此方案主要应用于外部应用之间的通知,例如支付宝、微信的支付结果通知

这篇关于分布式事务工业界的解决方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/476587

相关文章

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

SpringKafka消息发布之KafkaTemplate与事务支持功能

《SpringKafka消息发布之KafkaTemplate与事务支持功能》通过本文介绍的基本用法、序列化选项、事务支持、错误处理和性能优化技术,开发者可以构建高效可靠的Kafka消息发布系统,事务支... 目录引言一、KafkaTemplate基础二、消息序列化三、事务支持机制四、错误处理与重试五、性能优

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

找不到Anaconda prompt终端的原因分析及解决方案

《找不到Anacondaprompt终端的原因分析及解决方案》因为anaconda还没有初始化,在安装anaconda的过程中,有一行是否要添加anaconda到菜单目录中,由于没有勾选,导致没有菜... 目录问题原因问http://www.chinasem.cn题解决安装了 Anaconda 却找不到 An

Spring定时任务只执行一次的原因分析与解决方案

《Spring定时任务只执行一次的原因分析与解决方案》在使用Spring的@Scheduled定时任务时,你是否遇到过任务只执行一次,后续不再触发的情况?这种情况可能由多种原因导致,如未启用调度、线程... 目录1. 问题背景2. Spring定时任务的基本用法3. 为什么定时任务只执行一次?3.1 未启用

MySQL新增字段后Java实体未更新的潜在问题与解决方案

《MySQL新增字段后Java实体未更新的潜在问题与解决方案》在Java+MySQL的开发中,我们通常使用ORM框架来映射数据库表与Java对象,但有时候,数据库表结构变更(如新增字段)后,开发人员可... 目录引言1. 问题背景:数据库与 Java 实体不同步1.1 常见场景1.2 示例代码2. 不同操作

java常见报错及解决方案总结

《java常见报错及解决方案总结》:本文主要介绍Java编程中常见错误类型及示例,包括语法错误、空指针异常、数组下标越界、类型转换异常、文件未找到异常、除以零异常、非法线程操作异常、方法未定义异常... 目录1. 语法错误 (Syntax Errors)示例 1:解决方案:2. 空指针异常 (NullPoi

redis+lua实现分布式限流的示例

《redis+lua实现分布式限流的示例》本文主要介绍了redis+lua实现分布式限流的示例,可以实现复杂的限流逻辑,如滑动窗口限流,并且避免了多步操作导致的并发问题,具有一定的参考价值,感兴趣的可... 目录为什么使用Redis+Lua实现分布式限流使用ZSET也可以实现限流,为什么选择lua的方式实现

使用DrissionPage控制360浏览器的完美解决方案

《使用DrissionPage控制360浏览器的完美解决方案》在网页自动化领域,经常遇到需要保持登录状态、保留Cookie等场景,今天要分享的方案可以完美解决这个问题:使用DrissionPage直接... 目录完整代码引言为什么要使用已有用户数据?核心代码实现1. 导入必要模块2. 关键配置(重点!)3.