Distributed Transactions Mit 6.824
Topic1:distributed transactions = concurrency control + atomic commit
传统计划:事务
程序员标记代码序列的开始/结束作为事务。
事务示例
x和y是银行余额——数据库表中的记录。x和y位于不同的服务器上(可能在不同的银行)。x和y开始时都是 $10。T1和T2是事务。T1: 从x向y转账 $1。T2: 审计,检查没有钱丢失。
事务操作
T1: T2:
begin_xaction begin_xactionadd(x, 1) tmp1 = get(x)add(y, -1) tmp2 = get(y)
end_xaction print tmp1, tmp2end_xaction
事务的正确行为是什么?
通常被称为“ACID”:
- 原子性(Atomic) - 尽管有失败,但要么全部写入成功,要么一个也不写。
- 一致性(Consistent) - 遵守应用程序特定的不变量。
- 隔离性(Isolated) - 事务之间没有干扰——可串行化。
- 持久性(Durable) - 提交的写操作是永久的。
什么是可串行化?
当您执行一些并发事务并产生结果时,“结果”既包括输出也包括数据库中的变化。如果满足以下条件,则这些结果是可串行化的:
- 存在一种串行执行事务的顺序,
- 这种顺序产生的结果与实际执行的结果相同。
(串行意味着一次一个——没有并行执行。)
(这个定义应该让你想起线性一致性。)
您可以通过寻找产生相同结果的顺序来测试执行结果是否是可串行化的。对于我们的例子,可能的串行顺序是:
- T1; T2
- T2; T1
因此,正确的(可串行化的)结果是:
- T1; T2:x=11 y=9 “11,9”
- T2; T1:x=11 y=9 “10,10”
两者的结果不同;任何一种都可以接受。没有其他结果是可以接受的。实现可能已经并行执行了T1和T2,但它必须仍然产生如同按照串行顺序执行一样的结果。
为什么可串行化很受欢迎
- 对程序员来说是一个简单的模型
- 程序员可以编写复杂的事务,同时忽略并发性。
- 它允许对不同记录的事务进行并行执行。
事务可以在出现问题时“中止”
- 中止会撤销任何记录修改。
- 事务可能会自愿中止,
- 例如,如果账户不存在,或者
y的余额小于等于0。
- 例如,如果账户不存在,或者
- 系统可能会强制中止,例如,为了打破锁定死锁。
- 一些服务器故障会导致中止。
- 应用程序可能会(也可能不会)再次尝试事务
分布式事务包含两个主要组成部分:
- 并发控制(提供隔离性/可串行化)
- 原子提交(尽管有失败,也能提供原子性)
首先,让我们谈谈并发控制:
- 正确执行并发事务
事务的并发控制分为两类:
- 悲观并发控制:
- 在使用记录前对其加锁
- 冲突会导致延迟(等待锁)
- 乐观并发控制:
- 不加锁地使用记录
- 提交时检查读/写操作是否可串行化
- 冲突会导致中止+重试
- 称为乐观并发控制(OCC)
- 如果冲突频繁,悲观并发控制更快
- 如果冲突罕见,乐观并发控制更快
今天的话题是悲观并发控制,下周将讨论乐观并发控制(FaRM)。
“两阶段锁定”是实现可串行化的一种方式:
- 两阶段锁(2PL)定义:
- 事务在使用记录前必须获取记录的锁
- 事务必须持有其锁,直到提交或中止之后
我们例子中的两阶段锁定
假设T1和T2同时开始:
- 事务系统会根据需要自动获取锁
- 因此,T1/T2中首先使用x的将获取锁
- 另一个事务将等待,直到第一个事务完全结束
- 这阻止了非可串行化的交错执行
详细信息:
- 每个数据库记录都有一个锁
- 如果是分布式的,锁通常存储在记录的服务器上
- [图表:客户端、服务器、记录、锁]
- (但两阶段锁定不太受分布式影响)
- 执行事务需要时获取锁,首次使用时
add()和get()隐式获取记录的锁end_xaction()释放所有锁
- 所有锁都是排他性的(在此讨论中,没有读/写锁)
- 全名是“强严格两阶段锁定”
- 与线程锁定相关(例如,Go的Mutex),但更简单:
- 显式的
begin/end_xaction - 数据库在每条记录首次使用时自动加锁
- 数据库在事务结束时自动解锁
- 数据库可能会自动中止以解决死锁
- 显式的
保持锁直到提交(commit)或中止(abort)之后的原因是为了确保事务的整体原子性和一致性。如果在完成记录使用后立即释放锁,可能会导致一些问题,例如:
- 如果T2在
get(x)后立即释放x的锁,T1就可以在T2的两次get()操作之间执行。结果,T2可能会打印10,9,这并不是一个可串行化的执行:既不是T1;T2也不是T2;T1的顺序。 - 如果T1写入x然后立即释放x的锁,T2可以读取x并打印结果。但如果T1之后中止,那么T2使用了一个实际上从未存在过的值。理论上,我们应该中止T2,这将是一个“级联中止”;这样做会非常尴尬。
两阶段锁定(2PL)可能会导致死锁,例如:
T1 T2
get(x) get(y)
get(y) get(x)
系统必须检测(循环?锁超时?)并中止一个事务。
两阶段锁定(2PL)是否会禁止一个正确的(可串行化的)执行?
是的,例如:
T1 T2
get(x) get(x)put(x,2)
put(x,1)
锁定会禁止这种交错执行,但结果(x=1)是可串行化的(与T2;T1相同)。
问题:描述一个情况,其中两阶段锁定比简单锁定产生更高的性能。
简单锁定:在任何使用之前锁定每个记录;在中止/提交后释放。
两阶段锁定(2PL)比简单锁定(Simple Locking)表现出更高性能的一个情况是:
- 当事务访问的数据集合较小并且与其他事务的重叠较少时。在这种情况下,2PL通过只在需要时锁定记录,允许更多的并行执行。事务可以更快地完成其操作并释放资源,减少了等待和阻塞的时间,从而提高了整体系统性能。
- 相反,简单锁定策略要求事务在开始执行任何操作之前锁定其将访问的所有记录,这会导致不必要的等待,特别是在事务只需要读取一小部分其锁定记录的情况下。这种策略在高并发环境下性能较差,因为它限制了并行操作的可能性,增加了事务完成时间。
Next topic: distributed transactions versus failures
我们将开发一个称为“两阶段提交”的协议,它被分布式数据库用于多服务器事务
我们想要的是“原子提交”:
- 一群计算机在某项任务上合作
- 每台计算机扮演不同的角色
- 想要确保原子性:全部执行,或者全部不执行
- 挑战:故障,性能
设置
- 数据在多个服务器之间分片
- 事务在“事务协调器”(TC)上运行
- 对于每个读/写,TC向相关的分片服务器发送RPC
- 每个都是一个“参与者”
- 每个参与者管理其数据分片的锁
- 可能有许多并发事务,许多TC
- TC为每个事务分配唯一的事务ID(TID)
- 每条消息,每个表条目都用TID标记
- 为了避免混淆
无故障时的两阶段提交:
- TC向A,B发送put(),get()等RPC
- 修改是暂定的,只有在提交时才会安装。
- TC进行到事务的末尾。
- TC向A和B发送准备(PREPARE)消息。
- 如果A愿意提交,
- A回应YES。
- 然后A处于“准备好的”状态。
- 否则,A回应NO。
- B也是同样。
- 如果A和B都说YES,TC向A和B发送提交(COMMIT)消息。
- 如果A或B任何一个说NO,TC发送中止(ABORT)消息。
- 如果A/B收到TC的提交消息,他们将提交。
- 即,他们将暂定记录写入真实数据库。
- 并释放其记录上的事务锁。
- A/B确认提交消息。
这个协议试图确保即使在分布式系统中发生故障时,事务也能保持原子性,确保所有参与的服务器要么都提交事务,要么都不提交。

这为什么到目前为止是正确的?
- 除非A和B都同意,否则它们都不能提交。
如果B崩溃并重启会怎样?
- 如果B在崩溃前发送了YES,B必须记住(尽管发生了崩溃)!
- 因为A可能已经收到了COMMIT消息并提交了。
- 所以B必须能够在重启后提交(或不提交)。
因此,参与者必须写入持久的(磁盘上的)状态:
- B在说YES前必须在磁盘上记住,包括修改过的数据。
- 如果B重启,磁盘显示YES但没有COMMIT,
- B必须询问TC,或等待TC重新发送。
- 与此同时,B必须继续持有事务的锁。
- 如果TC说COMMIT,B将修改的数据复制到真实数据。
如果TC崩溃并重启会怎样?
- 如果TC可能在崩溃前发送了COMMIT,TC必须记住!
- 因为一个工作者可能已经提交了。
- 因此,TC必须在发送COMMIT消息前将COMMIT写入磁盘。
- 并且如果它崩溃并重启后重复COMMIT,
- 或者如果参与者询问(即,如果A/B没有收到COMMIT消息)。
- 参与者必须过滤掉重复的COMMIT(使用TID)。
如果TC从B那里永远得不到YES/NO怎么办?
- 也许B崩溃了并没有恢复;也许网络出现了故障。
- TC可以超时,并中止(因为没有发送任何COMMIT消息)。
- 好处是:允许服务器释放锁。
如果B在等待TC的PREPARE时超时或崩溃怎么办?
- B还没有对PREPARE做出回应,所以TC不能决定提交
- 所以B可以单方面中止,并释放锁
- 对未来的PREPARE响应NO
如果B回答了PREPARE的YES,但没有收到COMMIT或ABORT怎么办?
- B可以单方面决定中止吗?
- 不可以!TC可能已经从两者那里得到了YES,
- 并且向A发送了COMMIT,但在发送给B之前崩溃了。
- 那么A将提交而B将中止:这是错误的。
- B也不能单方面提交:
- A可能已经投了NO。
所以:如果B投了YES,它必须“阻塞”:等待TC的决定。
注意:
- 提交/中止决定由单一实体——TC做出。
- 这使得两阶段提交相对直接。
- 惩罚是A/B在投了YES之后必须等待TC。
TC何时可以完全忘记一个已提交的事务?
- 如果它从每个参与者那里收到了对COMMIT的确认。
- 然后没有参与者会再次需要询问。
参与者何时可以完全忘记一个已提交的事务?
- 在它确认了TC的COMMIT消息之后。
- 如果它再次收到COMMIT,并且没有该事务的记录,
- 它必须已经提交并忘记了,并且可以再次确认。
两阶段提交的视角
- 在事务使用多个分片上的数据时,在分片数据库中使用
- 但它有不好的声誉:
- 慢:多轮消息传递
- 慢:磁盘写入
- 在准备/提交交换期间持有锁;阻塞其他事务
- TC崩溃可能导致无限期阻塞,同时持有锁
- 因此通常只在单一小领域中使用
- 例如,不在银行之间,不在航空公司之间,不在广域网上
Raft和两阶段提交解决的是不同的问题!
- 使用Raft通过复制来获得高可用性
- 即,当一些服务器崩溃时仍能操作
- 服务器都做相同的事情
- 使用2PC是当每个参与者做不同的事情时
- 并且所有参与者都必须做他们的部分
- 2PC不提高可用性
- 因为所有服务器必须运行才能完成任何事情
- Raft不确保所有服务器都做某事
- 因为只需要大多数活着就可以了
如果你想要高可用性和原子提交怎么办?
- 这里有一个计划。
- TC和服务器应该每个都用Raft复制
- 在复制的服务之间运行两阶段提交
- 然后你可以在容忍故障的同时仍然取得进展
- 你将在实验4中构建类似的东西来转移分片
- 下次会议的Spanner使用了这种安排
FAT
-
为什么事务的原子性如此重要?
所谓“事务”,意味着事务内的步骤相对于故障和其他事务而言是原子性发生的。这里的原子性意味着“全部或无”。事务是某些存储系统提供的一项功能,旨在简化编程工作。事务有用的一个例子是银行转账。如果银行想要从爱丽丝的账户转移100美元到鲍勃的账户,如果在此过程中途发生崩溃,导致爱丽丝的账户被扣除了100美元但鲍勃的账户未增加100美元,这将非常尴尬。因此,如果您的存储系统支持事务,程序员可以编写如下内容:
BEGIN TRANSACTIONdecrease Alice's balance by 100;increase Bob's balance by 100; END TRANSACTION事务系统将确保事务是原子的;即使在某处发生故障,也要么两者都发生,要么都不发生。
-
两阶段锁定是否会产生死锁?
是的。如果两个事务都使用记录R1和R2,但以相反的顺序,它们将各自获得其中一个锁,然后在尝试获取另一个锁时发生死锁。数据库能够检测到这些死锁并解决它们。数据库可以通过锁获取的超时,或者在事务之间的等待图中寻找循环来检测死锁。可以通过中止参与的其中一个事务来打破死锁。
-
可以同时有多个活跃的事务吗?参与者如何知道一条消息是指哪个事务?
可以有许多并发事务,由许多事务协调器(TC)管理。TC为每个事务分配一个唯一的事务ID(TID)。每条消息都包括相关事务的TID。TC和参与者用TID标记他们表中的条目,这样(例如)当一个COMMIT消息到达一个参与者时,它知道哪些暂定记录要变成永久的,以及要释放哪些锁。
-
如果必须中止一个事务,两阶段提交系统如何撤销修改?
每个参与者对记录的临时副本进行修改。如果参与者对TC的准备消息回答“是”,参与者必须首先将临时记录值保存到其磁盘上的日志中,这样如果它崩溃并重启,它可以找到它们。如果TC决定提交,参与者必须将临时值复制到真实数据库记录中;如果TC决定中止,参与者必须丢弃临时记录。
-
可串行化与线性一致性有什么关系?
它们是来自不同社区的类似概念。两者都要求最终结果与某些串行执行相同。可串行化通常指的是涉及多个操作的整个事务。线性一致性通常指的是简单的读和写操作。还有一点是,线性一致性要求等效的串行执行与实际执行的实时顺序相匹配,而可串行化通常不要求这一点。
-
为什么在我们查看的设计中经常出现日志?
- 一个原因是日志捕获了系统为事务选择的串行顺序,这样例如所有副本以相同的顺序执行事务,或者服务器在崩溃+重启后按照与崩溃前相同的顺序考虑事务。
- 另一个原因是日志是一种高效的方式将数据写入硬盘或SSD,因为这两种媒介在顺序写入(即追加到日志)时要比随机写入快得多。、第三个原因是日志是一种方便的方式,让崩溃恢复软件看到系统在崩溃前进行到哪一步,以及最后的事务是否在日志中有完整记录,因此可以安全地重放。也就是说,日志是实现可崩溃恢复的原子事务的一种方便方式,通过预写日志(write-ahead logging)。
相关文章:
Distributed Transactions Mit 6.824
Topic1:distributed transactions concurrency control atomic commit 传统计划:事务 程序员标记代码序列的开始/结束作为事务。 事务示例 x 和 y 是银行余额——数据库表中的记录。x 和 y 位于不同的服务器上(可能在不同的银行&#x…...
Redis可视化工具:Another Redis Desktop Manager下载安装使用
1.Github下载 github下载地址: Releases qishibo/AnotherRedisDesktopManager GitHub 2. 安装 直接双击exe文件进行安装 3. 连接Redis服务 先启动Redis服务,具体启动过程可参考: Windows安装并启动Redis服务端(zip包)…...
Parquet文件格式详解(含行、列式存储区别)
Parquet文件格式详解 Parquet 是一种列式存储格式,旨在高效地存储和处理大规模数据集。它被设计用于在大数据生态系统中进行数据存储和分析,如 Apache Hadoop 和 Apache Spark。 行式存储 vs 列式存储 在了解 Parquet 文件格式之前,先来对…...
一文了解https为什么是安全的
目录 前言一、https和http二、http为什么不安全?2.1 http的工作原理2.2 http的明文传输 三、https3.1 加密3.2 身份验证 四、总结 前言 目前绝大多数网站都已经切换到了https,切换的原因很简单,因为它更安全,https未来会完全取代…...
[‘column‘]和[:,‘column‘]的区别
之前,关于numpy和pandas的操作一直不熟悉,对于获取数据中的行,列一直混淆。 df[column] df[column]是 Pandas DataFrame 切片的常用语法,用于选择名为 column 的单个列。它返回一个 Pandas Series 对象。 df.loc[:,column] df[:,…...
icloud如何高效利用
iCloud是Apple提供的一项云存储和云计算服务,能够帮助用户在不同的Apple设备之间同步和共享数据。要高效利用iCloud,可以参考以下几个方面: 自动备份:确保所有重要的Apple设备都开启了iCloud备份功能,这样可以自动将设…...
k8s二进制安装与部署
目录 一、实验目的 二、实验环境 三、实验步骤 3.1 操作系统初始化配置 3.2 部署 docker引擎 3.3 部署 etcd 集群 3.3.1 在 master01 节点上操作 3.3.2 在 node01 节点上操作 3.3.3 在 node02 节点上操作 3.4 部署 Master 组件 3.4.1 在 mast…...
驱动编译报error: negative width in bit-field ‘<anonymous>’错误
错误如下图所示: 代码如下: 问题点:module_param的其他用户的权限参数上。 在Linux中,文件权限由读(r)、写(w)、执行(x)权限组成,分别对应数值4、2、1。 第一位0是占位符,在这里没有意义,因为…...
Go语言的命名规范是怎样的?
文章目录 Go语言的命名规范详解一、标识符命名规范示例代码 二、包名命名规范示例代码 三、变量命名规范示例代码 四、常量命名规范示例代码 五、函数命名规范示例代码 总结 Go语言的命名规范详解 在Go语言中,代码的命名规范对于项目的可读性、可维护性和可扩展性至…...
Vue3骨架屏(Skeleton)
效果如下图:在线预览 APIs 参数说明类型默认值必传animated是否展示动画效果booleantruefalsebutton是否使用按钮占位图boolean | SkeletonButtonPropsfalsefalseavatar是否显示头像占位图boolean | SkeletonAvatarPropsfalsefalseinput是否使用输入框占位图boolea…...
【文末附gpt升级方案】亚马逊与Hugging Face合作:定制芯片低成本运行AI模型的创新探索
亚马逊与Hugging Face合作:定制芯片低成本运行AI模型的创新探索 摘要 本文探讨了亚马逊云部门与人工智能初创公司Hugging Face的合作,旨在通过定制计算芯片Inferentia2在亚马逊网络服务(AWS)上更低成本地运行数千个AI模型。文章首…...
二叉树的链式实现
目录 一、二叉树的基础操作 二、二叉树代码图解 2.1 遍历 2.2 求大小 2.3 创建与销毁 2.4 与队列结合解决问题 三、二叉树C语言源码汇总 二叉树的代码实现运用了函数递归的思想,了解函数递归的知识请见博主的另一篇博客: http://t.csdnimg.cn/Po…...
STM32中断编程入门
文章目录 一、 理论部分1.中断系统2.中断执行流程3.NVIC的基本结构4.EXTI介绍5.AFIO复用IO口 二、实验目的:学习stm32中断原理和开发编程方法。使用标准完成以下任务:(一)实验一 开关控制LED的亮灭1.代码部分2.运行结果 ÿ…...
《我的阿勒泰》读后感
暂没时间写,记录在此,防止忘记,后面补上!!! 【经典语录】 01、如果天气好的话,阳光广阔地照耀着世界,暖洋洋又懒洋洋。这样的阳光下,似乎脚下的每一株草都和我一样,也把身子完全舒展开了。 02、…...
Android.mk简单介绍、规则与基本格式
文章目录 Android.mk与makefile区别Android.mk规则Android.mk基本格式 Android.mk与makefile区别 Android.mk 和 Makefile 都是用于构建代码项目的构建脚本文件,但是它们在特定上下文中有一些区别: Android.mk: Android.mk 是用于构建 Android 应用或库…...
【MySQL精通之路】InnoDB(3)-MVCC多版本管理
InnoDB是一个多版本(MVCC)的存储引擎。 它保留有关更改行的旧版本的信息,以支持事务性功能,如并发和回滚。 这些信息存储在称为回滚段的数据结构中的Undo表空间中。 参见“Undo表空间”。 InnoDB使用回滚段(rollback…...
uniapp 对接 微信App/支付宝App 支付
相关文档:uni.requestPayment(OBJECT) | uni-app官网 示例代码: import qs from qsasync aliPay(){const { provider } await uni.getProvider({ service:payment })if(provider.includes(alipay)){uni.request({url:后端接口地址,data:{ //传参 },suc…...
cmake配置opencv与boost库
Cmake配置外部依赖库(以Opencv和Boost为例) Cmake对于外部依赖库,需要知道外部库的头文件路径,库文件路径以及库的名字。比如,对于要使用的Boost库,需要知道头文件的位置,库目录的位置以及库依…...
【Kotlin 一】Kotlin入门知识简介、变量声明、数字类型
1. Kotlin简介 Kotlin旨在解决 Java语言在编码效率和代码质量上存在的问题,并且与Java语言完全兼容。Kotlin通过简化语法、提供更强大的函数以及减少样本代码的编写,使开发者能够更高效地编写代码。Kotlin适用于Android、Web后端开发等多种场景 2.Kotl…...
Java 微信小程序登录(openId方式)
1 需求 在开发微信小程序项目时,登录采用的是openId方式,这是一种用户无感的登录方式,用户点开微信小程序时,去调用后端的登录接口。 核心代码 Slf4j Component public class WeChatUtil {private static final String …...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...
