1. 为什么需要分布式事务
在传统的单体架构中,我们只需要使用本地事务就能保证数据一致性。比如一个电商下单流程,在同一个数据库里扣减库存、创建订单、扣减余额,用@Transactional注解就能轻松搞定。但随着业务发展,系统被拆分成多个微服务后,问题就来了。
想象一下:订单服务、库存服务、账户服务各自独立部署,数据库也完全隔离。用户下单时,如果库存扣减成功了,但创建订单时网络超时,这时候就会出现库存少了但订单没生成的数据不一致问题。这就是典型的分布式事务场景。
我去年就遇到过这样的生产事故:大促期间由于分布式事务没处理好,导致超卖了200多件商品,最后只能人工核对数据并给用户退款。这种问题在微服务架构中会频繁出现,所以我们需要引入分布式事务解决方案。
2. Seata的核心工作原理
Seata是阿里开源的分布式事务框架,它的AT模式对开发者最友好。先来看下它的核心组件:
- TC (Transaction Coordinator): 事务协调器,维护全局事务状态
- TM (Transaction Manager): 事务管理器,负责开启/提交/回滚全局事务
- RM (Resource Manager): 资源管理器,管理分支事务资源
具体工作流程是这样的:
- TM向TC申请开启全局事务,生成XID(全局事务ID)
- 各微服务执行SQL时,RM会拦截SQL生成undo_log回滚日志
- TM向TC发起全局提交/回滚
- TC调度所有RM完成分支事务的提交或回滚
我画个简单示意图:
[TM] -- 开启事务 --> [TC] [RM1] -- 注册分支 --> [TC] [RM2] -- 注册分支 --> [TC] [TM] -- 提交/回滚 --> [TC] [TC] -- 通知 --> [RM1] [TC] -- 通知 --> [RM2]3. 单机版Seata快速搭建
3.1 环境准备
首先确保你的环境满足:
- JDK 1.8(实测JDK11+会报GC错误)
- MySQL 5.7+
- Nacos 1.4.0+
这里有个坑我踩过:Seata 1.4.0+版本开始支持JDK11,但生产环境建议还是用JDK8更稳定。
3.2 数据库初始化
需要创建两个关键表:
- 全局事务表(在seata库):
CREATE DATABASE seata; USE seata; -- 执行下载包中的db_store.sql- undo_log表(在每个业务库):
-- 在订单库、库存库、账户库都要执行 CREATE TABLE undo_log ( id bigint(20) NOT NULL AUTO_INCREMENT, branch_id bigint(20) NOT NULL, xid varchar(100) NOT NULL, context varchar(128) NOT NULL, rollback_info longblob NOT NULL, log_status int(11) NOT NULL, log_created datetime NOT NULL, log_modified datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY ux_undo_log (xid,branch_id) );3.3 配置文件修改
主要修改conf目录下两个文件:
file.conf(存储模式配置):
store { mode = "db" db { datasource = "druid" dbType = "mysql" driverClassName = "com.mysql.cj.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true" user = "root" password = "123456" minConn = 5 maxConn = 30 } }registry.conf(注册中心配置):
registry { type = "nacos" nacos { serverAddr = "localhost:8848" namespace = "" cluster = "default" } } config { type = "nacos" nacos { serverAddr = "localhost:8848" namespace = "" group = "SEATA_GROUP" } }4. 高可用集群部署方案
4.1 集群架构设计
生产环境建议采用这种架构:
[Nginx] | [Seata Server1] [Seata Server2] [Seata Server3] | | | [Nacos Cluster] [MySQL Cluster]关键点:
- 至少部署3个Seata Server形成集群
- 使用Nacos作为配置中心和注册中心
- 数据库采用主从复制
4.2 事务分组配置
这是Seata集群的核心配置,在nacos-config.txt中:
service.vgroupMapping.default_tx_group=cluster1 service.vgroupMapping.order_tx_group=cluster2然后在客户端配置对应的事务组:
seata: tx-service-group: order_tx_group这样不同业务可以使用不同的事务分组,实现资源隔离。
4.3 启动参数优化
生产环境启动时需要调整JVM参数:
sh seata-server.sh -p 8091 -h 192.168.1.100 \ -m db -n 3 \ -e test \ -Xmx2048m -Xms2048m \ -Xmn1024m -XX:SurvivorRatio=10参数说明:
- -n:服务器节点编号
- -e:环境名称
- -Xmx:堆内存最大值
- -Xms:堆内存初始值
5. 生产环境常见问题
5.1 版本兼容性问题
我整理过版本匹配表:
| Seata版本 | Spring Cloud Alibaba版本 | Nacos版本 |
|---|---|---|
| 1.4.2 | 2021.0.1.0 | 2.0.3 |
| 1.5.0 | 2021.0.4.0 | 2.1.0 |
常见报错:
No available service 'null' found:检查事务组配置can't get cluster name:Nacos集群配置错误branch transaction rollback failed:undo_log表缺失
5.2 性能优化建议
根据压测经验,给出以下参数建议:
# file.conf store { db { queryLimit = 200 # 提高查询效率 maxWait = 10000 # 连接池等待时间(ms) } } # seata-server.properties server.undo.logSaveDays=7 # undo日志保留天数 server.recovery.committingRetryPeriod=3000 # 重试间隔(ms)6. 真实案例:电商下单流程
以典型的下单场景为例:
@GlobalTransactional public void createOrder(OrderDTO orderDTO) { // 1. 扣减库存 storageFeignClient.deduct(orderDTO.getSkuCode(), orderDTO.getCount()); // 2. 创建订单 orderService.create(orderDTO); // 3. 扣减余额 accountFeignClient.debit(orderDTO.getUserId(), orderDTO.getAmount()); }关键点:
- 所有Feign调用必须开启seata代理
- 每个微服务都要配置
seata.enable-auto-data-source-proxy=true - 异常传播机制要正确(建议使用RuntimeException)
我在实际项目中遇到过这样的坑:有个同事在account服务里捕获了异常没有抛出,导致事务无法回滚。所以一定要确保异常能传播到事务发起方。
7. 监控与运维
7.1 控制台部署
Seata自带管理控制台,部署步骤:
- 下载seata-console工程
- 修改application.yml中的nacos配置
- 启动后访问http://ip:7091
可以实时查看:
- 全局事务列表
- 分支事务状态
- 锁冲突情况
7.2 告警配置
建议监控以下指标:
- 事务成功率(低于99%需要告警)
- 平均处理时间(超过500ms需要优化)
- 锁等待超时次数
可以与Prometheus集成:
metrics: enabled: true registryType: compact exporterList: prometheus最后说下我的经验:分布式事务不是银弹,设计时应该尽量避免跨服务事务。比如可以把库存扣减和订单创建放在同一个服务中,用本地事务处理。只有真正需要强一致性的场景才使用Seata。