当前位置: 首页 > article >正文

Sentinel服务保护 + Seata分布式事务

服务保护

雪崩问题】微服务调用链路中某个服务,引起整个链路中所有微服务都不可用。在这里插入图片描述
原因】:

  1. 微服务相互调用,服务提供者出现故障。
  2. 服务调用这没有做好异常处理,导致自身故障。
  3. 调用链中所有服务级联失败,导致整个集群故障。

解决方案】:
请求限流、线程隔离、服务熔断
服务保护技术】:
在这里插入图片描述

Sentinel服务保护

官方文档:Sentinel

使用步骤

1. 使用docker部署sentinel

创建并运行sentinel容器:

docker run -d \
--net=host \
--name sentinel \
--restart=always \
-e AUTH_USERNAME=admin \
-e AUTH_PASSWORD=admin \
bladex/sentinel-dashboard:1.8.6

完成后在浏览器输入:192.168.140.101:8858,用户名admin,密码admin

2.在微服务中连接sentinel控制台

引入sentinel依赖:

<!--sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId> 
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

配置控制台:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: 192.168.140.101:8858 # sentinel的控制台地址
      http-method-specify: true # 开启请求方式前缀

Restful风格的API请求路径一般相同,会导致簇点资源名称重复。所以我们要修改配置,把请求方式 + 请求路径作为簇点资源名称

簇点链路

簇点链路就是单机调用链路,是一次请求进入服务后经过的每一个被Sentinel监控的资源链。默认Sentinel会监控SpringMVC的每一个Endpoint(Http接口)。限流、熔断等都是争对簇点链路中的资源设置的,资源名默认就是接口的请求路径。
在这里插入图片描述

解决方案

1. 请求限流

限制访问微服务的请求的并发量,避免服务因流量激增而出现故障。
在这里插入图片描述
在这里插入图片描述
这个接口每秒钟只能处理6个请求,使用ApiFox进行测试,会有部分请求失败【失败返回状态码429】。
在这里插入图片描述

2. 线程隔离

通过限定每个业务能使用的线程数量而将故障业务隔离,避免故障扩散。
在这里插入图片描述
场景】:假设有大量的查询购物车的请求,通过对查询购物车这个线程做线程隔离,可以保证购物车这个微服务的资源不会被耗尽,不会对修改购物车等其他业务造成影响。
在这里插入图片描述
在这里插入图片描述

线程隔离和请求限流的区别:
请求限流:控制接受请求的速度(每秒访问几次)
线程隔离:控制最多能接收请求的次数(一次访问的线程数)
就算请求限流设置的再慢,如果线程卡住的话,不设置线程隔离,也会导致资源占用。

3. fallback

在这里插入图片描述
一、 将FeignClient作为Sentinel的簇点资源:

feign:
  sentinel:
    enabled: true # 开启流量控制

二、 为FeignClient添加Fallback:

  • 方法1:FallbackClass,无法对远程调用的异常做处理
  • 方法2:FallbackFactory,可以对远程调用的异常做处理
  1. 自定义类,实现FallbackFactory,编写对某个FeignClient的fallback逻辑:
@Slf4j
public class ItemClientFallbackFactory implements FallbackFactory<ItemClient> {
    @Override
    public ItemClient create(Throwable cause) {
    	// 编写失败的处理逻辑(失败后就会走里边的方法)
        return new ItemClient() {
            @Override
            public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
                log.error("查询商品失败,"+ cause);
                return CollUtils.emptyList();
            }

            @Override
            public void deductStock(List<OrderDetailDTO> items) {
                log.error("扣减商品库存失败,"+ cause);
                throw new RuntimeException(cause);
            }
        };
    }
}
  1. 将定义的FallbackFactory注册为一个Bean:
public class DefaultFeignConfig {
	@Bean
	public ItemClientFallbackFactory itemClientFallbackFactory() {
		return new ItemClientFallbackFactory();
	}
}
  1. 在ItemClient接口中使用FallbackFactory:@FeignClient(value = "item-service", fallbackFactory = ItemClientFallbackFactory.class)
@FeignClient(value = "item-service", fallbackFactory = ItemClientFallbackFactory.class)
public interface ItemClient {
    @GetMapping("/items")
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);

    @PutMapping("/items/stock/deduct")
    void deductStock(@RequestBody List<OrderDetailDTO> items);
}

在这里插入图片描述

4. 服务熔断

断路器统计请求的异常比例或慢调用比例,如果超出阈值则会熔断该业务,则拦截改接口的请求。熔断期间,所有请求快速失败,全部走fallback逻辑。当服务恢复时,断路器会放行访问该服务的请求。
在这里插入图片描述

断路器工作原理:

在这里插入图片描述
默认情况:Closed状态
如果失败的比例过高:就会进入Open状态【拦截一切请求,快速失败】
Open状态下会尝试放行一次请求,进入Half-Open状态,如果仍然失败,再次返回Open状态;如果成功,回到Closed状态

配置熔断策略

在这里插入图片描述
在这里插入图片描述

分布式事务

如果一个业务需要多个服务合作完成,而且每个服务都有事务,多个事务必须同时成功或同时失败,这样的事务就是分布式事务。其中每一个服务的事务就是一个分支事务。整个业务称为全局事务
场景】:用户下单后,订单服务首先创建订单,随后调用购物车服务清理购物车,最后调用库存服务扣减商品的库存。如果在调用库存服务的时候商品库存为0,此时扣减库存失败,订单服务和购物车服务应该同时失败。
在这里插入图片描述
出现问题的原因】:各个分支服务不知道对方的情况
解决思路】:让各个分支事务感受到对方的存在,让所有的微服务向事务协调者报告当前的状态。
在这里插入图片描述

Seata架构

  • 事务协调者(TC):维护全局和分支事务的状态,协调全局事务提交或回滚。
  • 事务管理器(TM):定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • 资源管理器(RM):管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态。
    在这里插入图片描述

1. 部署TC服务

  1. 创建数据库,导入sql文件
    在这里插入图片描述
CREATE DATABASE IF NOT EXISTS `seata`;
USE `seata`;


CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;


CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;


CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(128),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_status` (`status`),
    KEY `idx_branch_id` (`branch_id`),
    KEY `idx_xid_and_branch_id` (`xid` , `branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS `distributed_lock`
(
    `lock_key`       CHAR(20) NOT NULL,
    `lock_value`     VARCHAR(20) NOT NULL,
    `expire`         BIGINT,
    primary key (`lock_key`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
  1. 准备配置文件
    seata运行时所需的配置文件
    上传前先看看application.yml,里边可能有些配置需要改一下
    把上边的配置文件丢到root根目录下
    在这里插入图片描述
  2. docker部署
    在/root目录下执行以下命令,创建并允许seata容器
docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.140.101 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hm-net \
--restart=always \
-d \
seataio/seata-server:1.5.2
  1. 以上操作都完成后,在浏览器输入:http://192.168.140.101:7099/后即可登录seata控制台。(初始账号:admin、密码:admin)

2. 微服务集成Seata

  1. 引入Seata依赖
<!--seata-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
  1. 配置TC服务地址,让微服务找到TC服务地址
    在这里插入图片描述
seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
      server-addr: 192.168.140.101:8848 # nacos地址
      namespace: "" # namespace,默认为空
      group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
      application: seata-server # seata服务名称
      username: nacos
      password: nacos
  tx-service-group: hmall # 事务组名称
  service:
    vgroup-mapping: # 事务组与tc集群的映射关系
      hmall: "default"

因为很多服务都需要实现分布式事务,所以可以把对于seata的配置抽取成一个共享配置写在nacos里。所以添加依赖的时候,检查一下是否有bootstrapnacos配置管理的依赖。

查看seata的日志文件,可以看到购物车服务、交易服务、商品服务已经全部和seata的TC服务建立连接。
在这里插入图片描述

3. Seata解决分布式事务问题

XA模式—强一致

在这里插入图片描述

  1. 一阶段工作:
  • RM注册分支事务
  • RM执行分支事务sql但不提交
  • RM报告执行状态到TC
  1. 二阶段工作:
  • TC检测各分支事务执行状态:
    • 如果都成功,通知所有RM提交事务
    • 如果有失败,通知所有RM回滚事务
  • RM接收到TC指令,提交或回滚事务

通过“等待”的方式,确保了全局事务的ACID特性。但是一阶段需要锁定数据库的资源,到二阶段才释放,性能差。

实现步骤
  1. 修改(每个参与事务的微服务)application.yml文件,开启XA模式
seata:
  data-source-proxy-mode: XA
  1. 给发起全局事务的入口添加@GlobalTransactional注解
    在这里插入图片描述

AT模式(主推)—最终一致

AT模式弥补了XA模式中资源锁定周期过长的缺陷。
在这里插入图片描述

  1. 一阶段RM的工作:
  • 注册分支事务
  • 记录undo-log(数据快照)
  • 执行业务sql并提交
  • 报告事务状态
  1. 二阶段提交时RM的工作:
  • 删除undo-log即可
  1. 二阶段回滚时RM的工作:
  • 根据undo-log恢复数据到更新前

AT模式相比于XA模式的优点在于:在一阶段不需要等待彼此执行,而是各自提交,这样资源就没有锁定,性能也会好。
但是如果二阶段需要进行回滚,在回滚之前,会出现数据短暂的不一致。
AT模式与XA模式的区别】:

  1. XA模式一阶段不提交事务,锁定资源
    AT模式一阶段直接提交,不锁定资源
  2. XA模式依赖数据库机制实现回滚
    AT模式利用数据快照实现回滚
  3. XA模式强一致
    AT模型最终一致
实现步骤
  1. 创建数据表,导入用来记录数据快照的undo_log表
    】:每个分支事务都需要有自己的undo_log表
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
  1. 修改application.yml文件,将事务模式修改为AT模式
seata:
  data-source-proxy-mode: AT

在这里插入图片描述
数据快照(undo_log表):
在这里插入图片描述


http://www.kler.cn/a/487374.html

相关文章:

  • ThreadLocal 的使用场景
  • 【电子通识】PWM驱动让有刷直流电机恒流工作
  • 【生物信息】h5py.File
  • 33.3K 的Freqtrade:开启加密货币自动化交易之旅
  • WebRTC 在视频联网平台中的应用:开启实时通信新篇章
  • Flutter Web 中文字体显示异常问题
  • 开放词汇检测新晋SOTA:地瓜机器人开源DOSOD实时检测算法
  • C# winform 多线程 UI更新数据 报错:无法访问已释放的对象。
  • 【AI日记】25.01.09
  • 程序血缘分析技术在工商银行软件工程中的应用
  • 一.MySQL程序简介
  • 用BaoStock判断一只股票昨天是否涨停~~
  • GC8872 是一款带故障报告功能的刷式直流电机驱动芯片, 适用于打印机、电器、工业设备以及其他小型机器。
  • 【贵州省】乡镇界arcgis格式shp数据乡镇名称和编码内容下载测评
  • c#学生课程设计之仿windows计算器开发
  • OWASP ZAP安全测试--使用(自动扫描、手动浏览)
  • 系统思考—问题分析
  • 移动端可互动轮播图
  • VoiceBox:基于文本引导的多语种通用大规模语音生成
  • CSS学习记录26
  • 32单片机从入门到精通之软件编程——任务调度(十)
  • 对话新晋 Apache SeaTunnel Committer:张圣航的开源之路与技术洞察
  • Maven核心插件之maven-resources-plugin
  • 如何训练大型语言模型?
  • Java学习,Finally用法
  • BigDecimal:高精度数值运算类