SpringCloud系列教程:微服务的未来(二十)Seata快速入门、部署TC服务、微服务集成Seata

前言

Seata(Simple Extensible Autonomous Transaction Architecture)是一个开源的分布式事务解决方案,旨在解决微服务架构中跨服务事务的一致性问题。在微服务架构中,每个服务通常都有自己的数据库,当涉及到多个服务的操作时,传统的事务管理模式(如分布式锁、两阶段提交等)并不适用。Seata 提供了轻量级的解决方案,帮助我们在微服务环境下实现分布式事务。

本博客将通过 Seata 的快速入门、部署 TC 服务以及微服务集成的方式,帮助大家了解如何在实际项目中使用 Seata 来管理分布式事务。


分布式事务

下单业务,前端请求首先进入订单服务,创建订单并写入数据库。然后订单服务调用购物车服务和库存服务:

  • 购物车服务负责清理购物车信息
  • 库存服务负责扣减商品库存

当创建订单,对应的订单服务的事务提交,然后调用购物车服务清理购物车,对应的事务提交,但是由于库存不足,扣减商品库存失败,对应的事务提交失败,此时就发生了冲突。此时下单失败,但是购物车被清零了。

在分布式系统中,如果一个业务需要多个服务合作完成,而且每一个服务都有事务,多个事务必须同时成功或失败,这样的事务就是分布式事务 。其中的每个服务的事务就是一个分支事务 。整个业务称为全局事务

初始Seata

Seata是 2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。官网地址:http://seata.io/,其中的文档、播客中提供了大量的使用说明、源码分析。

解决分布式事务,各个子事务之间必须能感知到彼此的事务状态,才能保证状态一致。

Seata的事务管理中有三个重要的角色:

  • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager) - 资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

TM和RM可以理解为Seata的客户端部分,引入到参与事务的微服务依赖中即可。将来TM和RM就会协助微服务,实现本地分支事务与TC之间交互,实现事务的提交或回滚。

部署TC服务

(1)部署数据库表seata-tc

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);

(2)准备seata目录中的application.yaml包含seata运行时所需要的配置文件:

xml 复制代码
server:
  port: 7099

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${user.home}/logs/seata
  # extend:
  #   logstash-appender:
  #     destination: 127.0.0.1:4560
  #   kafka-appender:
  #     bootstrap-servers: 127.0.0.1:9092
  #     topic: logback_to_logstash

console:
  user:
    username: admin
    password: admin

seata:
  config:
    # support: nacos, consul, apollo, zk, etcd3
    type: file
    # nacos:
    #   server-addr: nacos:8848
    #   group : "DEFAULT_GROUP"
    #   namespace: ""
    #   dataId: "seataServer.properties"
    #   username: "nacos"
    #   password: "nacos"
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    nacos:
      application: seata-server
      server-addr: nacos:8848
      group : "DEFAULT_GROUP"
      namespace: ""
      username: "nacos"
      password: "nacos"
#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
  server:
    # service-port: 8091 #If not configured, the default is '${server.port} + 1000'
    max-commit-retry-timeout: -1
    max-rollback-retry-timeout: -1
    rollback-retry-timeout-unlock-enable: false
    enable-check-auth: true
    enable-parallel-request-handle: true
    retry-dead-threshold: 130000
    xaer-nota-retry-timeout: 60000
    enableParallelRequestHandle: true
    recovery:
      committing-retry-period: 1000
      async-committing-retry-period: 1000
      rollbacking-retry-period: 1000
      timeout-retry-period: 1000
    undo:
      log-save-days: 7
      log-delete-period: 86400000
    session:
      branch-async-queue-size: 5000 #branch async remove queue size
      enable-branch-async-remove: false #enable to asynchronous remove branchSession
  store:
    # support: file 、 db 、 redis
    mode: db
    session:
      mode: db
    lock:
      mode: db
    db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://mysql:3306/seata?rewriteBatchedStatements=true&serverTimezone=UTC
      user: root
      password: 1234
      min-conn: 10
      max-conn: 100
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock
      query-limit: 1000
      max-wait: 5000
    # redis:
    #   mode: single
    #   database: 0
    #   min-conn: 10
    #   max-conn: 100
    #   password:
    #   max-total: 100
    #   query-limit: 1000
    #   single:
    #     host: 192.168.150.101
    #     port: 6379
  metrics:
    enabled: false
    registry-type: compact
    exporter-list: prometheus
    exporter-prometheus-port: 9898
  transport:
    rpc-tc-request-timeout: 15000
    enable-tc-server-batch-send-response: false
    shutdown:
      wait: 3
    thread-factory:
      boss-thread-prefix: NettyBoss
      worker-thread-prefix: NettyServerNIOWorker
      boss-thread-size: 1

将整个seata文件夹放到虚拟机的/root目录下

(3)docker部署

要确保nacos、mysql都在hgq网络中。如果某个容器不再hm-net网络,可以运行下面的命令将某容器加入指定网络:

java 复制代码
docker network connect [网络名] [容器名]
java 复制代码
docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.244.134 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hgq \
-d \
seataio/seata-server:1.5.2


访问http://你的虚拟机地址:7099,账号密码在seata目录中的application.yaml文件中设置。

微服务集成Seata

(1)要在项目中引入Seata依赖:

xml 复制代码
	<!--统一配置管理-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  </dependency>
  <!--读取bootstrap文件-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
  </dependency>
  <!--seata-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  </dependency>

(2)在在nacos上添加一个共享的seata配置,命名为shared-seata.yaml:

xml 复制代码
seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
      server-addr: 192.168.244.134: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"
  • registry: 配置注册中心信息,这里使用 Nacos 作为注册中心,Seata 将根据这些配置与 Nacos 通信来获取事务协调(TC)服务地址。

    • type: 注册中心的类型,配置为 nacos。
    • server-addr: 配置 Nacos 服务器地址和端口。
    • namespace: 配置 Nacos 命名空间,默认值为空。
    • group: 配置 Nacos 配置组,默认为 DEFAULT_GROUP。
    • application: 设置 Seata 服务的名称,用于注册到 Nacos 时标识。
    • username 和 password: 设置用于访问 Nacos 的认证信息。
  • tx-service-group: 配置事务服务组的名称,用于标识 Seata 中的事务服务组,确保事务的正确管理。

  • service.vgroup-mapping: 配置事务组与事务协调(TC)集群的映射关系。此配置将事务组(如 hmall)映射到指定的 TC 集群(如 default)。 通过这种方式,可以将不同的事务组映射到不同的 TC 集群。

  • 将依赖分别导入cart-service服务和trade-service服务。

    trade-service服务的bootstrap文件内容如下:

java 复制代码
spring:
  application:
    name: trade-service # 服务名称
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.150.101 # nacos地址
      config:
        file-extension: yaml # 文件后缀名
        shared-configs: # 共享配置
          - dataId: shared-jdbc.yaml # 共享mybatis配置
          - dataId: shared-log.yaml # 共享日志配置
          - dataId: shared-swagger.yaml # 共享日志配置
          - dataId: shared-seata.yaml # 共享seata配置

修改对应的application.yaml文件内容如下:

java 复制代码
server:
  port: 8085
feign:
  okhttp:
    enabled: true # 开启OKHttp连接池支持
  sentinel:
    enabled: true # 开启Feign对Sentinel的整合
hm:
  swagger:
    title: 交易服务接口文档
    package: com.hmall.trade.controller
  db:
    database: hm-trade

同时分别改造cart-service和item-service两个微服务模块。


总结

Seata 提供了一种简单、高效的方式来解决微服务架构中的分布式事务问题。在本博客中,我们介绍了 Seata 的快速入门、部署 TC 服务以及如何将 Seata 集成到微服务中。通过正确配置和使用 Seata,我们可以在微服务环境中实现事务的一致性管理,提高系统的可靠性和可维护性。

通过本文的指导,相信你已经掌握了如何快速入门 Seata,部署 TC 服务,并将其集成到微服务项目中。

相关推荐
taopi202438 分钟前
android java系统弹窗的基础模板
android·java·开发语言
松仔log1 小时前
Java多线程——对象的组合
java·开发语言·jvm
酷爱码1 小时前
springboot 动态配置定时任务
java·spring boot·后端
从未止步..2 小时前
Jenkins未在第一次登录后设置用户名,第二次登录不进去怎么办?
java·运维·jenkins
老马啸西风2 小时前
IM 即时通讯系统-42-基于netty实现的IM服务端,提供客户端jar包,可集成自己的登录系统
java
2501_903238652 小时前
Java 9模块开发:Eclipse实战指南
java·开发语言·eclipse·个人开发
test猿2 小时前
hive为什么建表,表存储什么
java
我的K84094 小时前
Spring Boot基本项目结构
java·spring boot·后端
码农小苏245 小时前
K个不同子数组的数目--滑动窗口--字节--亚马逊
java·数据结构·算法