目录
1.⾸先我们要下载seata-server包,地址在http://seata.io/zh-cn/blog/download.html(小编也会在评论区发seate的安装包)
3.修改配置:conf⽬录下的registry.conf⽂件:
4.创建数据库 这些表主要记录全局事务、分⽀事务、全局锁信息:
微服务事务管理
在微服务架构中,应用被拆分为多个独立部署、职责单一的服务,每个服务通常拥有自己的数据库(数据去中心化)。这导致传统单体应用中基于数据库的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 的部署与集成关键步骤,最终也通过测试验证了其能有效避免数据不一致。希望这些实践内容能为你在微服务项目中落地事务管理提供参考,助力构建更稳定可靠的分布式系统。
有问题欢迎留言!!!😗
肥嘟嘟左卫门就讲到这里啦,记得一键三连!!!😗