【Spring】SpringCloudAlibaba学习笔记

发布于:2024-07-01 ⋅ 阅读:(12) ⋅ 点赞:(0)

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没有收到心跳, 直接剔除该实例

主流注册中心对比

指标 Nacos Eureka Consul CoreDNS Zookeeper
一致性协议 CP/AP AP CP - CP
健康检查 TCP/HTTP/MySQL/Client Beat Client Beat TCP/HTTP/gRPC/Cmd - -
负载均衡策略 权重/metadata/Selector Ribbon Fabio RoundRobin -
雪崩保护
自动注销实例 支持 支持 支持 不支持 支持
访问协议 HTTP/DNS HTTP HTTP/DNS DNS TCP
监听支持 支持 支持 支持 不支持 支持
多数据中心 支持 支持 支持 不支持 不支持
跨注册中心同步 支持 不支持 支持 不支持 不支持
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. 实现服务保护的技术对比:

    技术 Sentinel Hystrix
    隔离策略 信号量 线程池/信号量
    熔断降级策略 基于慢调用比例或异常比例 基于失败比例
    实时指标实现 滑动窗口 滑动窗口(基于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) ↩︎