微服务设计模式 — 补偿事务模式(Compensating Transaction Pattern)
定义
在云计算和分布式系统中,管理跨多个微服务或组件的事务一致性是一项极具挑战性的任务,补偿事务模式Compensating Transaction Pattern)是一种允许在分布式系统中处理长时间运行的跨多个服务的事务一致性的方法。在执行主要事务步骤时,系统记录每个步骤的补偿操作(即回滚操作),以便在事务失败时可以撤销已执行的操作。简而言之,补偿事务通过逆向操作来确保系统达到一致性状态。
结构
补偿事务模式通常由以下几个部分组成:
-
主事务:主要事务逻辑,包含一系列需要执行的业务步骤。
-
补偿操作:用于撤销主事务中的某个步骤,如果该步骤失败则触发补偿操作。
-
事务管理器:负责协调事务步骤和补偿步骤的执行。
工作原理
补偿事务的工作原理如下:
- 执行主事务步骤:按照预定的业务逻辑,依次执行每个操作步骤。
- 记录补偿操作:在每个步骤成功后,记录对应的补偿操作,以备将来可能的回滚。
- 检测失败:在每个步骤执行期间,检测到失败时,立即执行已记录的补偿操作,撤销此前已完成的步骤。
- 成功完成:所有步骤成功后,事务完成;否则,执行完整的补偿逻辑,确保系统状态回滚至初始状态。
好处
-
高可用性:即使某些服务暂时不可用,补偿事务模式也能确保其他步骤的事务完成并且系统保持一致性。
-
灵活性:补偿操作提供了更多的控制和灵活性,可以根据业务逻辑定制补偿步骤。
-
低耦合性:通过分离主事务和补偿事务,可以降低服务之间的耦合性。
-
更好的性能:相比于两阶段提交,补偿事务模式更高效,不需要在所有资源上保持锁定,更适用于需要灵活性和容忍短暂不一致性的分布式系统,而两阶段提交则适用于需要强一致性和事务原子性的关键性场景。
应用场景
在微服务架构中,补偿事务模式广泛用于确保跨多个服务的长时间运行操作之间的一致性。以下是一些常见的应用场景:
- 订单处理系统:处理跨多个服务的订单,如支付、库存扣减和物流等步骤。
- 银行转账系统:处理跨多个银行账户的转账操作,如扣款、汇入和通知等步骤。
- 旅游预订系统:处理酒店预订、航班预订和租车预订等多个步骤。
以订单处理系统为例,演示如何使用补偿事务模式。假设订单处理系统提供如下服务:
- 服务1:支付服务
- 服务2:库存扣减服务
- 服务3:物流预订服务
每个服务分别执行其操作,并在失败时触发补偿操作,具体流程如下:
示例代码
以下是一个简单的示例代码,在 Spring Boot 中实现补偿事务,并没有引入特别的事务补偿框架或者库。
项目结构
compensating-transaction/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ ├── com/
│ │ │ │ ├── example/
│ │ │ │ │ ├── controller/
│ │ │ │ │ ├── service/
│ │ │ │ │ ├── model/
│ │ │ │ │ ├── repository/
│ │ │ │ │ ├── CompensatingTransactionApplication.java
│ ├── resources/
│ │ ├── application.properties
配置文件
application.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
主类
CompensatingTransactionApplication.java
:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CompensatingTransactionApplication {
public static void main(String[] args) {
SpringApplication.run(CompensatingTransactionApplication.class, args);
}
}
模型类
model/Order.java
:
package com.example.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String status;
// Getters and setters
}
仓储接口
repository/OrderRepository.java
:
package com.example.repository;
import com.example.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
}
服务类
service/OrderService.java
:
package com.example.service;
import com.example.model.Order;
import com.example.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public void processOrder() {
// 执行支付操作
try {
performPayment();
} catch (Exception e) {
performPaymentCompensation();
return;
}
// 执行库存扣减
try {
performInventory();
} catch (Exception e) {
performInventoryCompensation();
performPaymentCompensation();
return;
}
// 执行物流预订
try {
performShipping();
} catch (Exception e) {
performShippingCompensation();
performInventoryCompensation();
performPaymentCompensation();
return;
}
}
private void performPayment() throws Exception {
// 模拟支付操作
Order order = new Order();
order.setStatus("PAYMENT_SUCCESS");
orderRepository.save(order);
// 如果支付失败,抛出异常
}
private void performPaymentCompensation() {
// 模拟支付补偿操作
System.out.println("Payment compensation executed.");
}
private void performInventory() throws Exception {
// 模拟库存扣减
Order order = new Order();
order.setStatus("INVENTORY_SUCCESS");
orderRepository.save(order);
// 如果库存扣减失败,抛出异常
}
private void performInventoryCompensation() {
// 模拟库存扣减补偿操作
System.out.println("Inventory compensation executed.");
}
private void performShipping() throws Exception {
// 模拟物流预订
Order order = new Order();
order.setStatus("SHIPPING_SUCCESS");
orderRepository.save(order);
// 如果物流预订失败,抛出异常
}
private void performShippingCompensation() {
// 模拟物流预订补偿操作
System.out.println("Shipping compensation executed.");
}
}
控制器类
controller/OrderController.java
:
package com.example.controller;
import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/process")
public String processOrder() {
orderService.processOrder();
return "Order processed.";
}
}
问题和考虑
以上代码只是理想情况下的一种的简化,主要是借此来说明一下补偿机制的主要思想。尽管补偿事务模式提供了有效的方法保证分布式事务的一致性,但是在设计和实现过程中仍然需要考虑以下问题:
-
补偿逻辑的冗余:需要为每个操作设定相应的补偿操作,这可能增加代码的复杂性和维护成本。
-
不完全回滚:不是所有业务操作都能被完全回滚,设计补偿操作时需谨慎对待,补偿事务不一定总是成功,应该使补偿步骤具备幂等能力,这样即使补偿事务失败,也可以被安全地重复执行。
-
最终一致性:补偿事务模式强调的是最终一致性,而不是强一致性,需要根据具体业务需求权衡。
-
补偿逻辑的专一性:补偿逻辑难以通用化,因为它是特定于应用程序的。应用程序需要足够的信息,才能成功撤销失败操作的每一步。
在实际项目开发中,一般需要有效地结合补偿事务模式和重试模式,提高系统的可靠性并减少事务失败的影响。以下是一些具体的建议:
-
优先使用重试模式:
- 识别瞬态故障:分辨出哪些故障是暂时性的(如网络波动、暂时的资源不可用)并优先对这些故障应用重试模式。
- 设置重试策略:配置合适的重试策略,包括重试次数、重试间隔和指数退避等,以确保重试时不会对系统造成过大负载。
-
设置明确的重试限度:
- 重试次数限制:为每个操作设置重试的最大次数。如果重试次数超过此限度,则不再尝试重试,转而启动补偿事务。
- 超时机制:设置合理的超时机制以防止操作长时间挂起。一旦触发超时,系统应立即停止重试并启动补偿事务。
-
集成补偿事务模式:
- 捕获所有重试失败:确保所有重试失败的情况都会被准确捕获并且能有效地启动补偿事务。
- 确保持久性:记录每一步操作及其状态,以便在重试多次失败后能够在补偿事务中撤销这些操作。
-
补偿事务步骤的幂等性:
- 确保幂等性:补偿事务的步骤必须是幂等的,即使被多次执行也不会对系统状态产生额外影响。这确保如果补偿事务执行过程中出现故障,可以安心地再次执行同样的补偿步骤。
-
设计良好的事务边界:
-
明确的事务边界:清晰地定义事务的开始和结束,确保每个事务都是一个原子操作。尽量减少跨多个服务或数据存储的长事务,减少事务失败的复杂度。
-
资源锁定和管理:按需锁定资源并在补偿事务中优先释放资源,以防止资源长时间被占用导致其他操作受阻。
-
总结
补偿事务模式是一种非常有效的方法,用于处理分布式系统中长时间运行事务的一致性问题。通过在主事务执行失败时执行补偿操作,系统能够恢复到一致性状态。尽管这一设计模式涉及较高的复杂性和代码冗余,但其在保证系统一致性和稳定性方面的优势是不可忽视的。在实际应用中,补偿事务模式广泛用于订单处理、银行转账等需要跨多个服务协调的业务场景。通过本文的示例,希望读者能够更好地理解和应用补偿事务模式。