微服务设计模式 — 补偿事务模式(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 {
    
    @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.";
    }
}

问题和考虑

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

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

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

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

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

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

  1. 优先使用重试模式:

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

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

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

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

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

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

总结

cloud-native-definition-2

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HTML 基础标签——元数据标签 <meta>

文章目录 1. `<meta>` 标签概述2. 属性详解2.1 `charset` 属性2.2 `name` 属性2.3 `content` 属性2.4 `http-equiv` 属性3. 其他常见属性小结在 HTML 文档中,元数据标签 <meta> 是一种重要的标签,用于提供关于文档的信息,这些信息不直接显示在网页内容中,但对于…

Golang | Leetcode Golang题解之第526题优美的排列

题目&#xff1a; 题解&#xff1a; func countArrangement(n int) int {f : make([]int, 1<<n)f[0] 1for mask : 1; mask < 1<<n; mask {num : bits.OnesCount(uint(mask))for i : 0; i < n; i {if mask>>i&1 > 0 && (num%(i1) 0 |…

Rust 力扣 - 1297. 子串的最大出现次数

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 越短的子串出现的次数越多&#xff0c;我们只需要求某个长度为min_size的子串出现的次数&#xff0c;并且该子串中不重复字符小于等于max_letters的数量 遍历长度长度为min_size的子串&#xff0c;然后将不重复…

[neo4j报错]py2neo.errors.ClientError: [Request.Invalid] Not Found解决方案

报错源代码 g Graph(http://localhost:7687, auth("neo4j", "password"))或许这是从网上复制下来的代码&#xff0c;看上去没什么问题&#xff0c;但实际上 要结合具体的浏览器上的地址来看&#xff0c;具体如下&#xff1a; 看到了吗&#xff0c;这里才…

[ shell 脚本实战篇 ] 编写恶意程序实现需求(恶意程序A监测特定目录B出现特定文件C执行恶意操作D-windows)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

Q:无法连接到此网络

无法连接到此网络 今天上午正用浏览器开始搜索&#xff0c;发现网络出了问题&#xff0c;就去找了相关的视频教程&#xff1a; 这些视频大概说了这几种&#xff1a; 设备管理器-网络适配器-找到含有WIFI的选项-查看是否状态异常control-网络和internet-网络和共享中心-更改适…

【实战篇】requests库 - 有道云翻译爬虫 【附:代理IP的使用】

目录 〇、引言一、目标二、请求参数分析三、响应分析四、编写爬虫脚本【隧道代理的使用】 〇、引言 无论是学习工作、旅游出行、跨境电商、日常交流以及一些专业领域都离不开翻译工具的支持。本文就带大家通过爬虫的方式开发一款属于自己的翻译工具~ 一、目标 如下的翻译接口…

[ 应急响应靶场实战 ] VMware 搭建win server 2012应急响应靶机 攻击者获取服务器权限上传恶意病毒 防守方人员应急响应并溯源

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

鸿蒙NEXT开发-学生管理系统小案例(基于最新api12稳定版)

注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得博主文章写的好的话&#xff0c;可以点下关注&#xff0c;博主会一直更新鸿蒙next相关知识 专栏地址: https://blog.csdn.net/qq_56760790/…

keepalived + nginx 实现网站高可用性(HA)

keepalive 一、keepalive简介二、实现步骤1. 环境准备2. 安装 Keepalived3. 配置 Keepalived 双机主备集群架构4. 配置 Nginx5. 启动Keepalived6. 测试高可用性7. 配置keepalived 双主热备集群架构 一、keepalive简介 目前互联网主流的实现WEB网站及数据库服务高可用软件包括&a…

Javase——正则表达式

正则表达式的相关使用 public static void main(String[] args) {//校验QQ号 System.out.println("3602222222".matches("[1-9][0-9]{4,}"));// 校验18位身份证号 System.out.println("11050220240830901X".matches("^([0-9]){7,18}…

数据结构与算法 - 基础

本文首发于 个人博客 程序 数据结构 算法 其实很多同学知道数据结构与算法很重要&#xff0c;但是却不明觉厉。 这里我们看一个简单的题&#xff1a; 对自然数从1到100的求和 最简单的设计无非是&#xff1a; void addNum () { int total 0; for (int i 1; i < 1…

【React 轮子】文本溢出后显示展开/收起按钮

/** hooks* 用于文本展示时判断是否展示 展开/收起按钮 &#xff08;包含监听 文本变化/页面尺寸变换&#xff09;* param { string } text 需要展示的文本* param { number } maxLength 文本最大展示行数* param { number } lineHeight 文本行高 (单位 px) */ import React, …

交通工具图像分割系统:全面扶持小白

交通工具图像分割系统源码&#xff06;数据集分享 [yolov8-seg-vanillanet&#xff06;yolov8-seg-C2f-Parc等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI Global A…

pta题目

1.查询至少生产两种不同的计算机(PC或便携式电脑)且机器速度至少为133的厂商 AC: select distinct(pd.maker) --去重查询 from product pd where pd.type in (个人电脑, 便携式电脑) --题目上要求的&#xff0c;至少一个&#xff0c;in是从里面选择 and --这里也是model其实相…

windows下用CMake构建使用protobuf的应用,编译使用VS2022

最近构建一个使用protobuf的应用&#xff0c;踩了不少坑&#xff0c;在此记录一下 一、编译protobuf protobuf只提供源码&#xff0c;没有编译好的库文件给使用造成一定的障碍&#xff08;差评&#xff09;。所以c应用中使用protobuf的第一步是用cmake对protobuf进行构建。 1.…

计组-主存的分类和编址,随机存取(RAM)和只读(ROM)存储器

随机和只读存储器这2类是有着不同的功能的 像我们的内存就属于随机存取存储器&#xff08;RAM&#xff09;&#xff0c;其特点就是当内存一旦断电时&#xff0c;内存里面的所有数据都将被清除掉&#xff0c;无法保存下来&#xff0c;即一断电信息就会丢失 而ROM在断电后是可以…

【electron+vue3】使用JustAuth实现第三方登录(前后端完整版)

实现过程 去第三方平台拿到client-id和client-secret&#xff0c;并配置一个能够外网访问回调地址redirect-uri供第三方服务回调搭建后端服务&#xff0c;引入justauth-spring-boot-starter直接在配置文件中定义好第一步的三个参数&#xff0c;并提供获取登录页面的接口和回调…

一次线程池使用错误导致的问题

记录一次服务线程数量异常问题的排查过程 背景 通过监控发现一个服务的线程数异常多 同期CPU 内存 网络连接都没有什么异常。 排查 第一个反应就是查看线程栈 "pool-2493-thread-3" #3718833 prio5 os_prio0 tid0x00007f1610041000 nid0x38bff6 waiting on con…

我为何要用wordpress搭建一个自己的独立博客

我在csdn有一个博客&#xff0c;这个博客是之前学习编程时建立的。 博客有哪些好处呢&#xff1f; 1&#xff0c;可以写自己的遇到的问题和如何解决的步骤 2&#xff0c;心得体会&#xff0c;经验&#xff0c;和踩坑 3&#xff0c;可以转载别人的好的技术知识 4&#xff0c;宝贵…