Dubbo生态之初识分布式事务

1.分布式事务简介

传统的关系型数据库只能保证单个数据库中多个数据表的事务特性。一旦多个SQL操作涉及到多个数据库,这类的事务就无法解决跨库事务问题。在传统架构下,这种问题出现的情况非常少,但是在分布式微服务架构中,分布式事务的问题变得更加突出。

举例,假设我们要涉嫌下面电商系统中的支付功能。

 分析: 上图中有3个服务 支付服务、资金服务和红包服务

当用户发起支付时,就会涉及到几个事务操作

  • 创建支付订单
  • 从资金服务中扣除余额
  • 从红包服务中扣除余额
  • 更新支付结果
这是四个典型的事务操作,而且这些操作分别属于不同的数据库,最终期望 的结果是希望这三个服务所对应的数据是一致的,很显然传统的事务无法解 决这个问题!

 于是,分布式事务就诞生了。说到分布式事务,我们就不得不提以下X/OpenDTP事务模型

2.X/OpenDTP事务模型

这个事务模型定义了一套分布式事务的标准,也就是定义了规范和API接口,并且这个标准提出了二阶段提交(2PC-Two-Phase-Commit)来保证分布式事务的完整性。

2.1 二阶段提交模型

那么什么是二阶段提交2PC协议呢?

如上图所示, 在分布式事务中,多个小事务的提交与回滚,只有当前进程知道,其它进程是不清楚的。而为了实现多个数据库的事务一致性,就必然需要引入第三方节点来进行事务协调,如下图所示,

通过一个全局的分布式事务协调,从而来实现多个数据库事务的提交与回滚,在这样的架构下,事务的管理方式就变成了两个步骤。

a.开启事务并向各个数据库节点写入事务日志

b.根据第一个步骤中各个节点的执行结果,来决定对事务进行提交或回滚。

 这就是所谓的2PC提交协议,用图来表示为如下:

 

 2PC的提交流程如下:

1. 表决阶段:此时 TM(协调者)向所有的参与者发送一个 事务请求,参与者在收到这请求后,如果准备好了(写事务日志)就会向 TM发送一个 执行成功 消息作为回应,告知 TM 自己已经做好了准备,否则会返回一个 失败 消息;
2. 提交阶段:TM 收到所有参与者的表决信息,如果所有参与者一致认为可以提交事务,那么 TM就会发送 提交 消息,否则发送 回滚 消息;对于参与者而言,如果收到 提交 消息,就会提交本地事务,否则就会取消本地事务。 

2.2 X/OpenDTP事务模型 

基于对上述2PC的了解,我们再来看下X/OpenDTP事务模型,如下图所示

 如上图所示,X/OpenDTP模型定义了三个角色和两个协议,其中三个角色为

AP(Application Program),表示应用程序,也可以理解成使用DTP模型的程序
RM(Resource Manager),资源管理器,这个资源可以是数据库, 应 用程序通过资源管理器对资源进行控制,资源管理器必须实现XA定义的接口
TM(Transaction Manager),表示事务管理器,负责协调和管理全局事务,事务管理器控制整个全局事务,管理事务的生命周期,并且协调资源。

两个协议分别是

 XA协议: XA 是X/Open DTP定义的资源管理器和事务管理器之间的接口 规范,TM用它来通知和协调相关RM事务的开始、结束、提交或回滚。目前Oracle、Mysql、DB2都提供了对XA的支持; XA接口是双向的系统接口,在事务管理器(TM)以及多个资源管理器之间形成通信的桥梁(XA不 能自动提交)

XA {START|BEGIN} xid [JOIN|RESUME] --负责开启或者恢复一个事务分支,并且管理XID到调用线程
XA END xid [SUSPEND [FOR MIGRATE]] --负责取消当前线程与事务分支的关联
XA PREPARE xid --负责询问RM 是否准备好了提交事务分支
XA COMMIT xid [ONE PHASE] --知RM提交事务分支
XA ROLLBACK xid --通知RM回滚事务分支
XA RECOVER [CONVERT XID]

 TX协议: 全局事务管理器与资源管理器之间通信的接口

在分布式系统中,每一个机器节点虽然都能够明确知道自己在进行事务操作过程中的结果是成功还是失败,但却无法直接获取到其他分布式节点的操作结果。因此当一个事务操作需要跨越多个分布式节点的时候,为了保持事务处理的ACID特性,就需要引入一个“协调者”(TM)来统一调度所有分布式节点的执行逻辑,这些被调度的分布式节点被称为AP。TM负责调度AP的行为,并最终决定这些AP是否要把事务真正进行提交到(RM)

 2.3 基于XA协议的2PC提交流程

二阶段提交,是计算机网络尤其是在数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务处理过程中能够保持原子性和一致性而设计的一种算法。通常,二阶段提交协议也被认为是一种一致性协议,用来保证分布式系统数据的一致性。目前,绝大部分的关系型数据库都是采用二阶段提交协议来完成分布式事务处理的,利用该协议能够非常方便地完成所有分布式事务AP的协调,统一决定事务的提交或回滚,从而能够有效保证分布式数
据一致性,因此2pc也被广泛运用在许多分布式系统中。

 

整体流程如上图所示,下面为演示Mysql使用XA实现分布式事务提交:

-- 启动一个XA事务 (xid 必须是一个唯一值; [JOIN|RESUME] 字句不被支持)
xa start 'xatest1';
insert into ums_user(username,password) values('test1','test1');
-- 结束一个XA事务 ( [SUSPEND [FOR MIGRATE]] 字句不被支持)
xa end 'xatest1';
-- 准备 此动作会把这个事务的redo日志写入innodb redo log,只要这一阶段是成功的,那么后续XACommit一定会成功
xa prepare 'xatest1';
XA COMMIT 'xatest1'; //提交事务
-- 或者回滚代码,如果xa prepare这个环节出现错误,事务协调者就把这个事务回滚。
xa rollback 'xatest'; //回滚

 但是在这个过程中,数据库需要提供针对分布式事务处理的接口给到事务管理器,这样事务管理器就能够基于这些接口来协调各个资源的事务提交和回滚操作。也就是说,数据库层面必须要支持。

2.4 基于XA协议的开源框架

主流的数据库如Oracle、mysql都支持XA协议,因此都可以基于xa协议规范,通过二阶段提交来实现数据的一致性。

J2EE就遵循了这些规范,设计还并实现了Java里面的分布式事务编程接口规范-JTA,我们可以利用这些API来完成各个数据库的事务一致性处理。

但是,在XA事务中,根据前面我们的操作过程可以发现,另外,在XACOMMIT阶段,如果其中一个RM因为网络超时没有收到数据提交的指令,会导致数据不一致。
所以如果我们要基于这些API来实现一个比较成熟的分布式事务解决方案, 还需要考虑到这些问题并提出解决方案。比如针对这个问题,我们可以采用 重试的机制来完成数据一致性。

 因此,针对这类分布式事务解决方案的开源框架也很多,比如

Atomikos,Atomikos是为Java平台提供的开源的事务管理工具,它包含收费和开源两个版本,开源版本基本能满足我们的需求。
Bitronix,是一个流行的开源JTA事务管理器实现,你可以使用spring-boot-starter-jta-bitronixstarter为项目添加合适的Birtronix依赖。
Seata事务,阿里巴巴开源的事务解决方案

3.分布式事务解决方案

对于我们的分布式事务来说,存在着网络通信的不确定性,比如当分布式事务中的其中一个节点故障,导致无法提交的情况下,我们究竟是抛弃呢? 还是等待?如果是抛弃,那么我们的数据强一致性就无法保证,但是如果是等待,又容易造成大量线程阻塞,系统性能严重受损。换句话说,我们是要保证可用性呢还是一致性,二者只能选其一。

3.1 CAP理论

CAP理论说的就是分布式架构下的数据一致性和性能问题的平衡方案,

C:Consistency 一致性 同一数据的多个副本是否实时相同。
A:Availability 可用性 可用性:一定时间内 & 系统返回一个明确的结果则称为该系统可用。
P:Partition tolerance 分区容错性 将同一服务分布在多个系统中,从而保证某一个系统宕机,仍然有其他系统提供相同的服务。

CAP理论告诉我们,在分布式系统中,C,A,P三个条件中我们最多只能选择两个,那么问题就来了,我们究竟选择那两个条件较为合适呢?

 对于一个业务系统来说,可用性和分区容错性是必须要满足的两个条件,并且这两者是相辅相成的。业务系统之所以使用分布式系统,主要原因有两个 :

提升整体性能 当业务量猛增,单个服务器已经无法满足我们的业务需求的时候,就需要使用分布式系统,使用多个节点提供相同的功能,从而整体上提升系统的性能,这就是使用分布式系统的第一个原因。
实现分区容错性 单一节点 或 多个节点处于相同的网络环境下,那么会存在一定的风险,万一该机房断电、该地区发生自然灾害,那么业务系统就全面瘫痪了。为了防止这一问题,采用分布式系统,将多个子系统分布在不同的地域、不同的机房中,从而保证系统高可用性。

 这说明了分区容错性是分布式系统的根本,如果分区容错性不能满足,那么使用分布式系统将失去意义。

此外,可用性对业务系统也尤为重要。在大谈用户体验的今天,如果业务系统时常出现'系统异常',响应时间过长等情况,这就使得用户对系统的好感度大打折扣。

在互联网行业竞争激烈的今天,相同领域的竞争者不甚枚举,系统的间歇性 不可用会立马导致用户流向竞争对手。因此,我们只能通过牺牲一致性来换取系统的 可用性 分区容错

 因此,例如Zookeeper中就采用了基于少数服从多数的2pc落地方案

因此,也引出了另外一个理论,叫Base理论

3.2 Base理论

CAP理论告诉我们一个悲惨但不得不接受的实时--我们只能在C,A,P中选择两个条件。而对于业务系统而言,我们往往选择牺牲一致性来换取系统的可用性和分区容错性。不过这里要指出的是,所谓的“牺牲一致性”并不是完全放弃数据一致性,而是牺牲强一致性换取弱一致性。

BA:Basic Available 基本可用
        整个系统在某些不可抗力的情况下,仍然能够保证“可用性”,即一定 时间内仍然能够返回一个明确的结果。只不过“基本可用”和“高可用” 的区别是:
                1.“一定时间”可以适当延长 当举行大促时,响应时间可以适当延长
                2.给部分用户返回一个降级页面 给部分用户直接返回一个降级页面,从而缓解服务器压力。但要注意,返回降级页面仍然是返回明确结果。
S:Soft State:柔性状态 同一数据的不同副本的状态,可以不需要实时 一致。
E:Eventual Consisstency:最终一致性 同一数据的不同副本的状态,可以不需要实时一致,但一定要保证经过一定时间后仍然是一致的。

所以,对于服务来说,也有很多方案可供选择

  • 提供查询服务确认数据状态 
  • 幂等操作对于重发保证数据的安全性
  • TCC事务操作
  • 补偿操作
  • 定期校对

4. 基于可靠性消息的最终一致性方案

最终一致性方案,也称为弱一致性方案

它是基于BASE理论的落地。假设我们要实现一个用户服务里面去实现用户购买,积分服务里面增加用户的积分

@Transactional
public void register(){
    // 用户交易 // local transaction(失败)
    // 发送MQ // mq 成功了 --> 意外获得了积分
}

这样可能存在两种异常情况  用户交易成功,积分失败了 ; 用户交易失败,积分成功了

那么我们怎么要才能保证分布式事务的解决,以及数据的一致性呢。一方面我们要保证事务参与方接收消息的可靠性;另一方面还有就是消息重复消费的问题,消息重复发送怎么保证

4.1 事务参与方接收消息的可靠性

事务参与方接收消息的可靠性可以采用本地事务的方式来解决, 新增一个消息表,来记录MQ的消息发送,发送成功,那么事务同时进行,如果新增用户失败,则回滚,如果消息发送失败了,那就定时任务接着发,但同时也会记录消息表,比如采用一个状态记录成功或失败,定时任务后面可以接着发送

整体交互流程如下:

1. 用户注册 :用户服务在本地事务新增用户和增加 “积分消息日志”。(用 户表和消息表通过本地事务保证一致)下边是伪代码,这种情况下,本 地数据库操作与存储积分消息日志处于同一个事务中,本地数据库操作 与记录消息日志操作具备原子性。
begin transaction;
//1.新增用户
//2.存储积分消息日志
commit transation;

 2. 定时任务扫描日志,在第一步中,我们把需要发送到消息队列的事务消 息保存到了消息日志表,为了保证消息能够百分之百的发送给消息队 列,这里可以启动一个定时任务不断扫描这个消息表中的消息发送到消 息队列。当消息队列反馈发送成功后,删除该消息日志,否则等到下一 个任务周期重试

 3. 消息的可靠性消费,主流的MQ都带了消息确认机制(ack),消费者监 听MQ的消息。

        1. 当消费者受到消息并处理完成后,返回一个ACK给到MQ,告诉MQ 该消息已经消费完成,MQ不需要再向消费者投递该消息。
        2. 否则,MQ会不断重新投递这个消息给到消费者。 当消费者收到“新增积分”消息后,根据该消息的逻辑规则完成指定用户 的积分更新,再基于MQ的ACK机制,从而可以实现可靠的消息投递功能

4.2 消息重复投递的幂等性保障

所谓幂等性,就是MQ重复调用多次产生的业务结果与调用一次产生的业务结果相同。

在分布式架构中,我们调用一个远程服务去完成一个操作,除了成功和失败 以外,还有未知状态,那么针对这个未知状态,我们会采取一些重试的行为;
或者在消息中间件的使用场景中,消费者可能会重复收到消息。对于这两种情况,消费端或者服务端需要采取一定的手段,也就是考虑到重发的情况下保证数据的安全性。一般我们常用的手段:
  • 消息表  用MD5+唯一约束
  • redis SetNx(md5,5min)
  • 状态机  数据的状态(当前状态在状态机中只会存在一次)
  • 上游生成唯一id

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/657393.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Golang | Leetcode Golang题解之第117题填充每个节点的下一个右侧节点指针II

题目: 题解: func connect(root *Node) *Node {start : rootfor start ! nil {var nextStart, last *Nodehandle : func(cur *Node) {if cur nil {return}if nextStart nil {nextStart cur}if last ! nil {last.Next cur}last cur}for p : start; …

NDIS协议驱动(四)

NDIS 定义对象标识符 (OID) 值,以标识适配器参数,其中包括设备特征、可配置设置和统计信息等操作参数。 协议驱动程序可以查询或设置基础驱动程序的操作参数。 NDIS 还为 NDIS 6.1 及更高版本的协议驱动程序提供直接 OID 请求接口。 直接 OID 请求路径支…

5-时间、日期与组合框

时间、日期与组合框 1 日期时间1.1 日期时间相关的类1.2 日期、时间和字符串的转换1.3 例子 2、组合框2.1 QComboBox2.2 QPlainTextEdit2.3 案例 3、自定义右键菜单 1 日期时间 1.1 日期时间相关的类 QTime 时间数据类型,仅表示时间,如:15:…

nano机器人2:机械臂的视觉抓取

前言 参考链接: 【机械臂入门教程】机械臂视觉抓取从理论到实战 GRCNN 通过神经网络,先进行模型训练,在进行模型评估。 机械臂逆运动学求解 所有串联型6自由度机械臂均是可解的,但这种解通常只能通过数值解法得到,计算难度大&am…

Python | Leetcode Python题解之第118题杨辉三角

题目: 题解: class Solution:def generate(self, numRows: int) -> List[List[int]]:ret list()for i in range(numRows):row list()for j in range(0, i 1):if j 0 or j i:row.append(1)else:row.append(ret[i - 1][j] ret[i - 1][j - 1])ret…

如何批量提取pdf文件名?批量提取文件夹里的文件名,只要用对方法!

在数字化时代,PDF文件已经成为我们日常工作中不可或缺的一部分。然而,随着PDF文件数量的不断增加,如何高效地管理这些文件成为了一个挑战。批量提取PDF文件名,就是解决这一问题的关键所在。本文将为你介绍几种实用的方法&#xff…

【Game】Powerful

文章目录 【小伙伴】隐藏小伙伴 【百趣集】【人物属性点】【宠物打造】【奇遇】【钓鱼】 【小伙伴】 刷新位置 小伙伴等级详情 克制关系 隐藏小伙伴 1、仙缘小伙伴(6种) 遇到仙缘驭宠师然后进入战斗抓取 107、七彩仙凤 108、小青兔 109、小布 110、黑腹蛛…

基于jeecgboot-vue3的Flowable增加表单功能(二)

因为这个项目license问题无法开源,更多技术支持与服务请加入我的知识星球。 接上一节 6、增加一个types.ts 类型 export interface FormForm {id: number | string | undefined;formName: string;formContent?: string;remark: string; } 7、api增加一个getForm…

【Java】【python】leetcode刷题记录--双指针

双指针也一般称为快慢指针,主要用于处理链表和数组等线性数据结构。这种技巧主要涉及到两个指针,一个快指针(通常每次移动两步)和一个慢指针(通常每次移动一步)。快指针可以起到’探路‘的作用,…

【Mybatis】映射文件中获取参数的符号#{}和${}的区别

在xml映射文件中获取参数的符号都是用的#{}的方式,其实Mybatis还支持另一种符号来接收传递过来的参数值,就是${},他们是区别就在与底层使用jdbc的statement不一样 #{}对应的是PreparedStatementd对象来执行sql语句 ${}对应的是Statement对象…

C语言-01_HelloWord

文章目录 1.C程序运行机制2.HelloWorld的剖析① main()② 函数体③ printf()④ 标准库、头文件 3.输出3.1 printf()标准格式3.2 占位符3.3 输出格式 1.C程序运行机制 过程1:编辑 编写C语言源程序代码,并已文件的形式存储到磁盘中。源程序文件以“.c”作…

100个 Unity小游戏系列五 -Unity 抽奖游戏专题三老虎机游戏

一、演示效果 二、知识点讲解 2.1 布局 public void CreateItems(SlotsData[] slotsData){isInited false;slotsPrizeList new List<SlotsData>();for (int i 0; i < slotsData.Length; i){var item slotsData[i];slotsPrizeList.Add(item);}float bottomY -it…

AI赋能数字人:打造与语音节奏完美匹配的高质量手势动画

在数字化时代,人机交互正以前所未有的速度进化,而AI数字人的发展正是这一进程中的重要里程碑。近期,一项旨在根据语音内容自动生成匹配手势的技术方案引起了广泛关注,该技术不仅增强了数字人的表现力,也为远程沟通、教育、娱乐等多个领域带来了革新性的应用潜力。本文将深…

手机版AI写作软件哪个好用?5款AI写作软件分享

在这个快节凑的时代&#xff0c;人们对于高效、便捷的创作方式很是追求。尤其是在人工智能技术发展迅速的今天&#xff0c;AI写作软件的出现&#xff0c;让很多自媒体创作者都会想到在手机上面进内容创作&#xff0c;这样不仅能提高工作效率&#xff0c;而且工作的自由度会更高…

APM2.8如何做加速度校准

加速度的校准建议准备一个六面平整&#xff0c;边角整齐的方形硬纸盒或者塑料盒&#xff0c;如下图所示&#xff0c;我们将以它作为APM校准时的水平垂直姿态参考&#xff0c;另外当然还需要一块水平的桌面或者地面 首先用双面泡沫胶或者螺丝将APM主板正面向上固定于方形盒子上&…

农产品产品防伪防窜货+二维码防伪+溯源系统源码全平台一物一码数字化防伪防窜货和溯源查询系统

农产品产品防伪防防窜货二维码防伪溯源系统源码全平台一物一码数字化防伪防窜货和溯源查询系统 产品防伪防防窜货二维码防伪溯源系统源码&#xff0c;该系统采用最简单易用的phpMySQL进行搭建&#xff0c;拥有完善的网站前后台&#xff0c;通过对每件产品生产线上的单品、二级…

【数据结构与算法 经典例题】返回单链表的倒数第 k 个节点

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;数据结构与算法刷题系列&#xff08;C语言&#xff09; 期待您的关注 目录 一、问题描述 二、解题思路 方法一:计数器方式 方法…

leetCode.84. 柱状图中最大的矩形

leetCode.84. 柱状图中最大的矩形 题目思路 代码 class Solution { public:int largestRectangleArea( vector<int>& h ) {int n h.size();vector<int> left( n ), right( n );stack<int> st;// 求每个矩形的第一个小于左边界的矩形 - 用单调栈for ( …

Java基础:面向对象(二)

Java基础&#xff1a;面向对象&#xff08;二&#xff09; 文章目录 Java基础&#xff1a;面向对象&#xff08;二&#xff09;1. 面向对象编程思想2. 类与对象2.1 类2.1.1 类的定义2.1.2 成员变量2.1.3 局部变量 2.2 对象2.2.1 对象的定义2.2.2 对象的使用2.2.3 对象创建的原理…

gmssl vs2010编译

1、虚拟机win10 x64&#xff0c;离线安装vs2010和2010sp1补丁&#xff1b; 2、安装ActivePerl_v5.28.1.0000和nasm-2.16.03-installer-x64均是默认完整安装&#xff1b; nasm官网下载&#xff1a; Index of /pub/nasm/releasebuilds/2.16.03/win64https://www.nasm.us/pub/nas…