曼基康矮脚猫库存平白少 1 只?Seata 部署 + 微服务集成实战,彻底搞定分布式事务

目录

微服务事务管理

案例分析

初始Seate

Seate

部署TC服务

1.⾸先我们要下载seata-server包,地址在http://seata.io/zh-cn/blog/download.html(小编也会在评论区发seate的安装包)

2.解压

3.修改配置:conf⽬录下的registry.conf⽂件:

4.创建数据库 这些表主要记录全局事务、分⽀事务、全局锁信息:

5.启动TC服务:

微服务集成Seata

引⼊依赖

配置TC地址(以AT模式为例)

成功场景

​失败场景

微服务事务管理

在微服务架构中,应用被拆分为多个独立部署、职责单一的服务,每个服务通常拥有自己的数据库(数据去中心化)。这导致传统单体应用中基于数据库的ACID 事务(原子性、一致性、隔离性、持久性)难以直接复用 —— 一个业务流程可能涉及多个微服务的数据库操作,如何保证这些跨服务操作的最终一致性,成为微服务架构的核心挑战之一,这就是微服务事务管理的核心目标。

案例分析

为了让大家充分理解微服务事务管理的作用,我们来看一个案例;要是大家想直接了解 Seata,也可以跳过本案例环节。

我们有shop_order表(订单表),用户下订单时,会记录用户 id、用户名、商品 id、商品名、商品单价;还有shop_product表(商品表),记录商品 id、商品名称、商品单价、商品库存,用户下订单后,要减去对应商品的库存数量。现在商品名为曼基康矮脚的库存是 20,shop_order表暂时为空。

order服务的service层有下单方法,会通过 Feign 调用product服务中的减库存方法,代码如下:

//order服务-下单
 @Override
    @Transactional
    public Order createOrder(int pid) {
        //通过fegin调用商品微服务->查询商品信息
        Product product = productClient.findByPid(pid);
        //下单
        Order order = new Order();
        order.setUid(1);
        order.setUsername("测试用户");
        order.setPid(pid);
        order.setPname(product.getPname());
        order.setPprice(product.getPprice());
        order.setNumber(1);//设置购买数量
        orderMapper.insert(order);
        //通过fegin调用商品微服务-扣库存
        productClient.reduceInventory(pid, order.getNumber());
        return order;
    }

product服务的service层有减库存方法,代码如下:

//product服务-减库存
 @Override
    public void reduceInventory(Integer pid, int num) {
        Product product=productMapper.selectById(pid);
        product.setStock(product.getStock()-num);
        //减库存
        productMapper.updateById(product);
        int a=2/0;//模拟异常处理

当我们下单商品编号为 30 的商品时,由于a = 2 / 0的算数异常,出现了内部服务器错误(Internal Server Error,状态码 500),下单失败,页面显示 Spring Boot 应用的默认错误页面(Whitelabel Error Page),提示应用没有为/error路径显式映射错误处理。

但此时编号为 30 的商品曼基康矮脚的库存量却变成了 19,少了 1只猫🐱,这就导致了数据不一致的问题 —— 订单没创建成功,库存却被扣除了。所以@EnableTransactionManagement、@Transactional并不能处理微服务的事务管理。

初始Seate

Seata是 2019 年 1 ⽉份蚂蚁⾦服和阿⾥巴巴共同开源的分布式事务解决⽅案。致⼒于提供⾼性能和简单 易⽤的分布式事务服务,为⽤户打造⼀站式的分布式解决⽅案。

官⽹地址:http://seata.io/,其中的⽂档、播客中提供了⼤量的使⽤说明、源码分析。

Seate

Seata事务管理中有三个重要的⻆⾊:

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

Seata基于上述架构提供了四种不同的分布式事务解决⽅案:

  • XA模式:强⼀致性分阶段事务模式,牺牲了⼀定的可⽤性,⽆业务侵⼊
  • TCC模式:最终⼀致的分阶段事务模式,有业务侵⼊
  • AT模式:最终⼀致的分阶段事务模式,⽆业务侵⼊,也是Seata的默认模式
  • SAGA模式:⻓事务模式,有业务侵⼊

⽆论哪种⽅案,都离不开TC,也就是事务的协调者。

部署TC服务

1.⾸先我们要下载seata-server包,地址在http://seata.io/zh-cn/blog/download.html(小编也会在评论区发seate的安装包)

2.解压

3.修改配置:conf⽬录下的registry.conf⽂件:

registry {
#tc 服务的注册中心类,这里选择 nacos,也可以是 eureka、zookeeper 等
type = "nacos"
nacos {
#seata tc 服务注册到 nacos 的服务名称,可以自定义
application = "seata-tc-server"
serverAddr = "127.0.0.1:8848"
group = "DEFAULT_GROUP"
namespace = ""
cluster = "SH"
username = "nacos"
password = "nacos"
}
}
config {
#读取 tc 服务端的配置文件的方式,这里是从 nacos 配置中心读取,这样如果 tc 是集群,可以共享配置
type = "nacos"
#配置 nacos 地址等信息
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}

服务端配置⽂件seataServer.properties⽂件需要在nacos中配

点击+添加配置,配置格式为Properties

配置内容

#数据存储方式,db 代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
#事务、日志等配置
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
#客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
#关闭 metrics 功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

4.创建数据库 这些表主要记录全局事务、分⽀事务、全局锁信息:

-- 创建global_table表
CREATE TABLE `global_table` (
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint DEFAULT NULL,
  `status` tinyint NOT NULL,
  `application_id` varchar(32) DEFAULT NULL,
  `transaction_service_group` varchar(32) DEFAULT NULL,
  `transaction_name` varchar(128) DEFAULT NULL,
  `timeout` int DEFAULT NULL,
  `begin_time` bigint DEFAULT NULL,
  `application_data` longtext,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`xid`),
  KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
  KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 创建branch_table表
CREATE TABLE `branch_table` (
  `branch_id` bigint NOT NULL,
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint DEFAULT NULL,
  `resource_group_id` varchar(32) DEFAULT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `branch_type` varchar(8) DEFAULT NULL,
  `status` tinyint NOT NULL,
  `client_id` varchar(64) DEFAULT NULL,
  `application_data` longtext,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 创建lock_table表
CREATE TABLE `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(128) DEFAULT NULL,
  `transaction_id` longtext,
  `branch_id` longtext,
  `resource_id` varchar(256) DEFAULT NULL,
  `table_name` varchar(32) DEFAULT NULL,
  `pk` varchar(36) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`row_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

5.启动TC服务:

  • 进⼊bin⽬录,运⾏其中的seata-server.bat即可默认端⼝为8091
  • 或者运⾏指令指定端⼝:seata-server.bat -p 9000 -m file
  • 启动成功后,seata-server应该已经注册到nacos注册中⼼了

微服务集成Seata

引⼊依赖

  <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-spring-boot-starter</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>${seata.version}</version>
        </dependency>

配置TC地址(以AT模式为例)

在订单服务和商品服务中的application.yml中,分别配置TC服务信息,通过注册中⼼nacos,结合服务名称获取TC地址:

seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
      server-addr: 127.0.0.1:8848 # nacos地址
      namespace: "" # namespace,默认为空
      group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
      application: seata-tc-server # seata服务名称
      username: nacos
      password: nacos
  tx-service-group: seata-demo # 事务组名称
  service:
    vgroup-mapping: # 事务组与cluster的映射关系
      seata-demo: SH
  data-source-proxy-mode: AT
  use-jdk-proxy: false
  enable-auto-data-source-proxy: true

微服务如何根据这些配置寻找TC的地址呢?

我们知道注册到Nacos中的微服务,确定⼀个具体实例需要四个信息:

  • namespace:命名空间
  • group:分组
  • application:服务名
  • cluster:集群名

以上四个信息,在刚才的yaml⽂件中都能找到

以AT模式为例需要导⼊数据库表,记录全局锁

  • lock_table导⼊到TC服务关联的数据库(上述创建数据库时已经造好了,这里不过多赘述)
  • undo_log表导⼊到微服务关联的数据库
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (
  `branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci 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 INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;

最后我们在service层的下单方法前加上@GlobalTransactional,并且在启动类中开启事务管理

@EnableTransactionManagement,最后重启测试

成功场景

页面访问成功,shop_product的商品库存数量减1,shop_order也记录了订单的基本信息

失败场景

我们还是在product服务减库存时模拟异常int a=2/0;

页面访问失败->用户下单失败,shop_product的商品库存没有变

微服务事务管理的核心是解决跨服务数据一致性难题,Seata 通过清晰的架构和灵活的模式(如默认的 AT 模式),给出了高效实用的解决方案。本文从问题案例出发,梳理了 Seata 的部署与集成关键步骤,最终也通过测试验证了其能有效避免数据不一致。希望这些实践内容能为你在微服务项目中落地事务管理提供参考,助力构建更稳定可靠的分布式系统。

有问题欢迎留言!!!😗

肥嘟嘟左卫门就讲到这里啦,记得一键三连!!!😗

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值