微服务设计模式 — 补偿事务模式(Compensating Transaction Pattern)
微服务设计模式 — 补偿事务模式(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 {@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.";}
}
问题和考虑
以上代码只是理想情况下的一种的简化,主要是借此来说明一下补偿机制的主要思想。尽管补偿事务模式提供了有效的方法保证分布式事务的一致性,但是在设计和实现过程中仍然需要考虑以下问题:
-
补偿逻辑的冗余:需要为每个操作设定相应的补偿操作,这可能增加代码的复杂性和维护成本。
-
不完全回滚:不是所有业务操作都能被完全回滚,设计补偿操作时需谨慎对待,补偿事务不一定总是成功,应该使补偿步骤具备幂等能力,这样即使补偿事务失败,也可以被安全地重复执行。
-
最终一致性:补偿事务模式强调的是最终一致性,而不是强一致性,需要根据具体业务需求权衡。
-
补偿逻辑的专一性:补偿逻辑难以通用化,因为它是特定于应用程序的。应用程序需要足够的信息,才能成功撤销失败操作的每一步。
在实际项目开发中,一般需要有效地结合补偿事务模式和重试模式,提高系统的可靠性并减少事务失败的影响。以下是一些具体的建议:
-
优先使用重试模式:
- 识别瞬态故障:分辨出哪些故障是暂时性的(如网络波动、暂时的资源不可用)并优先对这些故障应用重试模式。
- 设置重试策略:配置合适的重试策略,包括重试次数、重试间隔和指数退避等,以确保重试时不会对系统造成过大负载。
-
设置明确的重试限度:
- 重试次数限制:为每个操作设置重试的最大次数。如果重试次数超过此限度,则不再尝试重试,转而启动补偿事务。
- 超时机制:设置合理的超时机制以防止操作长时间挂起。一旦触发超时,系统应立即停止重试并启动补偿事务。
-
集成补偿事务模式:
- 捕获所有重试失败:确保所有重试失败的情况都会被准确捕获并且能有效地启动补偿事务。
- 确保持久性:记录每一步操作及其状态,以便在重试多次失败后能够在补偿事务中撤销这些操作。
-
补偿事务步骤的幂等性:
- 确保幂等性:补偿事务的步骤必须是幂等的,即使被多次执行也不会对系统状态产生额外影响。这确保如果补偿事务执行过程中出现故障,可以安心地再次执行同样的补偿步骤。
-
设计良好的事务边界:
-
明确的事务边界:清晰地定义事务的开始和结束,确保每个事务都是一个原子操作。尽量减少跨多个服务或数据存储的长事务,减少事务失败的复杂度。
-
资源锁定和管理:按需锁定资源并在补偿事务中优先释放资源,以防止资源长时间被占用导致其他操作受阻。
-
总结
补偿事务模式是一种非常有效的方法,用于处理分布式系统中长时间运行事务的一致性问题。通过在主事务执行失败时执行补偿操作,系统能够恢复到一致性状态。尽管这一设计模式涉及较高的复杂性和代码冗余,但其在保证系统一致性和稳定性方面的优势是不可忽视的。在实际应用中,补偿事务模式广泛用于订单处理、银行转账等需要跨多个服务协调的业务场景。通过本文的示例,希望读者能够更好地理解和应用补偿事务模式。
相关文章:

微服务设计模式 — 补偿事务模式(Compensating Transaction Pattern)
微服务设计模式 — 补偿事务模式(Compensating Transaction Pattern) 定义 在云计算和分布式系统中,管理跨多个微服务或组件的事务一致性是一项极具挑战性的任务,补偿事务模式Compensating Transaction Pattern)是一种…...

20 实战:形状编码、运动补偿和纹理编码的实现(基于python)
在当今多媒体时代,视频处理与编码已经成为各个领域中不可或缺的一部分。无论是视频编辑、流媒体传输,还是计算机视觉应用,视频编码技术都扮演着关键角色。本文将详细解析一个基于Python的图形用户界面(GUI)视频编码器。通过对代码的逐行讲解、功能分析以及参数调节方法的探…...

区块链-C++挖矿软件XMRIG源码分析
C++挖矿软件源码分析 3rdpartybackendgrgon2Obfusheader.hmain 程序 xmrig.cppxmrig命名空间process类Entry::IdApp类CoreControllerbasetoolkernelinterfacesDonateStrategy.cppdonate.h/2/dmiCmake 跨平台的自动化构建系统CMakeLists.txt.cmake 13个引入算力哈希率 HashrateE…...

C语言指针的介绍
零.导言 在日常生活中,我们常常在外出时居住酒店,细心的你一定能发现酒店不同的房间上有着不同的门牌号,上面写着像308,512之类的数字。当你定了酒店之后,你就会拿到一个写有门牌号的钥匙,凭着钥匙就能进入…...

八大排序算法——堆排序
目录 前言 一、向上调整算法建堆 二、向下调整算法建堆 三、堆排序 前言 堆排序是基于堆结构的一种排序思想,因此要为一个乱序的数组进行排序的前提是数组必须要是一个堆,所以要先对数组进行建堆操作 一、向上调整算法建堆 时间复杂度:O…...

U盘文件不翼而飞?这些数据恢复工具帮你找回!
U盘因其便携性是我们日常工作和生活中不可或缺的工具。不过有时候它也会出点小状况。如果你U盘里的数据突然不见了,不要着急,可以先试试这几款数据恢复工具! 福昕数据恢复 直达链接:www.pdf365.cn/foxit-restore/ 操作教程&…...
在Java中 try catch 会影响性能吗?
1、在Java中,异常处理确实会对性能产生影响,但在正常执行的代码路径中,即没有发生异常的情况下,try-catch块的性能影响是微不足道的 2、但是,如果出现异常被抛出时,Java虚拟机需要执行一些额外的操作来处理…...

吞吐量最高飙升20倍!破解强化学习训练部署难题
**强化学习(RL)对大模型复杂推理能力提升有关键作用,然而,RL 复杂的计算流程以及现有系统局限性,也给训练和部署带来了挑战。近日,字节跳动豆包大模型团队与香港大学联合提出 HybridFlow(开源项…...
redis的数据过期策略
Redis对数据设置了数据的有效时间,数据过期之后,就需要将数据从内存中删除掉.可以按照不同的规则进行删除,这种删除规则就被称之为数据的删除策略(数据过期策略),而这种策略有两种:惰性删除和定期删除 惰性删除:设置key过期时间后,我们不去管它,当需要该key时,我们在检查其是否…...

三周精通FastAPI:27 使用使用SQLModel操作SQL (关系型) 数据库
官网文档:https://fastapi.tiangolo.com/zh/tutorial/sql-databases/ SQL (关系型) 数据库 FastAPI不需要你使用SQL(关系型)数据库。 但是您可以使用任何您想要的关系型数据库。 这里我们将看到一个使用SQLModel的示例。 SQLModel是在SQLAlchemy和Pydantic的基础…...

Kubernetes金丝雀发布
华子目录 Canary金丝雀发布什么是金丝雀发布Canary发布方式基于header(http包头)灰度发布基于权重的金丝雀发布 Canary金丝雀发布 什么是金丝雀发布 金丝雀发布也称为灰度发布,是一种软件发布策略主要目的是在将新版本的软件全面推广到生产环…...
树形DP讲解
文章目录 树形DP讲解一、引言二、树形DP基础1、树的定义2、树形DP的基本思想3、代码示例:子树大小 三、经典例题解析1、树的平衡点1.1、代码示例 2、没有上司的舞会(树的最大独立集)2.1、代码示例 四、总结 树形DP讲解 一、引言 树形动态规…...
容器:如何调试容器
调试容器,主要是指的调试Dockerfile,调试Dockerfile中的各个命令的执行,大小等 1、docker history查看构建过程和所有的中间层 2、docker run rm -it -u root XXX sh,通过临时容器的方式启动,可以调试中间层文件 3、do…...

用图说明 CPU、MCU、MPU、SoC 的区别
CPU CPU 负责执行构成计算机程序的指令,执行这些指令所指定的算术、逻辑、控制和输入/输出(I/O)操作。 MCU (microcontroller unit) 不同的 MCU 架构如下,注意这里的 MPU 表示 memory protection unit MPU (microprocessor un…...
牛客周赛 Round 65
文章目录 超市思路:Solved: 雨幕思路:Solved: 闺蜜思路:Solved: 医生思路:Solved: 降温(easy)思路:Solved: F-降温(hard&a…...

超级经典的79个软件测试面试题(内含答案)
1、软件的生命周期(prdctrm) 计划阶段(planning)-〉需求分析(requirement)-〉设计阶段(design)-〉编码(coding)->测试(testing)->运行与维护(running maintrnacne) 测试用例 用例编号 测试项目 测试标题 重要级别 预置条件 输入数据 执行步骤 预期结果 2、问…...
【Mac】安装 F5-TTS
1、下载项目 项目地址:【GitHub】 SWivid F5-TTS 2、创建并激活 Python 虚拟环境 # 创建 Python 虚拟环境 userMac F5-TTS-main % python3 -m venv f5-tts# 激活进入 Python 虚拟环境 userMac F5-TTS-main % source f5-tts/bin/activate (f5-tts) userrMac F5-TT…...

Leaflet查询矢量瓦片偏移的问题
1、问题现象 使用Leaflet绘制工具查询出来的结果有偏移 2、问题排查 1)Leaflet中latLngToContainerPoint和latLngToLayerPoint的区别 2)使用Leaflet查询需要使用像素坐标 3)经排查发现,container获取的坐标是地图容器坐标&…...

存储引擎技术进化
B-tree 目前支撑着数据库产业的半壁江山。 50 年来不变而且人们还没有改变它的意向 鉴定一个算法的优劣,有一个学派叫 IO复杂度分析 ,简单推演真假便知。 下面就用此法分析下 B-tree(traditional b-tree) 的 IO 复杂度,对读、写 IO 一目了…...
CentOS 9 Stream 上安装 Maven
CentOS 9 Stream 上安装 Maven 在 CentOS 9 Stream 上安装 Maven,可以按照以下步骤进行: 更新系统软件包: sudo dnf update安装 Maven: CentOS 9 Stream 默认的包管理器中已经包含 Maven,你可以直接安装: s…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...