强一致性的皇冠:分布式事务模型的至高法则揭秘
关注微信公众号 “程序员小胖” 每日技术干货,第一时间送达!
引言
分布式事务模型是分布式系统设计的核心,关键在于保证数据一致性和事务完整性,尤其强调强一致性。诸如2PC、3PC、Saga、TCC等模型与协议,应运而生以解决分布式ACID实现难题,包括处理网络延迟、节点故障及并发控制等。它们追求在一致性和系统性能、可用性上的平衡。在微服务和云计算时代,高效灵活的分布式事务管理对于构建可靠、高性能系统尤为关键,对技术人员来说,精通这些模型及其应用场景,对提升系统质量和用户体验至关重要。
2PC
两阶段提交(2PC)故名思义,是有两个步骤组成的.其中涉及到两个角色 TM(事务管理器)和 RM(资源管理器)
准备阶段(Phase 1)
事务协调者向所有参与者发送事务预提交请求,询问是否准备好提交事务。
TM(事务管理器)通知各个RM(资源管理器)准备提交它们的事务分支。如果RM判断自己进行的工作可以被提交,那就对工作内容进行持久化,再给TM肯定答复;要是发生了其他情况,那给TM的都是否定答复。
提交阶段(Phase 2)
如果协调者收到所有参与者的“准备成功”响应,它会向所有参与者发送“提交事务”命令;
TM根据阶段1各个RM prepare的结果,决定是提交还是回滚事务。如果所有的RM都prepare成功,那么TM通知所有的RM进行提交;如果有RM prepare失败的话,则TM通知所有RM回滚自己的事务分支。
以mysql数据库为例,在第一阶段,事务管理器向所有涉及到的数据库服务器发出prepare"准备提交"请求,数据库收到请求后执行数据修改和日志记录等处理,处理完成后只是把事务的状态改成"可以提交",然后把结果返回给事务管理器。
如果第一阶段中所有数据库都prepare成功,那么事务管理器向数据库服务器
发出"确认提交"请求,数据库服务器把事务的"可以提交"状态改为"提交完成"状态,然后返回应答。如果在第一阶段内有任何一个数据库的操作发生了错误,或者事务管理器收不到某个数据库的回应,则认为事务失败,回撤所有数据库的事务。数据库服务器收不到第二阶段的确认提交请求,也会把"可以提交"的事务回撤。
使用场景
金融交易:如银行间的资金转账,确保资金不会凭空产生或消失。
库存管理系统:在电子商务中,确保商品的库存数量准确无误。
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;// 简化版的参与者
interface Participant {boolean prepare();void commit();void rollback();
}// 简化的协调者
class Coordinator {private List<Participant> participants;private ExecutorService executor;public Coordinator(List<Participant> participants) {this.participants = participants;this.executor = Executors.newFixedThreadPool(participants.size());}public void startTransaction() {try {List<Future<Boolean>> prepareResults = preparePhase();if (allTrue(prepareResults)) {commitPhase();} else {rollbackPhase();}} finally {executor.shutdown();}}private List<Future<Boolean>> preparePhase() {return executor.invokeAll(participants, preparingTask());}private void commitPhase() {participants.forEach(Participant::commit);}private void rollbackPhase() {participants.forEach(Participant::rollback);}private boolean allTrue(List<Future<Boolean>> results) throws InterruptedException {for (Future<Boolean> result : results) {if (!result.get()) return false;}return true;}private Runnable preparingTask() {return () -> {// 模拟参与者的行为,实际中参与者会在此执行本地事务并返回结果return true; // 返回true表示准备成功,false表示失败};}
}// 示例参与者
class MockParticipant implements Participant {@Overridepublic boolean prepare() {// 实现本地事务的预提交检查逻辑return true; // 假设总是成功}@Overridepublic void commit() {// 执行事务的提交操作System.out.println("Committing transaction...");}@Overridepublic void rollback() {// 执行事务的回滚操作System.out.println("Rolling back transaction...");}
}public class TwoPhaseCommitDemo {public static void main(String[] args) {List<Participant> participants = List.of(new MockParticipant(), new MockParticipant());Coordinator coordinator = new Coordinator(participants);coordinator.startTransaction();}
}
2PC存在的问题
二阶段提交看起来确实能够提供原子性的操作,但是不幸的是,二阶段提交还是有几个缺点的:
- 同步阻塞问题
2PC 中的参与者是阻塞的。在第一阶段收到请求后就会预先锁定资源,一直到 commit 后才会释放。
- 单点故障
由于协调者的重要性,一旦协调者TM发生故障,参与者RM会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。
- 数据不一致
若协调者第二阶段发送提交请求时崩溃,可能部分参与者收到commit请求提交了事务,而另一部
分参与者未收到commit请求而放弃事务,从而造成数据不一致的问题。
3PC
三阶段提交(3PC, Three-Phase Commit)模型是对两阶段提交(2PC)的一个改进,旨在减少阻塞范围并提高系统的可用性,同时仍然追求强一致性。3PC通过引入预提交阶段来减少参与者在不确定状态下的时间,从而降低了事务长时间阻塞的风险。
CanCommit阶段
3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返
回Yes响应,否则返回No响应。
- 事务询问 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响
应。 - 响应反馈 参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No
PreCommit阶段
协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作。根据响应情况,有以下两种可能。
假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。
- 发送预提交请求 协调者向参与者发送PreCommit请求,并进入Prepared阶段。
- 事务预提交 参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
- 响应反馈 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。
假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。
- 发送中断请求 协调者向所有参与者发送abort请求。
- 中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。
doCommit阶段
该阶段进行真正的事务提交,也可以分为以下两种情况
场景1:执行提交
- 发送提交请求 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
- 事务提交 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
- 响应反馈 事务提交完之后,向协调者发送Ack响应。
- 完成事务 协调者接收到所有参与者的ack响应之后,完成事务。
场景 2:中断事务
协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响
应超时),那么就会执行中断事务。
- 发送中断请求 协调者向所有参与者发送abort请求
- 事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成
回滚之后释放所有的事务资源。 - 反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息
- 中断事务 协调者接收到参与者反馈的ACK消息之后,执行事务的中断。
看到这里其实我们能看出来3PC是2PC的升级版本。三阶段提交相较于两阶段提交主要有两个改动点:
- 引入超时机制。同时在协调者和参与者中都引入超时机制。
2.在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
使用场景
- 金融交易:确保资金在多个账户间转移过程中的精确和不可逆。
- 分布式数据库同步:在多副本数据库系统中,确保数据更改在所有节点上一致。
- 关键业务流程:如保险索赔处理,需要高度一致性的步骤执行。
class Coordinator {List<Participant> participants;void start3PCTransaction() {// 第一阶段:Prepareif (allParticipantsReadyToPrepare()) {// 第二阶段:PreCommitif (allParticipantsReadyToPreCommit()) {// 第三阶段:CommitcommitTransaction();} else {abortTransaction();}} else {abortTransaction();}}private boolean allParticipantsReadyToPrepare() {// 实现逻辑:询问所有参与者是否准备好return true; // 简化处理}private boolean allParticipantsReadyToPreCommit() {// 实现逻辑:询问所有参与者是否准备好预提交return true; // 简化处理}private void commitTransaction() {// 实现逻辑:通知所有参与者提交事务}private void abortTransaction() {// 实现逻辑:通知所有参与者事务失败,需要回滚}
}interface Participant {boolean canPrepare();boolean canPreCommit();void commit();void rollback();
}// 示例参与者
class MockParticipant implements Participant {@Overridepublic boolean canPrepare() { return true; }@Overridepublic boolean canPreCommit() { return true; }@Overridepublic void commit() { System.out.println("Committing..."); }@Overridepublic void rollback() { System.out.println("Rolling Back..."); }
}
结语
总结而言,分布式事务模型在构建强一致性分布式系统中扮演着不可或缺的角色,是确保数据一致性和事务完整性的基石。面对分布式环境的固有挑战,诸如两阶段提交(2PC)、三阶段提交(3PC),为开发者提供了实现强一致性的多样化手段。这些模型通过精心设计的协议,在数据一致性、系统可用性与性能之间寻找微妙的平衡点。
相关文章:

强一致性的皇冠:分布式事务模型的至高法则揭秘
关注微信公众号 “程序员小胖” 每日技术干货,第一时间送达! 引言 分布式事务模型是分布式系统设计的核心,关键在于保证数据一致性和事务完整性,尤其强调强一致性。诸如2PC、3PC、Saga、TCC等模型与协议,应运而生以解…...

mac/windows下安装docker,minikube
1、安装docker Get Started | Docker 下载安装docker 就行 启动后,就可以正常操作docker了 使用docker -v 验证是否成功就行 2、安装minikube,是基于docker-desktop的 2.1、点击设置 2.2、选中安装,这个可能需要一点时间 这样安装后&…...
【爬虫】fake_useragent的使用、BeautifulSoup(find()和find_all())
1 fake_useragent 2 BeautifulSoup 3 Beautiful Soup库的find()和find_all() 1 fake_useragent fake_useragent是一个Python库,用于生成随机的用户代理字符串。 用户代理是在HTTP请求中发送给服务器的一种标识,它告诉服务器发送请求的客户端的类型、版本…...

ComfyUI中图像亮度/对比度/饱和度处理
用上面这个节点可以同时设置图片的亮度、对比度和饱和度。 【保姆级教程】一口气分享在ComfyUI中常用的30多种基本图像处理方式 更多好玩且实用AIGC工作流和节点 星球号:32767063 本期资料链接 往期学习资料 整理AI学习资料库...

基于FPGA的DDS波形发生器VHDL代码Quartus仿真
名称:基于FPGA的DDS波形发生器VHDL代码Quartus仿真(文末获取) 软件:Quartus 语言:VHDL 代码功能: DDS波形发生器VHDL 1、可以输出正弦波、方波、三角波 2、可以控制输出波形的频率 DDS波形发生器原理…...
C++语法|可调用对象与function类型
文章目录 引入function的使用function类型的典型应用function类型的原理实现代码优化可变参的函数对象 引入 还记得C语言中的函数指针数组吗? 我们通过函数指针数组实现一个,图书管理系统的界面: #include <stdio.h> void doShowAllB…...

Linux学习之路 -- 文件 -- 文件描述符
前面介绍了与文件相关的各种操作,其中的各个接口都离不开一个整数,那就是文件描述符,本文将介绍文件描述符的一些相关知识。 目录 <1>现象 <2>原理 文件fd的分配规则和利用规则实现重定向 <1>现象 我们可以先通过prin…...
JDK动态代理和Cglib动态代理区别
1.如果目标类实现了接口,将会使用JDK动态代理,否则会使用Cglib动态代理; 2.JDK代理使用自己的字节码生成工具生成代理对象,而Cglib会使用ASM字节码生成工具去生成; 3.JDK动态代理是通过反射的方式去实现代理对象的所有方法,通过…...

牛客 | 字符金字塔
请打印输出一个字符金字塔,字符金字塔的特征请参考样例 #include <stdio.h> #include <string.h> using namespace std; int main() {char c;scanf("%c", &c);for (int i 1; i < (c - 64); i)//第一个循环决定了有多少行{//c:67 第三…...

【计算机科学速成课】笔记三——操作系统
文章目录 18.操作系统问题引出——批处理设备驱动程序多任务处理虚拟内存内存保护Unix 18.操作系统 问题引出—— Computers in the 1940s and early 50s ran one program at a time. 1940,1950 年代的电脑,每次只能运行一个程序 A programmer would write one at…...

用js代码实现贪吃蛇小游戏
js已经学了大部分了,现在就利用我所学的js知识试试做贪吃蛇小游戏吧 以下部分相关图片以及思路笔记均出自渡一陈老师的视频 首先制作简单的静态页面,添加贪吃蛇移动的背景和相关图片,比如开始游戏等等 将各个功能均封装在函数中࿰…...

微信小程序+esp8266温湿度读取
本文主要使用微信小程序显示ESP8266读取的温湿度并通过微信小程序控制LED灯。小程序界面如下图所示 原理讲解 esp8266 通过mqtt发布消息,微信小程序通过mqtt 订阅消息,小程序订阅后,就可以实时收到esp8266 传输来的消息。 个人可免费注册五个微信小程序账号,在微信小程序官…...

软考中级-软件设计师(十)网络与信息安全基础知识
一、网络概述 1.1计算机网络的概念 计算机网络的发展:具有通信功能的单机系统->具有通信功能的多机系统->以共享资源为目的的计算机网络->以局域网及因特网为支撑环境的分布式计算机系统 计算机网络的功能:数据通信、资源共享、负载均衡、高…...

推荐一个好用的命令行工具ShellGPT
ShellGPT 配置安装常用功能聊天写命令并执行 高级功能函数调用角色管理 总结 这两天突然想到,现有的很多工具都在被大模型重构,比如诞生了像perplexity.ai 这种新交互形式的搜索引擎,就连wps也推出了AI服务,甚至都可以直接生成ppt…...

Prompt提示词教程 | 提示工程指南 | 提示词示例 入门篇
在上一节中,我们介绍并给出了如何赋能大语言模型的基本示例。如果还没看而且是刚入门的同学建议看下,有个基本概念。 Prompt提示词教程 | 提示工程指南 | 提示工程简介https://blog.csdn.net/HRG520JN/article/details/138523705在本节中,我…...

uniapp + uView动态表单校验
项目需求:动态循环表单,并实现动态表单校验 页面: <u--form label-position"top" :model"tmForm" ref"tmForm" label-width"0px" :rulesrules><div v-for"(element, index) in tmForm…...

【Linux】HTTPS
欢迎来到Cefler的博客😁 🕌博客主页:折纸花满衣 🏠个人专栏:Linux 目录 👉🏻HTTPS协议概念👉🏻加密为什么要进行加密 👉🏻常见的加密方式对称加密…...

语音识别--使用YAMNet识别环境音
⚠申明: 未经许可,禁止以任何形式转载,若要引用,请标注链接地址。 全文共计3077字,阅读大概需要3分钟 🌈更多学习内容, 欢迎👏关注👀【文末】我的个人微信公众号…...
前端JS必用工具【js-tool-big-box】,邮箱,手机,身份证号,ip地址等正则验证方法学习
这一小节,我们针对前端npm包 js-tool-big-box 的使用做一些讲解,主要是针对项目中,邮箱,手机号,身份证号,ip地址,url格式,邮政编码等验证的方法使用。 目录 1 安装和引入 2 邮箱验…...

notepad++安装 hex-editor插件
打开notepad 点击插件 搜索 hex-editor,点击右侧 安装install 安装成功后,在已安装插件中就有显示了...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...

【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...