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

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

相关文章

集中式版本控制与分布式版本控制——Git 学习笔记01

什么是版本控制 如果你用 Microsoft Word 写过东西,那你八成会有这样的经历: 想删除一段文字,又怕将来这段文字有用,怎么办呢?有一个办法,先把当前文件“另存为”一个文件,然后继续改,改到某个程度,再“另存为”一个文件。就这样改着、存着……最后你的 Word 文档变成了这样: 过了几天,你想找回被删除的文字,但是已经记不清保存在哪个文件了,只能挨个去找。真麻烦,眼睛都花了。看

MySql 事务练习

事务(transaction) -- 事务 transaction-- 事务是一组操作的集合,是一个不可分割的工作单位,事务会将所有的操作作为一个整体一起向系统提交或撤销请求-- 事务的操作要么同时成功,要么同时失败-- MySql的事务默认是自动提交的,当执行一个DML语句,MySql会立即自动隐式提交事务-- 常见案例:银行转账-- 逻辑:A给B转账1000:1.查询

js异步提交form表单的解决方案

1.定义异步提交表单的方法 (通用方法) /*** 异步提交form表单* @param options {form:form表单元素,success:执行成功后处理函数}* <span style="color:#ff0000;"><strong>@注意 后台接收参数要解码否则中文会导致乱码 如:URLDecoder.decode(param,"UTF-8")</strong></span>

开源分布式数据库中间件

转自:https://www.csdn.net/article/2015-07-16/2825228 MyCat:开源分布式数据库中间件 为什么需要MyCat? 虽然云计算时代,传统数据库存在着先天性的弊端,但是NoSQL数据库又无法将其替代。如果传统数据易于扩展,可切分,就可以避免单机(单库)的性能缺陷。 MyCat的目标就是:低成本地将现有的单机数据库和应用平滑迁移到“云”端

Lua 脚本在 Redis 中执行时的原子性以及与redis的事务的区别

在 Redis 中,Lua 脚本具有原子性是因为 Redis 保证在执行脚本时,脚本中的所有操作都会被当作一个不可分割的整体。具体来说,Redis 使用单线程的执行模型来处理命令,因此当 Lua 脚本在 Redis 中执行时,不会有其他命令打断脚本的执行过程。脚本中的所有操作都将连续执行,直到脚本执行完成后,Redis 才会继续处理其他客户端的请求。 Lua 脚本在 Redis 中原子性的原因

明明的随机数处理问题分析与解决方案

明明的随机数处理问题分析与解决方案 引言问题描述解决方案数据结构设计具体步骤伪代码C语言实现详细解释读取输入去重操作排序操作输出结果复杂度分析 引言 明明生成了N个1到500之间的随机整数,我们需要对这些整数进行处理,删去重复的数字,然后进行排序并输出结果。本文将详细讲解如何通过算法、数据结构以及C语言来解决这个问题。我们将会使用数组和哈希表来实现去重操作,再利用排序算法对结果

UE5 半透明阴影 快速解决方案

Step 1: 打开该选项 Step 2: 将半透明材质给到模型后,设置光照的Shadow Resolution Scale,越大,阴影的效果越好

laravel框架实现redis分布式集群原理

在app/config/database.php中配置如下: 'redis' => array('cluster' => true,'default' => array('host' => '172.21.107.247','port' => 6379,),'redis1' => array('host' => '172.21.107.248','port' => 6379,),) 其中cl

基于MySQL实现的分布式锁

概述 在单机时代,虽然不需要分布式锁,但也面临过类似的问题,只不过在单机的情况下,如果有多个线程要同时访问某个共享资源的时候,我们可以采用线程间加锁的机制,即当某个线程获取到这个资源后,就立即对这个资源进行加锁,当使用完资源之后,再解锁,其它线程就可以接着使用了。例如,在JAVA中,甚至专门提供了一些处理锁机制的一些API(synchronize/Lock等)。 但是到了分布式系统的时代,这种

MySQL主从同步延迟原理及解决方案

概述 MySQL的主从同步是一个很成熟的架构,优点为: ①在从服务器可以执行查询工作(即我们常说的读功能),降低主服务器压力; ②在从主服务器进行备份,避免备份期间影响主服务器服务; ③当主服务器出现问题时,可以切换到从服务器。 相信大家对于这些好处已经非常了解了,在项目的部署中也采用这种方案。但是MySQL的主从同步一直有从库延迟的问题,那么为什么会有这种问题。这种问题如何解决呢? MyS