分布式事务的实现方式主要有以下几种:
-
两阶段提交(2PC):这是一种最常见的分布式事务协议,它通过预提交、正式提交和回滚三个阶段来实现分布式事务的原子性和一致性。
-
补偿事务(TCC):TCC 是一种基于补偿机制的分布式事务解决方案,它在执行每个参与者的操作之前,先进行一次检查,如果检查通过,则执行业务操作,否则执行回滚操作。
-
最终一致性(XA):XA 是一种较为复杂的分布式事务协议,它通过使用两阶段提交协议和恢复管理器来实现分布式事务的原子性和一致性。
-
Sagas:Sagas 是最近比较流行的一种分布式事务解决方案,它将一个大事务划分为多个小的本地事务,并通过补偿机制来保证整个分布式事务的一致性。
两阶段提交事务(2pc)
在 Java 中实现 2PC 分布式事务,需要涉及到以下几个步骤:
-
首先需要定义两个角色:事务协调者和事务参与者。事务协调者负责协调各个事务参与者的工作,以保证分布式事务的原子性和一致性。
-
然后需要实现事务协调者的代码。在这个类中,需要进行如下操作:
(1)发出 prepare 请求,通知所有的事务参与者准备提交本次事务。
(2)收集所有事务参与者发送回来的“已准备就绪”响应。
(3)如果所有事务参与者都已经准备就绪,则向他们发出 commit 请求,并等待他们的响应。
(4)如果有任何一个事务参与者返回了“不准备就绪”的响应,则向所有事务参与者发出 rollback 请求,撤销事务。
-
最后需要实现事务参与者的代码。在这个类中,需要进行如下操作:
(1)接收到来自事务协调者的 prepare 请求。
(2)执行本地事务,并将执行结果保存在内存中。
(3)如果本地事务执行成功,则发送“已准备就绪”响应给事务协调者。
(4)如果本地事务执行失败,则发送“不准备就绪”响应给事务协调者。
代码示例:
// 事务协调者
public class TransactionCoordinator {
// 存储所有事务参与者的信息
private List<TransactionParticipant> participants;
// 发起分布式事务
public void startTransaction() {
// step1: 向所有参与者发送 prepare 请求
for (TransactionParticipant participant : participants) {
participant.prepare();
}
// step2: 收集所有参与者的响应
List<Boolean> responses = new ArrayList<>();
for (TransactionParticipant participant : participants) {
Boolean response = participant.getPrepareResponse();
responses.add(response);
}
// step3: 如果所有参与者都已准备就绪,则向它们发送 commit 请求
if (!responses.contains(false)) {
for (TransactionParticipant participant : participants) {
participant.commit();
}
} else { // step4: 如果有任意一个参与者未准备就绪,则向它们发送 rollback 请求
for (TransactionParticipant participant : participants) {
participant.rollback();
}
}
}
}
// 事务参与者
public class TransactionParticipant {
// 执行本地事务
public void executeLocalTransaction() {
// ...
}
// 向事务协调者发送 prepare 请求
public void prepare() {
// ...
}
// 获取事务协调者的 prepare 响应
public Boolean getPrepareResponse() {
return true/false;
}
// 向事务协调者发送 commit 请求
public void commit() {
// ...
}
// 向事务协调者发送 rollback 请求
public void rollback() {
// ...
}
}
补偿事务(TCC)
补偿事务(TCC)是一种基于补偿机制的分布式事务解决方案。在 TCC 中,将一个大事务拆分成多个小的本地事务,并通过补偿机制来保证整个分布式事务的一致性。下面是 TCC 在 Java 中的实现方法:
- 定义接口:创建一个 TCC 接口,包含三个方法:try、confirm 和 cancel。try 方法用于执行本地事务;confirm 方法用于提交分布式事务;cancel 方法用于回滚分布式事务。
public interface TccAction {
boolean tryAction();
boolean confirmAction();
boolean cancelAction();
}
- 实现参与者:对于每个本地事务,需要实现一个 TccAction 参与者,在其 try 方法中执行本地事务,在 confirm 和 cancel 方法中执行确认和回滚操作。
public class OrderTccAction implements TccAction {
private String orderId;
public OrderTccAction(String orderId) {
this.orderId = orderId;
}
@Override
public boolean tryAction() {
// 执行本地事务,如下单操作
OrderService orderService = new OrderServiceImpl();
return orderService.createOrder(orderId);
}
@Override
public boolean confirmAction() {
// 提交分布式事务,无需执行任何操作
return true;
}
@Override
public boolean cancelAction() {
// 回滚分布式事务,如取消订单操作
OrderService orderService = new OrderServiceImpl();
return orderService.cancelOrder(orderId);
}
}
- 实现协调者:对于每个分布式事务,需要实现一个 TccCoordinator 协调者,在其 execute 方法中按照 TCC 流程逐步执行 TccAction 的 try、confirm 和 cancel 方法,以确保分布式事务的一致性。
public class OrderTccCoordinator {
private List<TccAction> actions;
public OrderTccCoordinator(List<TccAction> actions) {
this.actions = actions;
}
public void execute() {
try {
// 尝试执行所有 TccAction 的 try 方法
for (TccAction action : actions) {
if (!action.tryAction()) {
throw new RuntimeException("Try phase failed");
}
}
// 尝试提交所有 TccAction 的 confirm 方法
for (TccAction action : actions) {
if (!action.confirmAction()) {
throw new RuntimeException("Confirm phase failed");
}
}
} catch (Exception e) {
// 如果提交 confirm 失败,则需要回滚之前的所有 try 操作
for (TccAction action : actions) {
action.cancelAction();
}
throw e;
}
}
}
- 调用方使用:最后,在调用方代码中使用 TccCoordinator 实例来执行分布式事务,例如:
List<TccAction> actions = new ArrayList<>();
actions.add(new OrderTccAction("order001"));
actions.add(new InventoryTccAction("product001", 10));
OrderTccCoordinator coordinator = new OrderTccCoordinator(actions);
coordinator.execute();
最终一致性(XA)java实现
XA 是一种分布式事务协议,用于保证多个参与者执行的本地事务以原子性的方式提交或回滚。在 Java 中实现 XA 分布式事务,需要遵循以下步骤:
- 实现事务管理器(Transaction Manager):作为整个分布式事务的管理者,负责控制全局事务的启动、提交和回滚等操作。
public class TransactionManager {
private DataSource dataSource;
public TransactionManager(DataSource dataSource) {
this.dataSource = dataSource;
}
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public void startTransaction() throws SQLException {
Connection conn = getConnection();
try {
conn.setAutoCommit(false);
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
// 保存连接对象到 ThreadLocal 变量中
TransactionContextHolder.setCurrentConnection(conn);
} catch (SQLException e) {
conn.rollback();
throw e;
}
}
public void commitTransaction() throws SQLException {
Connection conn = TransactionContextHolder.getCurrentConnection();
if (conn != null) {
try {
conn.commit();
} finally {
conn.close();
// 清除 ThreadLocal 变量
TransactionContextHolder.clearCurrentConnection();
}
}
}
public void rollbackTransaction() throws SQLException {
Connection conn = TransactionContextHolder.getCurrentConnection();
if (conn != null) {
try {
conn.rollback();
} finally {
conn.close();
// 清除 ThreadLocal 变量
TransactionContextHolder.clearCurrentConnection();
}
}
}
}
- 在业务逻辑代码中事务操作前后,调用 TransactionManager 开启和提交/回滚事务:
// 从容器中获取 TransactionManager 对象
TransactionManager txManager = (TransactionManager) context.getBean("transactionManager");
try {
txManager.startTransaction(); // 开启事务
// 执行业务逻辑,如新增订单
OrderService orderService = new OrderServiceImpl();
orderService.createOrder(order);
txManager.commitTransaction(); // 提交事务
} catch (Exception e) {
txManager.rollbackTransaction(); // 回滚事务
throw e;
}
- 配置数据源:配置数据库连接池,将其注入 TransactionManager 中使用:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean id="transactionManager" class="com.example.transaction.TransactionManager">
<constructor-arg ref="dataSource" />
</bean>
Sagas事务
Sagas是一种分布式事务处理模式,它可以在微服务架构中解决分布式事务的问题。Java实现Sagas事务可以通过以下步骤:
-
定义Saga:首先需要定义Saga,即描述分布式事务的过程和流程。Saga通常由多个子事务组成,每个子事务都是一个独立的业务操作。
-
实现Compensating Actions:在定义Saga时,需要为每个子事务实现Compensating Action(补偿操作),以便能够回滚或撤销之前的操作。
-
使用消息队列:在实现Sagas时,使用消息队列来协调各个子事务的执行顺序和状态。如果某个子事务失败,则会将回滚信息发送到消息队列中,以便后续的Compensating Action执行。
-
集成框架:为了更好地实现Sagas事务,可以使用现有的开源框架,如Apache ServiceComb Saga、Eventuate Tram Sagas等。
总的来说,Java实现Sagas事务需要借助消息队列和补偿操作来确保分布式事务的可靠性和正确性。
以下是一个简单的Java代码示例,演示如何实现一个Saga事务:
- 定义Saga和Compensating Actions
@Saga
public class OrderManagementSaga {
@Autowired
private OrderService orderService;
@StartSaga
@SagaEventHandler(eventType = "OrderPlacedEvent")
public void handle(OrderPlacedEvent event) {
// 调用订单服务,创建订单
Order order = orderService.createOrder(event.getOrderId(), event.getItems());
if (order == null) {
throw new RuntimeException("Failed to create order");
}
// 记录补偿操作,在需要回滚时调用
SagaEndEvent sagaEndEvent = new SagaEndEvent();
sagaEndEvent.setOrderId(order.getId());
SagaLifecycle.associateWith("sagaEndEvent", sagaEndEvent);
}
@EndSaga
@SagaEventHandler(eventType = "PaymentReceivedEvent")
public void handle(PaymentReceivedEvent event) {
// 更新订单状态为已支付
Order order = orderService.updateOrderStatus(event.getOrderId(), OrderStatus.PAID);
if (order == null) {
throw new RuntimeException("Failed to update order status");
}
}
@SagaEventHandler(eventType = "PaymentFailedEvent")
public void handle(PaymentFailedEvent event) {
// 执行Compensating Action,取消订单
orderService.cancelOrder(event.getOrderId());
// 删除关联的Saga信息
SagaLifecycle.end();
}
@SagaEventHandler(eventType = "SagaEndEvent")
public void handle(SagaEndEvent event) {
// 执行Compensating Action,删除订单
orderService.deleteOrder(event.getOrderId());
}
}
- 集成框架(例如Apache ServiceComb Saga)
<dependency>
<groupId>org.apache.servicecomb.pack</groupId>
<artifactId>saga-spring-starter</artifactId>
<version>0.5.0-incubating</version>
</dependency>
- 使用消息队列来协调各个子事务的执行顺序和状态
在Apache ServiceComb Saga中,可以使用Kafka或Redis作为消息队列。
- 触发Saga事务
@Service
public class OrderService {
@Autowired
private SagaStartedMessageSender sagaMessageSender;
public Order createOrder(String orderId, List<OrderItem> items) {
// 创建订单
Order order = new Order(orderId, items);
// 发送事件,触发Saga事务
OrderPlacedEvent event = new OrderPlacedEvent();
event.setOrderId(orderId);
event.setItems(items);
sagaMessageSender.send(event);
return order;
}
}
- 事务的选型
-
数据库支持:首先需要选择支持分布式事务的数据库,例如MySQL InnoDB引擎、Oracle RAC等。必要时可以考虑使用NoSQL等非关系型数据库。
-
业务场景:不同的业务场景需要采用不同的事务模型,在2PC、3PC、TCC等模型中根据具体情况进行选择。
-
可靠性要求:如果对数据的可靠性要求较高,则需要选择具有强一致性的事务模型,如2PC;如果对可靠性要求较低,则可以采用比较灵活的TCC模型。
-
性能需求:不同的事务模型对性能的影响也不同,如2PC模型需要在提交和回滚时进行网络通信,可能会影响性能。因此,在选择事务模型时需要考虑性能方面的因素。
-
实现难度:不同的事务模型实现难度不同,如2PC需要协调者和参与者之间的信息交互,并且容易出现死锁等问题。因此,在选择事务模型时需要考虑实现难度方面的因素。
-
社区支持:选择一个拥有活跃社区支持的事务框架,可以更好地保证系统的稳定性和可维护性。
- 分布式事务使用的注意事项
-
数据库选型:选择支持分布式事务的数据库,例如MySQL InnoDB引擎、Oracle RAC等。必要时可以考虑使用NoSQL等非关系型数据库。
-
事务模型:根据业务需求选择合适的事务模型,如两阶段提交(2PC)、三阶段提交(3PC)、补偿事务(TCC)等。每种模型都有其优缺点,需要根据具体情况选择。
-
隔离级别:分布式事务中各个节点的隔离级别应该统一设置为串行化(Serializable),以避免出现脏读、不可重复读、幻读等问题。
-
超时机制:设置合理的超时时间,防止单个节点发生故障导致整个事务无法完成。
-
异常处理:针对各种可能的异常,如网络故障、节点宕机、数据不一致等,需要编写相应的异常处理代码,在事务失败时回滚或者进行补偿操作。
-
安全问题:由于涉及多个节点的通信和数据传输,分布式事务的安全性需要得到保证。可以采用加密手段、访问控制等方式进行安全保护。