在分布式系统中,事务管理因涉及多个服务和数据库而变得复杂。分布式事务需要在高并发、跨节点的环境中保证数据一致性,广泛应用于电商、支付和微服务架构。本文将深入探讨分布式事务的背景、需求和常见解决方案,分析其优缺点,并通过一个 Spring Boot 3.2 集成 Seata 的示例展示如何实现分布式事务。本文面向 Java 开发者、架构师和系统工程师,目标是提供一份清晰的中文技术指南,帮助在 2025 年的分布式环境中有效管理事务。
分布式事务是指跨越多个节点(服务或数据库)的事务操作,需要保证所有操作要么全部成功,要么全部失败。与单机事务(ACID:原子性、一致性、隔离性、持久性)不同,分布式事务因网络延迟、节点故障和数据分区而更复杂。分布式事务的典型场景包括:
在单机系统中,数据库(如 MySQL)通过本地事务(BEGIN
、COMMIT
、ROLLBACK
)保证一致性。然而,分布式系统因以下特点导致本地事务不足:
分布式事务的目标是确保跨节点操作满足 ACID 特性,或在弱一致性场景下(如最终一致性)提供可接受的折衷。
一个优秀的分布式事务方案需要满足以下要求:
以下是分布式事务的常见解决方案,涵盖两阶段提交、补偿事务和消息队列等。
方案 | 一致性 | 性能 | 易用性 | 适用场景 |
---|---|---|---|---|
2PC | 强 | 低 | 高 | 强一致性、低并发 |
3PC | 强 | 中 | 中 | 强一致性、改进 2PC |
TCC | 强 | 高 | 低 | 高并发、复杂业务 |
消息队列 | 最终 | 高 | 高 | 高并发、弱一致性 |
Saga | 最终 | 高 | 中 | 微服务、长事务 |
Seata | 强/最终 | 高 | 高 | 综合场景、电商 |
以下是一个 Spring Boot 3.2 应用,集成 Seata 的 AT 模式实现订单和库存的分布式事务。
安装 Seata:
wget https://github.com/seata/seata/releases/download/v2.1.0/seata-server-2.1.0.tar.gz
./seata-server.sh
创建数据库:
CREATE DATABASE order_db;
CREATE DATABASE inventory_db;
USE order_db;
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id VARCHAR(50),
product_id BIGINT,
amount DECIMAL(10,2),
status VARCHAR(20)
);
USE inventory_db;
CREATE TABLE inventory (
product_id BIGINT PRIMARY KEY,
stock INT
);
INSERT INTO inventory (product_id, stock) VALUES (1, 100);
-- Seata 回滚日志表
CREATE TABLE undo_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
branch_id BIGINT NOT NULL,
xid VARCHAR(100) NOT NULL,
context VARCHAR(128) NOT NULL,
rollback_info LONGBLOB NOT NULL,
log_status INT NOT NULL,
log_created DATETIME NOT NULL,
log_modified DATETIME NOT NULL,
UNIQUE KEY ux_undo_log (xid, branch_id)
);
创建 Spring Boot 项目:
order-service
和 inventory-service
。spring-boot-starter-web
spring-boot-starter-data-jpa
seata-spring-boot-starter
lombok
<project>
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>3.2.0version>
parent>
<groupId>com.examplegroupId>
<artifactId>order-serviceartifactId>
<version>0.0.1-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
<version>2.1.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.33version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
project>
inventory-service
使用相同依赖。
配置 application.yml
:
order-service
:spring:
application:
name: order-service
datasource:
url: jdbc:mysql://localhost:3306/order_db?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
server:
port: 8081
seata:
enabled: true
application-id: order-service
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
grouplist:
default: 127.0.0.1:8091
logging:
level:
root: INFO
com.example.order: DEBUG
io.seata: INFO
inventory-service
:spring:
application:
name: inventory-service
datasource:
url: jdbc:mysql://localhost:3306/inventory_db?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
server:
port: 8082
seata:
enabled: true
application-id: inventory-service
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
grouplist:
default: 127.0.0.1:8091
logging:
level:
root: INFO
com.example.inventory: DEBUG
io.seata: INFO
运行环境:
订单服务:
Order.java
):package com.example.order.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.Data;
@Entity
@Data
public class Order {
@Id
private Long id;
private String userId;
private Long productId;
private Double amount;
private String status;
}
OrderRepository.java
):package com.example.order.repository;
import com.example.order.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository<Order, Long> {
}
OrderService.java
):package com.example.order.service;
import com.example.order.entity.Order;
import com.example.order.repository.OrderRepository;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private RestTemplate restTemplate;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public String createOrder(Long productId, String userId, Double amount) {
// 创建订单
Order order = new Order();
order.setId(System.currentTimeMillis());
order.setUserId(userId);
order.setProductId(productId);
order.setAmount(amount);
order.setStatus("PENDING");
orderRepository.save(order);
log.info("Order created: {}", order);
// 调用库存服务
String result = restTemplate.getForObject(
"http://localhost:8082/inventory/deduct/{productId}/{quantity}",
String.class, productId, 1);
if (!"Success".equals(result)) {
throw new RuntimeException("Failed to deduct inventory");
}
// 更新订单状态
order.setStatus("SUCCESS");
orderRepository.save(order);
log.info("Order completed: {}", order);
return "Order created successfully";
}
}
OrderController.java
):package com.example.order.controller;
import com.example.order.service.OrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Tag(name = "订单服务", description = "分布式事务订单管理")
public class OrderController {
@Autowired
private OrderService orderService;
@Operation(summary = "创建订单")
@GetMapping("/order/create/{productId}/{userId}/{amount}")
public String createOrder(@PathVariable Long productId, @PathVariable String userId,
@PathVariable Double amount) {
return orderService.createOrder(productId, userId, amount);
}
}
RestTemplateConfig.java
):package com.example.order.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
库存服务:
Inventory.java
):package com.example.inventory.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.Data;
@Entity
@Data
public class Inventory {
@Id
private Long productId;
private Integer stock;
}
InventoryRepository.java
):package com.example.inventory.repository;
import com.example.inventory.entity.Inventory;
import org.springframework.data.jpa.repository.JpaRepository;
public interface InventoryRepository extends JpaRepository<Inventory, Long> {
}
InventoryService.java
):package com.example.inventory.service;
import com.example.inventory.entity.Inventory;
import com.example.inventory.repository.InventoryRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Slf4j
public class InventoryService {
@Autowired
private InventoryRepository inventoryRepository;
@Transactional
public String deductInventory(Long productId, Integer quantity) {
Inventory inventory = inventoryRepository.findById(productId).orElseThrow();
if (inventory.getStock() < quantity) {
log.warn("Insufficient stock for product {}", productId);
throw new RuntimeException("Insufficient stock");
}
inventory.setStock(inventory.getStock() - quantity);
inventoryRepository.save(inventory);
log.info("Deducted {} from product {} stock, remaining: {}", quantity, productId, inventory.getStock());
return "Success";
}
}
InventoryController.java
):package com.example.inventory.controller;
import com.example.inventory.service.InventoryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Tag(name = "库存服务", description = "分布式事务库存管理")
public class InventoryController {
@Autowired
private InventoryService inventoryService;
@Operation(summary = "扣减库存")
@GetMapping("/inventory/deduct/{productId}/{quantity}")
public String deductInventory(@PathVariable Long productId, @PathVariable Integer quantity) {
return inventoryService.deductInventory(productId, quantity);
}
}
运行并验证:
mvn spring-boot:run -pl order-service
mvn spring-boot:run -pl inventory-service
curl http://localhost:8081/order/create/1/user123/999.99
Order created successfully
SELECT * FROM order_db.orders;
SELECT * FROM inventory_db.inventory;
SUCCESS
,库存减少 1。UPDATE inventory_db.inventory SET stock = 0 WHERE product_id = 1;
curl http://localhost:8081/order/create/1/user456/999.99
Error: Failed to deduct inventory
@GlobalTransactional
标记全局事务,Seata TC 分配 XID。undo_log
记录回滚信息。undo_log
中的补偿。undo_log
保证强一致性。undo_log
表。undo_log
写入 ~10ms/事务。@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class OrderTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void testCreateOrder() {
long start = System.currentTimeMillis();
ResponseEntity<String> response = restTemplate.getForEntity(
"/order/create/1/user123/999.99", String.class);
System.out.println("Create order: " + (System.currentTimeMillis() - start) + " ms");
Assertions.assertTrue(response.getBody().contains("successfully"));
}
}
方案 | 一致性 | 性能 | 易用性 | 适用场景 |
---|---|---|---|---|
2PC | 强 | 低 | 高 | 强一致性、低并发 |
3PC | 强 | 中 | 中 | 强一致性、改进 2PC |
TCC | 强 | 高 | 低 | 高并发、复杂业务 |
消息队列 | 最终 | 高 | 高 | 高并发、弱一致性 |
Saga | 最终 | 高 | 中 | 微服务、长事务 |
Seata | 强/最终 | 高 | 高 | 综合场景、电商 |
问题1:Seata Server 宕机:
./seata-server.sh --raft
问题2:回滚失败:
undo_log
丢失或数据不一致。spring:
jpa:
properties:
hibernate:
connection:
isolation: 4 # REPEATABLE_READ
undo_log
:DELETE FROM undo_log WHERE log_created < NOW() - INTERVAL 7 DAY;
问题3:性能瓶颈:
seata.server.max-branch-session=10000
seata.server.max-global-session=1000
seata:
async-committing: true
问题4:分支事务超时:
seata:
client:
tm:
default-global-transaction-timeout: 60000
案例1:电商订单:
案例2:银行转账:
分布式事务 是分布式系统中保证数据一致性的核心技术,Seata 以其综合性和易用性成为主流选择。常见方案包括 2PC、TCC、消息队列、Saga 和 Seata,各有适用场景。示例通过 Spring Boot 3.2 和 Seata AT 模式实现订单和库存事务,性能测试表明单节点 TPS ~200,强一致性。建议: