狂野飙车6鸿蒙版
471.84M · 2025-10-31
今天就从 “踩坑经历” 出发,把分布式事务、CAP 定理、Seata 这些知识点掰开揉碎讲 —— 保证不用晦涩术语,看完就能上手!
咱写单体应用时,事务就是个 “靠谱管家”:你让它干 “扣余额 + 生成订单”,要么全成,要么全撤,绝不搞 “余额扣了订单没生成” 的骚操作。这背后就是 ACID 四兄弟:
A(原子性) :要么全做完,要么全回滚(比如外卖下单,不能 “付款了没接单”);
C(一致性) :做完后数据得对(比如扣 100 余额,余额就该少 100,不能少 99);
I(隔离性) :两个事务别互相捣乱(你查库存的时候,我别插一腿改库存);
D(持久性) :做完就永久保存(订单生成了,数据库崩了重启也得在)。
但自从拆了微服务,麻烦就来了 —— 订单在订单服务的库,库存在库存服务的库,单机事务管不了两个库啊!这就轮到分布式事务登场了。
简单说,分布式事务就是 “跨服务、跨数据库” 的事务。比如用户下单的完整流程:
订单服务:创建订单(订单库);
库存服务:扣减库存(库存库);
支付服务:扣用户余额(用户库)。
这三步只要有一步失败,前面成功的就得回滚 —— 总不能 “订单创建了,库存没扣,最后超卖” 吧?
但问题是:三个服务不在一个库,单机事务的 ACID 没法跨库生效。这时候就需要专门的方案来管,而聊方案前,绕不开的就是 CAP 定理。
CAP 是分布式系统的 “铁律”,说的是三个特性里,你最多能同时满足两个:
C(一致性) :所有节点的数据得一样(比如你查库存是 10,我查也得是 10);
A(可用性) :不管啥情况,服务都得能响应(比如库存服务卡了,也得告诉用户 “稍等”,不能直接崩);
P(分区容错性) :服务之间断网了(比如订单服务和库存服务连不上),系统还能跑。
为啥不能三者兼得?举个例子:
假如库存服务和订单服务断网了(触发 P):
要 C(一致性):就必须等网络恢复,确认库存到底剩多少,这时候服务没法响应(丢了 A);
要 A(可用性):就先返回 “库存充足” 让用户下单,等网络恢复再同步数据,这时候可能出现数据不一致(丢了 C)。
在分布式系统中,分区容错性(P)是必须要保证的,我们必须做出取舍 所以实际开发中,咱得根据场景选 “CP” 或 “AP”:
适合 “数据错了比用不了更严重” 的场景,比如银行转账、库存扣减。
典型例子:ZooKeeper。它会保证所有节点数据一致,但如果主节点挂了,要等从节点选主,这期间服务不可用。
适合 “用不了比数据错了更严重” 的场景,比如商品列表、用户点赞。
典型例子:Eureka。服务注册信息可能暂时不一致(比如某个服务下线了,个别节点还显示在线),但永远能给你返回结果,不会卡着不动。
知道了 CAP 的取舍,那具体怎么实现分布式事务?阿里开源的 Seata 就是咱后端 er 的 “神器”—— 它把复杂的分布式事务逻辑封装好,咱不用自己写一堆协调代码。
先搞懂 Seata 里的三个 “关键角色”,记成 “项目组分工” 就好:
| 角色 | 作用 | 类比 |
|---|---|---|
| TM(事务管理器) | 发起和结束事务(比如订单服务说 “开始下单事务”“结束事务”) | 项目经理:负责启动项目、宣布项目成败 |
| RM(资源管理器) | 管理具体的数据库资源(比如库存服务负责扣库存、回滚库存) | 开发工程师:负责干具体活,听指挥回滚 |
| TC(事务协调器) | 居中协调 TM 和 RM(比如告诉库存服务 “该回滚了”) | 项目协调员:盯着进度,出问题了协调大家补救 |
简单说:TM 喊 “开始”,RM 们干活,TC 盯着,一旦有 RM 报错,TC 就让所有人回滚;全成了,TC 就宣布 “事务成了”。
光说不练假把式,咱以 Spring Cloud 项目为例,三步集成 Seata:
registry.conf:把注册中心改成你项目用的(比如 Nacos),让 TM/RM 能找到 TC;bin/seata-server.sh(Linux)或seata-server.bat(Windows)。xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
告诉服务 “TC 在哪”“用什么事务模式”:
yaml
seata:
tx-service-group: my_tx_group # 事务组名,要和TC配置一致
service:
vgroup-mapping:
my_tx_group: default # 映射到TC的集群名
grouplist:
default: 127.0.0.1:8091 # TC的地址和端口
registry:
type: nacos # 注册中心类型
nacos:
server-addr: 127.0.0.1:8848 # Nacos地址
在事务发起的方法上(比如订单服务的 “创建订单” 方法)加@GlobalTransactional:
java
@Service
public class OrderService {
// 调用库存服务、支付服务
@Autowired
private InventoryFeignClient inventoryClient;
@Autowired
private PaymentFeignClient paymentClient;
// 全局事务注解:这是TM的核心操作
@GlobalTransactional(rollbackFor = Exception.class)
public void createOrder(OrderDTO orderDTO) {
// 1. 创建订单(本地事务)
orderMapper.insert(orderDTO);
// 2. 调用库存服务扣库存(远程事务)
inventoryClient.deduct(orderDTO.getProductId(), orderDTO.getCount());
// 3. 调用支付服务扣余额(远程事务)
paymentClient.deduct(orderDTO.getUserId(), orderDTO.getAmount());
}
}
搞定!这样三个服务的事务就被 Seata 管起来了。
Seata 支持三种模式,各有优劣,咱按 “常用程度 + 重点掌握” 排序:
其实分布式事务没那么玄乎 —— 记住 “CAP 取舍是前提,Seata 是工具,AT 模式先上手”,大部分场景都能搞定。
你之前有没有遇到过 “库存飞了”“余额扣了订单没生成” 的坑?或者用 Seata 时踩过版本兼容的雷?评论区聊聊,咱一起避坑~