方法五:可靠消息事务
==========
消息事务的原理是 将两个事务通过消息中间件来进行异步解耦。基于可靠消息服务的方案是通过消息中间件来保证上、下游应用数据操作的一致性。假设有 A、B两个服务,分布可以处理 A、B两个任务,此时需要存在一个业务流程,将任务 A和B 放到同一个事物中处理,这种方式就可以借助消息中间件来实现。
整体上可以分为两个大的步骤:A服务向消息中间件发布消息 和 消息向B服务投递消息
步骤一: A 服务向消息中间件发布消息
-
在服务A处理任务A前,首先向消息中间件发送一条半信息
-
消息中间件收到后将该消息持久化,但不进行投递。持久化成功后,向A服务返回确认应答
-
服务A收到确认应答后,便可以开始处理任务A
-
任务A处理完成后,服务A便会向消息中间件发送Commit 或者 Rollback 请求,该请求发送完成后,服务A的工作任务就结束了,该事务的处理过程也就结束了
-
在消息中间件收到 Commit 后,便会向 B 服务投递消息,如果收到 Rollback 便会直接丢弃消息
如果消息中间件在最后的过程中,长时间没有收到服务A 发送的 Commit 或 Rollback 指令,这个时候就需要依靠 超时询问机制
超时询问机制:
服务A除了实现正常的业务流程之外,还是需要提供一个可供消息中间件事务询问的接口。在消息中间件第一次收到消息后便会开始计时,如果超过规定的时间没有收到后续的指令,就会主动调用服务A提供的事务询问接口,询问当前服务的状态,通常来说该接口会返回三种结果,中间件需要根据这三种不同的结果做出不同的处理:
提交:直接将该消息投递给服务B 回滚:直接将该消息丢弃 处理中:继续等待,重新计时
步骤二: 消息中间件向B服务投递消息
消息中间件收到A服务的提交 Commit指令后便会将该消息投递给B服务,然后将自己的状态置为阻塞等待状态。B服务收到消息中间件发送的消息后便开始处理任务B,处理完成后便会向消息中间件发出回应。但是在消息中间件阻塞等待的时候同样会出现问题
-
正常情况:消息中间件投递完消息后,进入阻塞等待状态,在收到确认应答后便认为事务处理完成,该流程结束
-
等待超时情况:在等待确认应答超时之后就会重新进行投递,直到B服务器返回消费成功响应为止。而消息重试的次数和时间间隔都可以设置,如果最终还是不能成功进行投递,则需要人工干预。
可靠消息服务方案是实现了 最终一致性。对比本地消息表实现方案,不需要再建立消息表。不用依赖本地数据库事务,适用于高并发的场景。RocketMQ 就很好的支持了消息事务。
方法四:最大努力通知
==========
最大努力通知也成为定期校对,是对可靠消息服务的进一步优化。它引入了本地消息表来记录错误消息,然后加入失败消息的定期校对功能,来进一步保证消息会被下游服务消费。
同样的这个跟消息事务一样可以分为两步:
步骤一: 服务A向消息中间件发送消息
-
在处理业务的同一个事务中,向本地消息表写入一条记录
-
消息发送者不断取出本地消息表中的消息发送到消息中间件,如果发送失败则进行重试
步骤二: 消息中间件向服务B投递消息
-
消息中间件收到消息后便会将消息投递到下游服务B,服务B收到消息后便会执行自己的业务
-
当服务B业务处理成功后,便会向消息中间件返回反馈应答,消息中间件便可将该消息删除,该流程结束
-
如果消息中间件向服务B投递消息失败,便会尝试重试,如果重试失败,便会将该消息接入失败消息表中
-
消息中间件同样需要提供查询失败消息的接口,服务B 定期
真题解析、进阶学习笔记、最新讲解视频、实战项目源码、学习路线大纲
详情关注公中号【编程进阶路】
查询失败信息,并进行消费
最大努力通知的方案实现比较简单,适用于一些最终一致性要求比较低的业务。
Seata
=====
Seata概念
=======
既然分布式事务处理起来这么麻烦,那能不能让分布式事务处理起来像本地事务那么简单。当然这是我们的愿景。当然这个愿景是所有开发人员所希望的。而阿里巴巴团队就为这个愿景做出了行动,发起了开源项目 Seata(Simple Extensible Autonomous Transaction Architecture) 。这是一套分布式事务解决方案,意在解决开发人员遇到的分布式事务各方面的难题。
Seata 的设计目标是对业务无侵入,因此它是从业务无侵入的两阶段提交(全局事务)着手,在传统的两阶段上进行改进,他把一个分布式事务理解成一个包含了若干分支事务的全局事务。而全局事务的职责是协调它管理的分支事务达成一致性,要么一起成功提交,要么一起失败回滚。也就是一荣俱荣一损俱损~
Seata 组成
========
我们看下 Seata 中存在几种重要角色:
-
TC(Transaction Coordinator):事务协调者。管理全局的分支事务的状态,用于全局性事务的提交和回滚。
-
TM(Transaction Manager):事务管理者。用于开启、提交或回滚事务。
-
RM(Resource Manager):资源管理器。用于分支事务上的资源管理,向 TC 注册分支事务,上报分支事务的状态,接收 TC 的命令来提交或者回滚分支事务。
这是一种很巧妙的设计,我们来看图:
执行流程是这样的:
-
服务A中的 TM 向 TC 申请开启一个全局事务,TC 就会创建一个全局事务并返回一个唯一的 XID
-
服务A中的 RM 向 TC 注册分支事务,然后将这个分支事务纳入 XID 对应的全局事务管辖中
-
服务A开始执行分支事务
-
服务A开始远程调用B服务,此时 XID 会根据调用链传播
-
服务B中的 RM 也向 TC 注册分支事务,然后将这个分支事务纳入 XID 对应的全局事务管辖中
-
服务B开始执行分支事务
-
全局事务调用处理结束后,TM 会根据有误异常情况,向 TC 发起全局事务的提交或回滚
-
TC 协调其管辖之下的所有分支事务,决定是提交还是回滚
Seata使用
=======
我们从上面了解到了 Seata 的组成和执行流程,我们接下来就来实际的使用下 Seata。
示例演示
====
我们简单创建了一个微服务项目,其中有订单服务和库存服务。
我们这里采用了 nacos 作为注册中心,分别启动两个服务,我们在nacos控制台可以看到两个已经注册的服务:
号外:如果对nacos还不熟悉的小伙伴可以跳转查看 nacos讲解:微服务新秀之Nacos
我们接着创建了一个数据库,其中有两张表:c_order 和 c_product,其中商品表中有一条数据,而订单表中还未有数据,接下来我们将要对其进行操作!
我们现在模拟一个下单的过程:
-
请求进来,通过商品 pid 往数据库中查商品的信息
-
创建一条该商品的订单
-
对应扣减该商品的库存量
-
流程结束
我们接下来就进入代码演示一下:
注意:这里 ProductService 并非是库存服务里面的类,而是利用 Feign 远程调用库存服务的接口
代码三步走,正常请况下肯定是没有问题的:
订单生成,库存也对应减少,感觉自己代码可以上线进入正轨的时候,我们来模拟一下库存中的异常,库存商品数量归为 100,订单表清空:
我们继续发送下单请求,可以看到库存服务已经抛出了异常
正常来说这个时候,库存表数量不应该减少,订单表不应该插入订单数据,但是事实真的是这样的吗?我们看数据:
库存数量没减,但是订单却增加了。好了,到这里,你就已经见识到了分布式事务的灾难性危害。接下来主角登场!
Seata 安装
========
我们首先需要点击下载地址进行下载 Seata。
由于我们是使用 nacos 作为服务中心和配置中心,因此我们下载解压后需要做一些修改操作
- 进入 conf 目录编辑 registry.conf 和 file.conf 两个文件,编辑后内容如下:
- 由于新版 Seata 中没有 nacos-conf.sh 和 config.txt 两个文件,因此我们需要独立下载:
nacos-config.sh 下载地址
config.txt 下载地址
我们需要将 config.txt 文件放到 seata 目录下,而非 conf 目录下,并且需要修改 config.txt 内容
config.txt就是seata各种详细的配置,执行 nacos-config.sh 即可将这些配置导入到nacos,这样就不需要将file.conf和registry.conf放到我们的项目中了,需要什么配置就直接从nacos中读取。
- 执行导入
在 conf 目录下打开 git bash 窗口,执行以下命令:
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t namespace-id(需要替换) -u nacos -w nacos
复制代码
操作结束后,我们便可以在 nacos 控制台中看到配置列表,日后配置有需要修改便可以直接从这边修改,而不用修改目录文件:
- 数据库配置
在 1.4.1 最新版中依然没有 sql 文件,所以我们还是需要另外下载:sql 下载地址
在 seata 数据中执行这个文件,生成三张表:
在我们的业务数据库中执行 undo_log 这张表:
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;
复制代码
- 添加 log 文件
如果我们没有log输出文件,启动 seata 可能会报错,因此我们需要在 seata 目录下创建 logs 文件夹,在 logs 文件下创建 seata_gc.log 文件
- 启动
做好了以上准备,我们便可以启动 seata 了,直接在 bin 目录下 cmd 执行 bat 脚本即可,启动结束便可在 nacos 中看到 seata 服务:
Seata 集成
========
在 Seata 安装的步骤中我们便完成了 Seata 服务端 的启动安装,接下来就是在项目中集成 Seata 客户端
- 第一步:我们需要在 pom.xml 文件中添加两个依赖:seata 依赖 和 nacos 配置依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
io.seata
seata-all
io.seata
seata-spring-boot-starter
io.seata
seata-all
1.4.1
io.seata
seata-spring-boot-starter
1.4.1
复制代码
注意: 这里需要排除
spring-cloud-starter-alibaba-seata 自带的 seata 依赖,然后引入我们自己需要的 seata 版本,如果版本不一致启动时可能会造成 no available server to connect 错误!
- 第二步:我们需要把 restry.conf 文件复制到项目的 resource 目录下
- 第三步:需要自己配置seata代理数据源
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = “spring.datasource”)
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Primary
@Bean
public DataSourceProxy dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}
复制代码
配置完数据源我们得在启动类的 SpringBootApplication 上排除Druid数据源依赖,否则可能会出现循环依赖的错误:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
复制代码
- 第四步:在 nacos 的配置文件控制台中加入我们服务的事务组项:
service.vgroupMapping + 服务名称 = default
group为: SEATA_GROUP
复制代码
js基础
1)对js的理解?
2)请说出以下代码输出的值?
3)把以下代码,改写成依次输出0-9
4)如何区分数组对象,普通对象,函数对象
5)面向对象、面向过程
6)面向对象的三大基本特性
7)XML和JSON的区别?
8)Web Worker 和webSocket?
9)Javascript垃圾回收方法?
10)new操作符具体干了什么呢?
11)js延迟加载的方式有哪些?
12)WEB应用从服务器主动推送Data到客户端有那些方式?
}
}
复制代码
配置完数据源我们得在启动类的 SpringBootApplication 上排除Druid数据源依赖,否则可能会出现循环依赖的错误:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
复制代码
- 第四步:在 nacos 的配置文件控制台中加入我们服务的事务组项:
service.vgroupMapping + 服务名称 = default
group为: SEATA_GROUP
复制代码
js基础
1)对js的理解?
2)请说出以下代码输出的值?
3)把以下代码,改写成依次输出0-9
4)如何区分数组对象,普通对象,函数对象
5)面向对象、面向过程
6)面向对象的三大基本特性
7)XML和JSON的区别?
8)Web Worker 和webSocket?
9)Javascript垃圾回收方法?
10)new操作符具体干了什么呢?
11)js延迟加载的方式有哪些?
12)WEB应用从服务器主动推送Data到客户端有那些方式?
[外链图片转存中…(img-BwbrPpCA-1720851151150)]
[外链图片转存中…(img-q8zt8zLr-1720851151151)]