微服务设计模式 — 补偿事务模式(Compensating Transaction Pattern)

微服务设计模式 — 补偿事务模式(Compensating Transaction Pattern)

Compensating Transaction Pattern

定义

在云计算和分布式系统中,管理跨多个微服务或组件的事务一致性是一项极具挑战性的任务,补偿事务模式Compensating Transaction Pattern)是一种允许在分布式系统中处理长时间运行的跨多个服务的事务一致性的方法。在执行主要事务步骤时,系统记录每个步骤的补偿操作(即回滚操作),以便在事务失败时可以撤销已执行的操作。简而言之,补偿事务通过逆向操作来确保系统达到一致性状态。

结构

补偿事务模式通常由以下几个部分组成:

  1. 主事务:主要事务逻辑,包含一系列需要执行的业务步骤。

  2. 补偿操作:用于撤销主事务中的某个步骤,如果该步骤失败则触发补偿操作。

  3. 事务管理器:负责协调事务步骤和补偿步骤的执行。

工作原理

补偿事务的工作原理如下:

  1. 执行主事务步骤:按照预定的业务逻辑,依次执行每个操作步骤。
  2. 记录补偿操作:在每个步骤成功后,记录对应的补偿操作,以备将来可能的回滚。
  3. 检测失败:在每个步骤执行期间,检测到失败时,立即执行已记录的补偿操作,撤销此前已完成的步骤。
  4. 成功完成:所有步骤成功后,事务完成;否则,执行完整的补偿逻辑,确保系统状态回滚至初始状态。

好处

  1. 高可用性:即使某些服务暂时不可用,补偿事务模式也能确保其他步骤的事务完成并且系统保持一致性。

  2. 灵活性:补偿操作提供了更多的控制和灵活性,可以根据业务逻辑定制补偿步骤。

  3. 低耦合性:通过分离主事务和补偿事务,可以降低服务之间的耦合性。

  4. 更好的性能:相比于两阶段提交,补偿事务模式更高效,不需要在所有资源上保持锁定,更适用于需要灵活性和容忍短暂不一致性的分布式系统,而两阶段提交则适用于需要强一致性和事务原子性的关键性场景。

应用场景

在微服务架构中,补偿事务模式广泛用于确保跨多个服务的长时间运行操作之间的一致性。以下是一些常见的应用场景:

  1. 订单处理系统:处理跨多个服务的订单,如支付、库存扣减和物流等步骤。
  2. 银行转账系统:处理跨多个银行账户的转账操作,如扣款、汇入和通知等步骤。
  3. 旅游预订系统:处理酒店预订、航班预订和租车预订等多个步骤。

订单处理系统为例,演示如何使用补偿事务模式。假设订单处理系统提供如下服务:

  • 服务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 {@Autowiredprivate OrderRepository orderRepository;@Transactionalpublic 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 {@Autowiredprivate OrderService orderService;@GetMapping("/process")public String processOrder() {orderService.processOrder();return "Order processed.";}
}

问题和考虑

以上代码只是理想情况下的一种的简化,主要是借此来说明一下补偿机制的主要思想。尽管补偿事务模式提供了有效的方法保证分布式事务的一致性,但是在设计和实现过程中仍然需要考虑以下问题:

  1. 补偿逻辑的冗余:需要为每个操作设定相应的补偿操作,这可能增加代码的复杂性和维护成本。

  2. 不完全回滚:不是所有业务操作都能被完全回滚,设计补偿操作时需谨慎对待,补偿事务不一定总是成功,应该使补偿步骤具备幂等能力,这样即使补偿事务失败,也可以被安全地重复执行。

  3. 最终一致性:补偿事务模式强调的是最终一致性,而不是强一致性,需要根据具体业务需求权衡。

  4. 补偿逻辑的专一性:补偿逻辑难以通用化,因为它是特定于应用程序的。应用程序需要足够的信息,才能成功撤销失败操作的每一步。

在实际项目开发中,一般需要有效地结合补偿事务模式和重试模式,提高系统的可靠性并减少事务失败的影响。以下是一些具体的建议:

  1. 优先使用重试模式:

    • 识别瞬态故障:分辨出哪些故障是暂时性的(如网络波动、暂时的资源不可用)并优先对这些故障应用重试模式。
    • 设置重试策略:配置合适的重试策略,包括重试次数、重试间隔和指数退避等,以确保重试时不会对系统造成过大负载。
  2. 设置明确的重试限度:

    • 重试次数限制:为每个操作设置重试的最大次数。如果重试次数超过此限度,则不再尝试重试,转而启动补偿事务。
    • 超时机制:设置合理的超时机制以防止操作长时间挂起。一旦触发超时,系统应立即停止重试并启动补偿事务。
  3. 集成补偿事务模式:

    • 捕获所有重试失败:确保所有重试失败的情况都会被准确捕获并且能有效地启动补偿事务。
    • 确保持久性:记录每一步操作及其状态,以便在重试多次失败后能够在补偿事务中撤销这些操作。
  4. 补偿事务步骤的幂等性:

    • 确保幂等性:补偿事务的步骤必须是幂等的,即使被多次执行也不会对系统状态产生额外影响。这确保如果补偿事务执行过程中出现故障,可以安心地再次执行同样的补偿步骤。
  5. 设计良好的事务边界:

    • 明确的事务边界:清晰地定义事务的开始和结束,确保每个事务都是一个原子操作。尽量减少跨多个服务或数据存储的长事务,减少事务失败的复杂度。

    • 资源锁定和管理:按需锁定资源并在补偿事务中优先释放资源,以防止资源长时间被占用导致其他操作受阻。

总结

cloud-native-definition-2

补偿事务模式是一种非常有效的方法,用于处理分布式系统中长时间运行事务的一致性问题。通过在主事务执行失败时执行补偿操作,系统能够恢复到一致性状态。尽管这一设计模式涉及较高的复杂性和代码冗余,但其在保证系统一致性和稳定性方面的优势是不可忽视的。在实际应用中,补偿事务模式广泛用于订单处理、银行转账等需要跨多个服务协调的业务场景。通过本文的示例,希望读者能够更好地理解和应用补偿事务模式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/57940.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java实战项目-基于SpringBoot+Vue的二手车交易系统的研究与实现

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

[mysql]数据定义语言DDL和数据操作语言DCL

目录 前文提要 数据定义语言DDL 数据操作语言DML 数据控制语言DCL 基础知识: 标识符(命名规则): 数据定义语言DDL 创建和管理数据库.: 管理数据库 切换数据库 修改数据库 更改数据库字符集 删除数据库 如何创建表 方式1:”白手起家的方式”创建表 方式2:已经有…

webpack使用详解

摘要&#xff1a;webpack作为一款主流的构建工具&#xff0c;对比后来者Vite虽然存在一些缺点&#xff0c;例如启动慢&#xff0c;配置复杂等。在很多项目中使用依然基于webpack构建&#xff0c;有必要掌握其概念、构建流程和配置方法。 1 webpack概述 1.1 基本概念 webpack …

基于YOLO11/v10/v8/v5深度学习的维修工具检测识别系统设计与实现【python源码+Pyqt5界面+数据集+训练代码】

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

Zypher Network:全栈式 Web3 游戏引擎,服务器抽象叙事的引领者

近期&#xff0c;《黑神话&#xff1a;悟空》的爆火不仅让 AAA 游戏重回焦点&#xff0c;也引发了玩家与开发者的热议。Web2 游戏的持续成功导致部分 Web3 玩家们的倒戈&#xff0c;对比之下 Web3 游戏存在生命周期短且商业模式难以明确的问题&#xff0c;尤其在当前加密市场环…

H7-TOOL自制Flash读写保护算法系列,为兆易创新GD32E23X制作使能和解除算法,支持在线烧录和脱机烧录使用(2024-10-29)

说明&#xff1a; 很多IC厂家仅发布了内部Flash算法文件&#xff0c;并没有提供读写保护算法文件&#xff0c;也就是选项字节算法文件&#xff0c;需要我们制作。 实际上当前已经发布的TOOL版本&#xff0c;已经自制很多了。但是依然有些厂家还没自制&#xff0c;所以陆续开始…

flutter 写个简单的界面

起因&#xff0c; 目的: 来源: 客户需求。 着急要&#xff0c;我随便写的&#xff0c;应付一下。 过程: 略&#xff0c;直接看代码&#xff0c;看注释。 代码 1 xxx import package:flutter/material.dart;void main() {runApp(const MyApp()); }// # class MyApp extends…

.NET 8 中 Entity Framework Core 的使用

本文代码&#xff1a;https://download.csdn.net/download/hefeng_aspnet/89935738 概述 Entity Framework Core (EF Core) 已成为 .NET 开发中数据访问的基石工具&#xff0c;为开发人员提供了强大而多功能的解决方案。随着 .NET 8 和 C# 10 中引入的改进&#xff0c;开发人…

推荐一款可视化和检查原始数据的工具:RawDigger

RawDigger是一款强大的工具&#xff0c;旨在可视化和检查相机记录的原始数据。它被称为一种“显微镜”&#xff0c;使用户能够深入分析原始图像数据&#xff0c;而不对其进行任何更改。RawDigger并不是一个原始转换器&#xff0c;而是一个帮助用户查看将由转换器使用的数据的工…

第三十三章 Vue路由进阶路由模块封装

目录 一、引言 二、完整代码 main.js index.js App.vue Find.vue My.vue 一、引言 在上一个章节中&#xff0c;我们将所有的路由配置都堆在main.js中来实现路径组件的路由&#xff0c;这样做的话非常不利于我们后期对项目的维护。因此正确的做法是将路由模块抽离出来&a…

基于java+SpringBoot+Vue的新闻推荐系统设计与实现

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis Maven mysql5.7或8.0等等组成&#x…

指派问题的求解

实验类型&#xff1a;◆验证性实验 ◇综合性实验 ◇设计性实验 实验目的&#xff1a;学会使用Matlab求解指派问题。 实验内容&#xff1a;利用Matlab编程实现枚举法求解指派问题。 实验例题&#xff1a;有5人分别对应完成5项工作&#xff0c;其各自的耗费如下表所示&#…

下载安装COPT+如何在jupyter中使用(安装心得,windows,最新7.2版本)

目录 1.到杉树科技官网申请下载COPT 2.安装COPT&配置许可文件 3.在jupyter中使用COPT的python接口 最近看到一本和数学建模有关的新书&#xff1a;《数学建模与数学规划&#xff1a;方法、案例及编程实战》&#xff0c;作为数学建模老手&#xff0c;肯定要学习一下&…

基于“互联网+”医养结合的智慧养老实训室建设方案

一、建设背景 根据国家统计局的数据&#xff0c;截至2023年末&#xff0c;我国60岁及以上的老年人口已达到29,697万人&#xff0c;占总人口的21.1%&#xff1b;其中&#xff0c;65岁及以上的人口为21,676万人&#xff0c;占总人口的15.4%。这一数据表明&#xff0c;我国正面临…

为什么需要MQ消息系统,mysql 不能满足需求吗?

大家好&#xff0c;我是锋哥。今天分享关于【为什么需要MQ消息系统&#xff0c;mysql 不能满足需求吗&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; 为什么需要MQ消息系统&#xff0c;mysql 不能满足需求吗&#xff1f; 1000道 互联网大厂Java工程师 精选面试…

计算机网络-以太网小结

前导码与帧开始分界符有什么区别? 前导码--解决帧同步/时钟同步问题 帧开始分界符-解决帧对界问题 集线器 集线器通过双绞线连接终端, 学校机房的里面就有集线器 这种方式仍然属于共享式以太网, 传播方式依然是广播 网桥: 工作特点: 1.如果转发表中存在数据接收方的端口信息…

C/C++常用编译工具链:GCC,Clang

目录 GNU Compiler Collection GCC的优势 编译产生的中间文件 Clang Clang的特点 什么是LLVM&#xff1f; Clang编译过程中产生的中间表示文件 关于Clang的调试 C 编译工具链中有几个主要的编译工具&#xff0c;包括&#xff1a; GNU Compiler Collection (GCC…

NNLM——预测下一个单词

一、原理篇 NNLM&#xff08;Neural Network Language Model&#xff0c;神经网络语言模型&#xff09;是一种通过神经网络进行语言建模的技术&#xff0c;通常用于预测序列中的下一个词。 NNLM的核心思想是使用词嵌入&#xff08;word embedding&#xff09;将词转换为低维向…

【C++】类和对象(十二):实现日期类

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解C的实现日期类&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 1 /!/>/</>/<运算符重载2 /-//-运算符重载(A) 先写&#xff0c;再通过写(B…

KTHREAD--InitialStack和KernelStack和TSS的esp0

InitialStack和TSS.esp0的关系,在这里可以看到 mov ecx, [esi_KTHREAD.InitialStack] ; esi: newthread lea eax, [ecx-210h] ; 越过FPXSAVE指令存储地址 test byte ptr [eax-1Ah], 2 ; 判断efalgs寄存器的VIF位是否为1 jnz short loc_458743 sub eax, 10h…