【Spring】SpringCloudAlibaba学习笔记

Nacos

  1. Nacos是一个更易于构建云原生应用的动态服务发现/服务配置服务管理平台
  2. 核心功能:
    1. 服务注册: Nacos Client会通过发送REST请求向Nacos Server注册自己的服务, 提供自己的元数据, 如ip地址/端口等信息; Nacos Server收到注册请求后, 就会把这些信息存储在Map中
    2. 服务心跳: 在服务注册后, Nacos Client会维护一个定时心跳来持续通知Nacos Server, 说明服务一致处于可用状态, 防止被剔除 默认5s发送一次心跳
    3. 服务发现: 服务消费者在调用服务提供者的服务时, 会发送一个REST请求给Nacos Server, 获取上面注册的服务清单, 并且缓存在Nacos Client本地; 同时在Nacos Client本地开启一个定时任务定时拉取最新的注册表信息到本地缓存
    4. 服务健康检查: Nacos Server会开启一个定时任务来检查注册服务实例的健康情况, 对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false; 如果某个实例超过30s没有收到心跳, 直接剔除该实例

主流注册中心对比

指标NacosEurekaConsulCoreDNSZookeeper
一致性协议CP/APAPCP-CP
健康检查TCP/HTTP/MySQL/Client BeatClient BeatTCP/HTTP/gRPC/Cmd--
负载均衡策略权重/metadata/SelectorRibbonFabioRoundRobin-
雪崩保护
自动注销实例支持支持支持不支持支持
访问协议HTTP/DNSHTTPHTTP/DNSDNSTCP
监听支持支持支持支持不支持支持
多数据中心支持支持支持不支持不支持
跨注册中心同步支持不支持支持不支持不支持
SpringCloud集成支持不支持支持不支持支持
Dubbo集成支持不支持支持不支持支持
K8S集成支持不支持支持支持不支持

基本使用

集群部署1

apiVersion: v1
kind: Service
metadata:
  name: nacos-svc
spec:
  type: LoadBalancer
  ports:
    - port: 8848
      name: server
      targetPort: 8848
    - port: 9848
      name: client-rpc
      targetPort: 9848
    - port: 9849
      name: raft-rpc
      targetPort: 9849
    ## 兼容1.4.x版本的选举端口
    - port: 7848
      name: old-raft-rpc
      targetPort: 7848
  selector:
    app: nacos
---
apiVersion: v1
kind: Service
metadata:
  name: nacos-headless
  labels:
    app: nacos-headless
spec:
  type: ClusterIP
  clusterIP: None
  ports:
    - port: 8848
      name: server
      targetPort: 8848
    - port: 9848
      name: client-rpc
      targetPort: 9848
    - port: 9849
      name: raft-rpc
      targetPort: 9849
    ## 兼容1.4.x版本的选举端口
    - port: 7848
      name: old-raft-rpc
      targetPort: 7848
  selector:
    app: nacos
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nacos-cm
data:
  mysql.host: "server.passnight.local"
  mysql.db.name: "nacos_devtest"
  mysql.port: "3306"
  mysql.user: "nacos"
  mysql.password: "*****************"
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nacos
spec:
  serviceName: nacos-headless
  replicas: 3
  template:
    metadata:
      labels:
        app: nacos
      annotations:
        pod.alpha.kubernetes.io/initialized: "true"
    spec:
      containers:
        - name: nacos
          imagePullPolicy: Always
          image: nacos/nacos-server:latest
          resources:
            requests:
              memory: "128Mi"
              cpu: "500m"
          ports:
            - containerPort: 8848
              name: client
            - containerPort: 9848
              name: client-rpc
            - containerPort: 9849
              name: raft-rpc
            - containerPort: 7848
              name: old-raft-rpc
          env:
            - name: NACOS_REPLICAS
              value: "3"
            - name: MYSQL_SERVICE_HOST
              valueFrom:
                configMapKeyRef:
                  name: nacos-cm
                  key: mysql.host
            - name: MYSQL_SERVICE_DB_NAME
              valueFrom:
                configMapKeyRef:
                  name: nacos-cm
                  key: mysql.db.name
            - name: MYSQL_SERVICE_PORT
              valueFrom:
                configMapKeyRef:
                  name: nacos-cm
                  key: mysql.port
            - name: MYSQL_SERVICE_USER
              valueFrom:
                configMapKeyRef:
                  name: nacos-cm
                  key: mysql.user
            - name: MYSQL_SERVICE_PASSWORD
              valueFrom:
                configMapKeyRef:
                  name: nacos-cm
                  key: mysql.password
            - name: SPRING_DATASOURCE_PLATFORM
              value: "mysql"
            - name: MYSQL_SERVICE_DB_PARAM
              value: characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
            - name: NACOS_SERVER_PORT
              value: "8848"
            - name: NACOS_APPLICATION_PORT
              value: "8848"
            - name: PREFER_HOST_MODE
              value: "hostname"
            - name: NACOS_SERVERS
              value: "nacos-0.nacos-headless.default.svc.cluster.local:8848 nacos-1.nacos-headless.default.svc.cluster.local:8848 nacos-2.nacos-headless.default.svc.cluster.local:8848"
  selector:
    matchLabels:
      app: nacos

注意这里会报错

error: error validating "nacos.yml": error validating data: ValidationError(StatefulSet.spec.template.metadata): unknown field "spec" in io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta; if you choose to ignore these errors, turn validation off with --validate=false

需要添加一层spec标签2

再登录会报错No DataSource set

需要配置MySQL8的一些常用配置项3并自己执行初始化脚本4; 需要执行对应版本的sql脚本nacos/distribution/conf/nacos-mysql.sql at 2.0.3 · alibaba/nacos (github.com) docker:latest对应的是2.0.3

这里把节点亲和性给去掉了; 允许所有节点部署Nacos

然后使用用户名nacos密码nacos就可以登陆了

在这里插入图片描述

java应用代码

使用nacos首先要引入Maven依赖

    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.9.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

然后再application.properties添加必要的配置

# 这个对应服务名
spring.application.name=stock-service 
spring.cloud.nacos.server-addr=192.168.100.73:8848
# 后面三项式默认配置
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
spring.cloud.nacos.discovery.namespace=public

之后就可以在服务列表中看到相应的服务了

在这里插入图片描述

Nacos服务发现

在服务注册后, 就可以通过服务名进行调用和负载均衡了

首选准备带负载均衡功能的客户端:

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

然后准备两个服务

@AllArgsConstructor
@RestController
@RequestMapping("/OrderController")
public class OrderController {

    private final RestTemplate restTemplate;


    @GetMapping("/add")
    public String add() {
        String response = restTemplate.getForObject("http://stock-service/StockService/reduce", String.class);
        return "In order-service, stock-service response: " + response;
    }
}

@RestController
@RequestMapping("/StockService")
public class StockController {
    @GetMapping("/reduce")
    public String reduce() {
        return "reduce stock";
    }
}

之后调用订单服务, 订单服务就会调用到库存服务 默认的负载均衡器是robbin

passnight@passnight-s600:~$ curl localhost:8010/OrderController/add
In order-service, stock-service response: reduce stock

配置中心使用

  1. Nacos提供用于配置存储和其他元数据的key/value存储; 为分布式系统中的外部化配置提供服务器端和客户端支持. 使用Spring Cloud Alibaba Nacos Config, 可以在Nacos Server中集中管理配置
  2. 使用Nacos有以下好处
    1. 易维护
    2. 时效性
    3. 安全性

Dubbo

  1. Dubbo以一款高性能的RPC框架
    1. 面向接口代理的高性能RPC调用: 为开发者屏蔽调用的底层细节
    2. 智能负载均衡
    3. 服务自动注册与发现
    4. 高扩展: 基于微内核+插件设计; 几乎所有的核心能力都支持第三方实现
    5. 运行期流量调度: 通过配置路由规则, 可以实现灰度发布/同机房优先等功能
    6. 可视化的服务治理与运维: 提供可视化的运维工具和服务治理工具

使用

安装dubbo-admin

version: "3.0"
services:
  dubbo-admin:
    image: apache/dubbo-admin:0.6.0
    container_name: dubbo-admin
    ports:
      - "20018:38080"
    volumes:
      - /opt/docker/dubbo-admin/data:/data
    environment:
      admin.registry.address: nacos://192.168.100.71:8848
      admin.config-center: nacos://192.168.100.71:8848
      admin.metadata-report.address: nacos://192.168.100.71:8848
    restart: always

然后使用docker-compose up -d启动即可

暴露服务

  1. 引入dubbo依赖

            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-spring-boot-starter</artifactId>
                <version>2.7.23</version>
            </dependency>
            <dependency>
                <groupId>com.squareup.okhttp3</groupId>
                <artifactId>okhttp</artifactId>
                <version>4.10.0</version>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-registry-nacos</artifactId>
                <version>2.7.23</version>
            </dependency>
    

原理

负载均衡机制

  1. RandomLoadBalance: 基于权重的随机负载均衡
  2. RoundRobinLoadBalance: 基于权重的轮询负载均衡 差分轮询算法
  3. LeastActiveLoadBalance: 最少活跃数负载均衡机制, 总是选择响应速度最快的服务
  4. ConsistentHashLoadBalance: 基于一致性哈希算法的负载均衡机制

Sentinel

基本概念

  1. 在微服务调用链路中某个服务故障, 引起整个链路中的微服务都不可用, 即雪崩

  2. 解决雪崩的方法

    1. 超时处理: 设定超时时间, 请求超过一定时间没有响应就返回错误信息, 不会无休止等待
    2. 仓壁模式: 限制每个业务所能够使用的资源, 以避免部分业务耗尽整个系统的资源; 即线程隔离
    3. 熔断降级: 由断路器统计业务执行的异常比例, 如果超出阈值则熔断该业务, 拦截访问该业务的一切请求
    4. 流量控制: 限制业务访问的QPS; 避免服务因为流量突增而故障
  3. 实现服务保护的技术对比:

    技术SentinelHystrix
    隔离策略信号量线程池/信号量
    熔断降级策略基于慢调用比例或异常比例基于失败比例
    实时指标实现滑动窗口滑动窗口(基于RxJava)
    规则配置支持多种数据源支持多种数据源
    扩展性多个扩展点插件形式
    基于注解的支持支持支持
    线瘤基于QPS, 支持基于调用关系的限流有限的支持
    流量整形支持慢启动/匀速排队模式不支持
    系统自适应保护支持不支持
    控制台开箱即用, 可配置规则, 查看秒级监控, 机器发现等不完善
    常见的框架的适配Servlet/Spring Cloud/ Dubbo/ gRPC等Servlet/ Spring Cloud Netfix

基本使用

  1. 安装dashboard

    version: "2.1"
    services:
      sentinel-dashboard:
        image: bladex/sentinel-dashboard:1.8.0
        container_name: sentinel-dashboard
        ports:
          - "20019:8858"
    
  2. 引入依赖

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            </dependency>
    
  3. 配置dashboard地址

    spring.cloud.sentinel.transport.dashboard=server.passnight.local:20019
    
  4. 之后就可以在dashboard中看到请求信息了

    在这里插入图片描述

  5. 选中端点并配置流量限制; 然后就可以实现流控了:

    1. 在这里插入图片描述

    2. 在这里插入图片描述

限流规则

  1. 簇点链路: 即项目中的调用链路, sentinel默认监控SpringMVC中的端点
  2. 流控模式:
    1. 关联: 统计与当前资源相关的另一个资源, 触发阈值时, 触发当前资源的限流 当read QOS达到阈值时, 对write限流

      1. 在这里插入图片描述
    2. 链路模式: 根据请求来源进行限流; 使用@SentinelResource将服务中的方法设置为资源

    3. 热点参数请求: 根据参数进行限流, 需要添加@SentinelResource才能生效 查询下, 对不同资源分别限流

  3. 流控效果:
    1. 快速失败: 达到阈值后, 新的请求会立即被拒绝, 并抛出异常 默认处理方式
    2. 预热模式: 对超出阈值的请求同样抛出异常, 但阈值会动态变化, 从最小值增加到最大值; 初识阈值为 t h r e s h o l d c o l d F a c t o r \frac{threshold}{coldFactor} coldFactorthreshold; 等待预热时间后达到阈值QPS
    3. 排队等待: 将请求放入队列, 直到预期等待时长请求超出等待时长才会拒绝请求 流量整形

隔离和降级

  1. 熔断降级: 断路器统计服务调用的异常比例/慢比例; 若超过阈值就会熔断该服务, 当服务恢复时自动放行该服务的请求 熔断时解决雪崩的重要手段, 即当服务异常则停止其提供服务

    失败达到阈值
    熔断时间结束
    success
    快速失败
    失败打开断路器
    一次请求成功, 关闭断路器
    Closed
    Open
    Half Open
  2. 熔断策略: 熔断策略有三种, 慢调用/异常比例/异常数

    1. 慢调用: 业务的相应时长(RT), 大于指定时长请求认定为慢调用请求, 在指定时间内, 如果请求数量超过最小数量, 慢调用比例大于设定值, 则触发熔断
    2. 异常比例/异常数: 指定事件内的调用, 若调用次数超过指定请求数, 且异常比例/异常数超过阈值, 则触发熔断
  3. 授权规则: 根据调用方来源进行控制, 有白名单黑名单两种方式例如绕过网关则block, 可以通过添加http header实现

  4. 自定义异常: 实现BlockExceptionHandler接口即可

规则持久化

  1. Sentinel控制台的规则管理有三种模式:

    1. 原始模式: 将规则保存在内存当中, 重启服务会丢失 默认模式

    2. pull模式: 控制台将配置的规则推送到Sentinel客户端, 而客户端会将规则持久化, 之后定时去本地文件或数据库中查询, 更新本地规则

      在这里插入图片描述

    3. push模式: 控制台将配置规则推送到配置中心, 服务监听配置中心 需要自己实现

      在这里插入图片描述

实现原理

Hystrix实现原理5

  1. 根据Hystrix的流程图来看, Hystrix主要实现的功能有三点:
    1. 缓存请求: 若请求结果在缓存当中, 则直接返回缓存中的结果
    2. 断路保护: 若命中了断路规则, 则直接断路, 执行fallback函数
    3. 池化请求: 由线程池(信号量)创建并执行请求; 因此若发生异常, 可以将异常隔离在Hystrix的线程池当中
    4. 在这里插入图片描述

Seata

项目准备

CREATE TABLE account_t
(
    id      BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id VARCHAR(64),
    money   DOUBLE
);
CREATE TABLE storage_t
(
    id             BIGINT PRIMARY KEY AUTO_INCREMENT,
    commodity_code VARCHAR(64),
    count          INT
);

CREATE TABLE order_t
(
    id             BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id        VARCHAR(64),
    commodity_code VARCHAR(64),
    count          INT,
    money          DOUBLE
);

INSERT INTO account_t (user_id, money) VALUE ('user1', 1000);
INSERT INTO storage_t(commodity_code, count) VALUE ('00001', 10);

请求

curl -X POST -d '{"userId": "user1", "commodityCode": "00001", "count": 2, "money": 200}' --header "Content-Type: application/json" server.passnight.local:8010/OrderController/create-order

触发库存不足异常:

curl -X POST -d '{"userId": "user1", "commodityCode": "00001", "count": 20, "money": 200}' --header "Content-Type: application/json" server.passnight.local:8010/OrderController/create-order

OrderService因为rpc调用失败, 且标注了@Transactional所以事务回滚; StockService因为异常所以没有扣减库存; 但UserService正常执行, 账户被扣减; 这不符合业务要求.

基本概念

  1. Seata事务管理中有三个重要角色:
    1. 事务协调者(Transaction CVoordinator): 维护全局和分支事务的状态, 协调全局事务提交或回滚
    2. 事务管理器(Transactrion Manager): 定义全局事务的范围, 开始全局事务, 提交或回滚全局事务
    3. 资源管理器(Resource Manager): 管理分支事务处理的资源, 与事务TC交谈以注册分支事务和报告分支事务的状态, 并驱动分支事务提交或回滚
  2. Seata分布式解决方案
    1. XA模式: 强一致分阶段事务模式, 牺牲一定的可用性, 无业务侵入
    2. TCC模式: 最终一致性的分阶段事务模式, 有业务侵入
    3. AT模式: 最终一致性的分阶段事务模式, 无业务侵入 是Seata的默认模式
    4. SAGA模式: 长事务模式, 有业务侵入

基本使用

部署TC服务

version: "3"
services:
  seata-server:
    container_name: seata-server
    image: seataio/seata-server:2.0.0
    hostname: seata-server
    ports:
      - "20020:8091"
      - "20021:7091"
    volumes:
      - "/opt/docker/seata/config:/seata-server/resources"
      - "/etc/localtime:/etc/localtime"
      - "/etc/timezone:/etc/timezone"
    environment:
      - SEATA_PORT=8091
      - STORE_MODE=file

建表

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
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;

-- the table to store BranchSession data
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;

-- the table to store lock data
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` (`xid`)
) 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);

添加

[lock.DataBaseDistributedLocker] [              <init>]  [] : The distribute lock table is not config, please create the target table and config it
store.db.distributed-lock-table=distributed_lock

分布式事务

XA模式

  1. Seata XA模式是在数据库XA模式上做了简单的封装 Seata的RM仅转发事务到db的RM
  2. 特点
    1. 优点:
      1. 强一致性, 满足ACID原子
      2. 常用的数据库都支持, 实现简单, 且没有代码侵入
    2. 缺点
      1. 一阶段需要锁定资源, 等到二阶段结束后才能释放, 性能较差
      2. 依赖关系型数据库实现事务 例如redis不支持XA, 则无法使用XA模式
实现
  1. 引入依赖

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            </dependency>
    
  2. 启动Seata

    @EnableSeataSpringConfig
    
  3. 配置Seata

    # seata
    seata.registry.type=nacos
    seata.registry.nacos.server-addr=192.168.100.71:8848
    seata.registry.nacos.namespace=public
    seata.registry.consul.cluster=default
    seata.config.nacos.group=SEATA_GROUP
    seata.registry.nacos.application=seata-server
    seata.tx-service-group=seata-sell
    # use XA mode
    seata.data-source-proxy-mode=XA
    seata.service.vgroup-mapping.seata-sell=default
    
  4. 在方法上标注@GlobalTransactional

        @Override
        @GlobalTransactional
        public Long createOrder(Order order) {
            orderMapper.insert(order);
            accountService.deduct(order.getUserId(), order.getMoney());
            stockService.deduct(order.getCommodityCode(), order.getCount());
            return order.getId();
        }
    

AT模式

  1. AT模式同样是分阶段提交的事务模型, 不过弥补了XA模型中锁定周期过长的缺陷 相比于XA模式, 它会直接提交事务; 而非等待执行

  2. 回滚方式: RM会拦截事务, 并生成undo快照, 失败则执行undo操作

  3. 二阶段提交只需要删除undo log; 并且报告TC的操作可以异步, 因为资源已经释放

  4. 在这里插入图片描述

  5. 可能存在的问题:

    事务1和事务2同时执行update account set money = money - 10 where id = 1

    事务1事务2
    获取锁, 保存快照{id: 1, money: 100}
    执行sql, set money = 90
    提交事务, 释放DB锁
    获取DB锁, 保存快照: {id : 1, money: 90}
    执行sql, set money = 80
    提交事务 释放DB锁
    回滚, set money = 100

    该过程丢失了事务2的更新

  6. 为了解决该问题, 使用全局锁实现写隔离, 全局锁有类似以下结构:

    xid(事务id)table(表)pk(行号)

    使用该数据结构隔离其他事务更新; 这样执行流程就变成了:

    事务1事务2
    获取锁, 保存快照{id: 1, money: 100}
    执行sql, set money = 90
    提交事务, 释放DB锁
    获取DB锁, 保存快照: {id : 1, money: 90}
    执行sql, set money = 80; 但需要等待全局锁
    回滚, set money = 100, 但需要等待DB锁 此时产生了死锁
    任务超时, 回滚业务, 释放全局锁
    获取DB锁, 根据快照恢复数据
  7. 尽管加了全局锁, 但该全局锁由Seata管理, 因此非Seata管理的服务可以访问该数据; 且锁的粒度是;

  8. 对于非Seata管理的全局事务, AT模式模式通过以下方式管理

    事务1事务2
    获取锁, 保存快照before-image: {id: 1, money: 100}
    执行sql, set money = 90
    提交事务, 释放DB锁
    获取DB锁
    保存快照after-image: {id: 1, money: 90}
    执行sql, set money = 80; 无需等待全局锁
    提交事务, 释放DB锁
    回滚, 将当前数据与after-image对比, 发现 90 ≠ 80 90 \ne 80 90=80; 说明数据被其他事务修改
    此时记录异常, 发送警告, 人工介入
  9. 特点

    1. 优点:

      1. 一阶段提交事务, 直接释放数据库资源, 性能较好
      2. 利用全局锁实现读写隔离
      3. 没有代码侵入, 框架自动完成代码的回滚和提交
    2. 缺点

      1. 两个阶段之间属于软状态, 属于最终一致性方案
      2. 框架的快照功能会影响性能, 但依旧比XA模式好很多
实现
  1. 在微服务中创建undo_log的表

    CREATE TABLE undo_log
    (
        branch_id     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',
        xid           VARCHAR(100) 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 'creation time',
        log_modified  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
        UNIQUE INDEX uni_undo_log (xid, branch_id)
    )
    
  2. 开启seataAT模式: seata.data-srouce-proxy-mode=AT

TCC模式

  1. TCC模式与AT模式相似, 每个阶段都是独立事务; 区别在于TCC是通过人工编码实现数据恢复的:

    1. Try: 资源的检测和预留
    2. Confirm: 完成资源操作业务
    3. Cancel: 预留资源释放 可以理解为Try的反向操作
  2. 在这里插入图片描述

  3. 举例(扣减用户余额30元):

    1. Try: 冻结金额30元
    2. Confirm: 清除掉冻结金额
    3. Cancel: 余额增增加冻结金额的值
  4. 特点

    1. 优点
      1. 性能好
      2. 不依数据库事务, 因此可以支持非事务型数据库
    2. 缺点
      1. 代码侵入性较大
      2. 软状态, 是最终一致性模型
      3. 需要考虑Confirm和Cancel的失败情况, 做好幂等处理
  5. 空回滚: 某分支try阶段阻塞, 导致全局事务触发cancel操作; 没有try的操作也要执行cancel操作, 此时cancel不能修改数据, 这就是空回滚: 在这里插入图片描述

  6. 业务悬挂: 在执行空回滚之后, try恢复, 此时不能执行try操作, 这就是业务悬挂

使用TCC实现扣款功能
  1. 需求:

    1. 编写TCC业务逻辑
    2. Try: 冻结金额, 扣减可用金额
    3. configm: 删除冻结金额
    4. cancel: 删除冻结金额, 恢复可用金额
    5. 保证confirm和cancel接口的幂等性 因为这些接口可能会因为执行失败重试
    6. 允许空回滚
    7. 拒绝业务悬挂
  2. 创建冻结表

    CREATE TABLE account_freeze_t
    (
        xid          VARCHAR(128) PRIMARY KEY NOT NULL,
        user_id      VARCHAR(255)    DEFAULT NULL COMMENT '用户id',
        freeze_money DOUBLE UNSIGNED DEFAULT 0 COMMENT '冻结金额',
        state        INT(1)          DEFAULT NULL COMMENT '事务状态, 0:try, 1:confirm, 2:cancel'
    );
    

SAGA模式

  1. SAGA模式是seata提供的长事务解决方案; 也分为两个阶段

    1. 阶段1: 直接提交本地事务 与TCC不同的是, tcc是在confirm阶段提交事务
    2. 阶段2: 若成功则什么都不做, 若失败则通过编写补偿业务回滚
  2. 在这里插入图片描述

  3. 特点:

    1. 优点:
      1. 基于事件驱动实现异步调用, 吞吐量高
      2. 一阶段直接提交, 性能较好
      3. 不用编写TCC三个阶段, 实现简单
    2. 缺点:
      1. 软状态持续时间不确定, 时效性差
      2. 没有锁, 没有事务隔离, 因此有脏写问题

引用


  1. Kubernetes Nacos ↩︎

  2. [google cloud platform - Kubernetes: Error validating data: ValidationError(Deployment.spec): unknown field “containers” in io.k8s.api.apps.v1.DeploymentSpec - Stack Overflow ↩︎

  3. https://github.com/nacos-group/nacos-docker/issues/251#issuecomment-1102186326 ↩︎

  4. https://github.com/nacos-group/nacos-docker/issues/251#issuecomment-1613891832 ↩︎

  5. How it Works · Netflix/Hystrix Wiki (github.com) ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pass night

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值