3、架构-事务处理

   

目录

   

概述

场景事例

本地事务

实现原子性和持久性

实现隔离性 


概述

    事务处理几乎在每一个信息系统中都会涉及,它存在的意义是为 了保证系统中所有的数据都是符合期望的,且相互关联的数据之间不 会产生矛盾,即数据状态的一致性(Consistency)。按照数据库的经 典理论,要达成这个目标,需要三方面共同努力来保障。

  1. 原子性(Atomic):在同一项业务处理过程中,事务保证了对 多个数据的修改,要么同时成功,要么同时被撤销。
  2. 隔离性(Isolation):在不同的业务处理过程中,事务保证了各 业务正在读、写的数据相互独立,不会彼此影响。
  3. 持久性(Durability):事务应当保证所有成功被提交的数据修 改都能够正确地被持久化,不丢失数据。

以上四种属性即事务的“ACID”特性,但笔者对这种说法其实不 太认同,因为这四种特性并不正交,A、I、D是手段,C是目的,前者 是因,后者是果,弄到一块去完全是为了拼凑个单词缩写。

事务的概念虽然最初起源于数据库系统,但今天已经有所延伸, 不再局限于数据库本身了。所有需要保证数据一致性的应用场景,包 括但不限于数据库、事务内存、缓存、消息队列、分布式存储,等 等,都有可能用到事务。后文里笔者会使用“数据源”来泛指所有这 些场景中提供与存储数据的逻辑设备,但是上述场景所说的事务和一 致性含义可能并不完全一致,说明如下。

  • 当一个服务只使用一个数据源时,通过A、I、D来获得一致性 是最经典的做法,也是相对容易的。此时,多个并发事务所读写的数 据能够被数据源感知是否存在冲突,并发事务的读写在时间线上的最 终顺序是由数据源来确定的,这种事务间一致性被称为“内部一致 性”。
  • 当一个服务使用到多个不同的数据源,甚至多个不同服务同时 涉及多个不同的数据源时,问题就变得困难了许多。此时,并发执行 甚至是先后执行的多个事务,在时间线上的顺序并不由任何一个数据 源来决定,这种涉及多个数据源的事务间一致性被称为“外部一致 性”。

        外部一致性问题通常很难使用A、I、D来解决,因为这样需要付出 很大甚至不切实际的代价;但是外部一致性又是分布式系统中必然会 遇到且必须要解决的问题,为此我们要转变观念,将一致性从“是或 否”的二元属性转变为可以按不同强度分开讨论的多元属性,在确保 代价可承受的前提下获得强度尽可能高的一致性保障,也正因如此, 事务处理才从一个具体操作上的“编程问题”上升成一个需要全局权 衡的“架构问题”。 人们在探索这些解决方案的过程中,产生了许多新的思路和概 念,有一些概念看上去并不那么直观,在本章,笔者会通过同一个场 景事例讲解如何在不同的事务方案中贯穿、理顺这些概念。

场景事例

        Fenix’s Bookstore是一个在线书店。当一本书被成功售出时,需 要确保以下三件事情被正确地处理:

  1. 用户的账号扣减相应的商品款项;
  2. 商品仓库中扣减库存,将商品标识为待配送状态;
  3. 商家的账号增加相应的商品款项。

         接下来,笔者将逐一介绍在“单个服务使用单个数据源”“单个 服务使用多个数据源”“多个服务使用单个数据源”以及“多个服务 使用多个数据源”下,可以采用哪些手段来保证数据在以上场景中被 正确地读写。

本地事务

        本地事务(Local Transaction)其实应该翻译成“局部事务”才 好与稍后的“全局事务”相对应,不过现在“本地事务”的译法似乎 已经成为主流,这里也就不去纠结名称了。本地事务是指仅操作单一 事务资源的、不需要全局事务管理器进行协调的事务。在没有介绍什 么是“全局事务管理器”前,很难从概念入手去讲解“本地事务”, 所以这里先暂且将概念放下,等后面再来对比理解。

        本地事务是一种最基础的事务解决方案,只适用于单个服务使用 单个数据源的场景。从应用角度看,它是直接依赖于数据源本身提供 的事务能力来工作的,在程序代码层面,最多只能对事务接口做一层 标准化的包装(如JDBC接口),并不能深入参与到事务的运作过程 中,事务的开启、终止、提交、回滚、嵌套、设置隔离级别,乃至与 应用代码贴近的事务传播方式,全部都要依赖底层数据源的支持才能 工作,这一点与后续介绍的XA、TCC、SAGA等主要靠应用程序代码来实 现的事务有着十分明显的区别。举个例子,假设你的代码调用了JDBC 中的Transaction::rollback()方法,方法的成功执行也并不一定代表 事务就已经被成功回滚,如果数据表采用的引擎是MyISAM,那 rollback()方法便是一项没有意义的空操作。因此,我们要想深入讨 论本地事务,便不得不越过应用代码的层次,去了解一些数据库本身 的事务实现原理,弄明白传统数据库管理系统是如何通过ACID来实现 事务的。

实现原子性和持久性

        原子性和持久性在事务里是密切相关的两个属性:原子性保证了 事务的多个操作要么都生效要么都不生效,不会存在中间状态;持久 性保证了一旦事务生效,就不会再因为任何原因而导致其修改的内容 被撤销或丢失。

        众所周知,数据必须要成功写入磁盘、磁带等持久化存储器后才 能拥有持久性,只存储在内存中的数据,一旦遇到应用程序忽然崩 溃,或者数据库、操作系统一侧崩溃,甚至是机器突然断电宕机等情 况就会丢失,后文我们将这些意外情况都统称为“崩溃”(Crash)。 实现原子性和持久性的最大困难是“写入磁盘”这个操作并不是原子 的,不仅有“写入”与“未写入”状态,还客观存在着“正在写”的 中间状态。由于写入中间状态与崩溃都不可能消除,所以如果不做额 外保障措施的话,将内存中的数据写入磁盘,并不能保证原子性与持 久性。下面通过具体事例来说明。

        按照前面预设的场景事例,从Fenix’s Bookstore购买一本书需 要修改三个数据:在用户账户中减去货款、在商家账户中增加货款、 在商品仓库中标记一本书为配送状态。由于写入存在中间状态,所以 可能出现以下情形。

  1. 未提交事务,写入后崩溃:程序还没修改完三个数据,但数据 库已经将其中一个或两个数据的变动写入磁盘,若此时出现崩溃,一 旦重启之后,数据库必须要有办法得知崩溃前发生过一次不完整的购 物操作,将已经修改过的数据从磁盘中恢复成没有改过的样子,以保 证原子性。
  2. 已提交事务,写入前崩溃:程序已经修改完三个数据,但数据 库还未将全部三个数据的变动都写入磁盘,若此时出现崩溃,一旦重 启之后,数据库必须要有办法得知崩溃前发生过一次完整的购物操 作,将还没来得及写入磁盘的那部分数据重新写入,以保证持久性。

        由于写入中间状态与崩溃都是无法避免的,为了保证原子性和持 久性,就只能在崩溃后采取恢复的补救措施,这种数据恢复操作被称 为“崩溃恢复”(Crash Recovery,也有资料称作Failure Recovery 或Transaction Recovery)。

        为了能够顺利地完成崩溃恢复,在磁盘中写入数据就不能像程序 修改内存中的变量值那样,直接改变某表某行某列的某个值,而是必 须将修改数据这个操作所需的全部信息,包括修改什么数据、数据物 理上位于哪个内存页和磁盘块中、从什么值改成什么值,等等,以日 志的形式——即以仅进行顺序追加的文件写入的形式(这是最高效的 写入方式)先记录到磁盘中。只有在日志记录全部安全落盘,数据库 在日志中看到代表事务成功提交的“提交记录”(Commit Record) 后,才会根据日志上的信息对真正的数据进行修改,修改完成后,再 在日志中加入一条“结束记录”(End Record)表示事务已完成持久 化,这种事务实现方法被称为“提交日志”(Commit Logging)。

        Commit Logging保障数据持久性、原子性的原理并不难理解:首 先,日志一旦成功写入Commit Record,那整个事务就是成功的,即使 真正修改数据时崩溃了,重启后根据已经写入磁盘的日志信息恢复现 场、继续修改数据即可,这保证了持久性;其次,如果日志没有成功 写入Commit Record就发生崩溃,那整个事务就是失败的,系统重启后 会看到一部分没有Commit Record的日志,将这部分日志标记为回滚状 态即可,整个事务就像完全没有发生过一样,这保证了原子性。

        Commit Logging的原理很清晰,也确实有一些数据库就是直接采 用Commit Logging机制来实现事务的,譬如较具代表性的是阿里的 OceanBase。但是,Commit Logging存在一个巨大的先天缺陷:所有对 数据的真实修改都必须发生在事务提交以后,即日志写入了Commit Record之后。在此之前,即使磁盘I/O有足够空闲,即使某个事务修改 的数据量非常庞大,占用了大量的内存缓冲区,无论何种理由,都决 不允许在事务提交之前就修改磁盘上的数据,这一点是Commit Logging成立的前提,却对提升数据库的性能十分不利。为此,ARIES 提出了“提前写入日志”(Write-Ahead Logging)的日志改进方案, 所谓“提前写入”(Write-Ahead),就是允许在事务提交之前写入变 动数据的意思。

        Write-Ahead Logging按照事务提交时点,将何时写入变动数据划 分为FORCE和STEAL两类情况。

  1. FORCE:当事务提交后,要求变动数据必须同时完成写入则称为 FORCE,如果不强制变动数据必须同时完成写入则称为NO-FORCE。 现实中绝大多数数据库采用的都是NO-FORCE策略,因为只要有了日 志,变动数据随时可以持久化,从优化磁盘I/O性能考虑,没有必要强 制数据写入时立即进行。
  2. STEAL:在事务提交前,允许变动数据提前写入则称为STEAL, 不允许则称为NO-STEAL。从优化磁盘I/O性能考虑,允许数据提前写 入,有利于利用空闲I/O资源,也有利于节省数据库缓存区的内存。

        Commit Logging允许NO-FORCE,但不允许STEAL。因为假如事务提 交前就有部分变动数据写入磁盘,那一旦事务要回滚,或者发生了崩 溃,这些提前写入的变动数据就都成了错误。         Write-Ahead Logging允许NO-FORCE,也允许STEAL,它给出的解 决办法是增加了另一种被称为Undo Log的日志类型,当变动数据写入 磁盘前,必须先记录Undo Log,注明修改了哪个位置的数据、从什么 值改成什么值等,以便在事务回滚或者崩溃恢复时根据Undo Log对提 前写入的数据变动进行擦除。Undo Log现在一般被翻译为“回滚日 志”,此前记录的用于崩溃恢复时重演数据变动的日志就相应被命名 为Redo Log,一般翻译为“重做日志”。由于Undo Log的加入, Write-Ahead Logging在崩溃恢复时会经历以下三个阶段。

  1. 分析阶段(Analysis):该阶段从最后一次检查点(Checkpoint, 可理解为在这个点之前所有应该持久化的变动都已安全落盘)开始扫 描日志,找出所有没有End Record的事务,组成待恢复的事务集合,这 个集合至少会包括事务表(Transaction Table)和脏页表(Dirty Page Table)两个组成部分。
  2. 重做阶段(Redo):该阶段依据分析阶段中产生的待恢复的事 务集合来重演历史(Repeat History),具体操作是找出所有包含 Commit Record的日志,将这些日志修改的数据写入磁盘,写入完成后 在日志中增加一条End Record,然后移出待恢复事务集合。
  3. 回滚阶段(Undo):该阶段处理经过分析、重做阶段后剩余的 恢复事务集合,此时剩下的都是需要回滚的事务,它们被称为Loser, 根据Undo Log中的信息,将已经提前写入磁盘的信息重新改写回去, 以达到回滚这些Loser事务的目的。

        重做阶段和回滚阶段的操作都应该设计为幂等的。为了追求高I/O 性能,以上三个阶段无可避免地会涉及非常烦琐的概念和细节(如 Redo Log、Undo Log的具体数据结构等)。数据库按照是否允许FORCE和STEAL可以产生四种组合,从优化磁 盘I/O的角度看,NO-FORCE加STEAL的组合的性能无疑是最高的;从算 法实现与日志的角度看,NO-FORCE加STEAL的组合的复杂度无疑也是最 高的。

实现隔离性 

        隔离性保证了每个 事务各自读、写的数据互相独立,不会彼此影响。只从定义上就能嗅 出隔离性肯定与并发密切相关,因为如果没有并发,所有事务全都是 串行的,那就不需要任何隔离,或者说这样的访问具备了天然的隔离 性。但现实情况是不可能没有并发,那么,要如何在并发下实现串行 的数据访问呢?几乎所有程序员都会回答:加锁同步呀!正确,现代 数据库均提供了以下三种锁。

  1. 写锁(Write Lock,也叫作排他锁,eXclusive Lock,简写为XLock):如果数据有加写锁,就只有持有写锁的事务才能对数据进行 写入操作,数据加持着写锁时,其他事务不能写入数据,也不能施加 读锁。
  2. 读锁(Read Lock,也叫作共享锁,Shared Lock,简写为SLock):多个事务可以对同一个数据添加多个读锁,数据被加上读锁 后就不能再被加上写锁,所以其他事务不能对该数据进行写入,但仍 然可以读取。对于持有读锁的事务,如果该数据只有它自己一个事务 加了读锁,则允许直接将其升级为写锁,然后写入数据。
  3. 范围锁(Range Lock):对于某个范围直接加排他锁,在这个 范围内的数据不能被写入。如下语句是典型的加范围锁的例子:

SELECT * FROM books WHERE price < 100 FOR UPDATE;

        请注意“范围不能被写入”与“一批数据不能被写入”的差别, 即不要把范围锁理解成一组排他锁的集合。加了范围锁后,不仅不能 修改该范围内已有的数据,也不能在该范围内新增或删除任何数据, 后者是一组排他锁的集合无法做到的。 

明天继续...

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

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

相关文章

meshlab: pymeshlab合并多个物体模型并保存(flatten visible layers)

一、关于环境 请参考&#xff1a;pymeshlab遍历文件夹中模型、缩放并导出指定格式-CSDN博客 二、关于代码 本文所给出代码仅为参考&#xff0c;禁止转载和引用&#xff0c;仅供个人学习。 本文所给出的例子是https://download.csdn.net/download/weixin_42605076/89233917中的…

np.linalg.norm()

np.linalg.norm()是NumPy中用于计算向量或矩阵的范数的函数。它可以计算不同类型的范数&#xff0c;包括向量的L1范数、L2范数以及矩阵的Frobenius范数等。 基本用法如下, numpy.linalg.norm(x, ordNone, axisNone, keepdimsFalse) x&#xff1a;输入数组&#xff0c;可以是…

Python | Leetcode Python题解之第90题子集II

题目&#xff1a; 题解&#xff1a; class Solution:def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:if not nums:return list()results list()nums.sort()visited [False] * len(nums)self.dfs(nums, results, list(), visited, 0)return resultsdef df…

elasticsearch使用Ngram实现任意位数手机号搜索

文章目录 Ngram自定义分词案例实战问题拆解 Ngram分词器定义Ngram分词定义Ngram分词示例Ngram分词应用场景 Ngram分词实战 Ngram自定义分词案例 当对keyword类型的字段进行高亮查询时&#xff0c;若值为123asd456&#xff0c;查询sd4&#xff0c;则高亮结果是&#xff1c;em&a…

欧洲风景(地理)

1.尼斯湖 尼斯湖亦译内斯湖&#xff0c;位于英国苏格兰高原北部的大峡谷中&#xff0c;湖长39公里&#xff0c;宽2.4公里。面积并不大&#xff0c;却很深。传说这儿住着一只水怪&#xff0c;因此吸引了大量游客。 2.伦敦塔桥 伦敦塔桥是从英国伦敦泰晤士河口算起的第一座桥(泰…

类图及类的关系

类图&#xff08;Class Diagram&#xff09;是UML&#xff08;Unified Modeling Language&#xff0c;统一建模语言&#xff09;中的一种图&#xff0c;用于描述系统中类的静态结构&#xff0c;包括类的属性、方法以及类之间的关系。 一、类 类&#xff08;Class&#xff09;…

Elasticsearch - HTTP

文章目录 安装基本语法索引创建索引查看索引删除索引 文档创建文档更新文档匹配查询多条件查询聚合查询映射 安装 https://www.elastic.co/downloads/past-releases/elasticsearch-7-17-0 下载完成启动bin/elasticsearch服务&#xff0c;可以在Postman调试各种请求。 基本语法…

数据库系统概论(超详解!!!)第八节 数据库设计

1.数据库设计概述 数据库设计是指对于一个给定的应用环境&#xff0c;构造&#xff08;设计&#xff09;优化的数据库逻辑模式和物理结构&#xff0c;并据此建立数据库及其应用系统&#xff0c;使之能够有效地存储和管理数据&#xff0c;满足各种用户的应用需求&#xff0c;包…

【Qt问题】windeployqt如何提取Qt依赖库

往期回顾 【Qt问题】Qt Creator 如何链接第三方库-CSDN博客 【Qt问题】Qt 如何带参数启动外部进程-CSDN博客 【Qt问题】VS2019 Qt win32项目如何添加x64编译方式-CSDN博客 【Qt问题】windeployqt如何提取Qt依赖库 考虑这个问题主要是&#xff1a;当我们的程序运行好之后&#…

Nginx生产环境最佳实践之配置灰度环境

你好呀&#xff0c;我是赵兴晨&#xff0c;文科程序员。 下面的内容可以说是干货满满建议先收藏再慢慢细品。 今天&#xff0c;我想与大家深入探讨一个我们日常工作中不可或缺的话题——灰度环境。你是否在工作中使用过灰度环境&#xff1f;如果是&#xff0c;你的使用体验如…

数据结构与算法===优先队列

文章目录 前言一、优先队列二、应用场景三、代码实现总结 前言 之前写过很多数据结构与算法相关的了&#xff0c;今天看一个新的数据结构&#xff0c;优先队列。优先队列类似队列&#xff0c;却又优先于队列&#xff0c;是堆实现的。接下来详细看看。 一、优先队列 优先队列一…

STL库简介

一、STL库的概念 STL&#xff1a;是C标准库的重要追组成部分&#xff0c;不仅是一个可以复用的组件库&#xff0c;而且还是一个包含了数据结构和算法的软件框架。 二、STL的版本 原始版本 Alexander Stepanov、 Meng Lee 在惠普实验室完成的原始版本&#xff0c; 是一个开源…

C语言错题本之<结构体>

以下叙述中正确的是________. A)预处理命令行必须位于源文件的开头 B)在源文件的一行上可以有多条预处理命令 C)宏名必须用大写字母表示 D)宏替换不占用程序的运行时间 答案&#xff1a;D 解析&#xff1a; A&#xff1a;在C、C等编程语言中&#xff0c;预处理指令&#xff08;…

【PB案例学习笔记】-02 目录浏览器

写在前面 这是PB案例学习笔记系列文章的第二篇&#xff0c;该系列文章适合具有一定PB基础的读者&#xff0c; 通过一个个由浅入深的编程实战案例学习&#xff0c;提高编程技巧&#xff0c;以保证小伙伴们能应付公司的各种开发需求。 文章中设计到的源码&#xff0c;小凡都上…

RS编码和卷积码总结

RS编码 简要介绍RS编码及其原理 1. RS编码简介 Reed-Solomon编码&#xff08;RS编码&#xff09;是一种强大的纠错码&#xff0c;广泛应用于数据存储和传输中。RS编码由Irving S. Reed和Gustave Solomon于1960年提出&#xff0c;属于BCH码的一种&#xff0c;是基于有限域&am…

杨校老师项目之基于单片机STC89C52的智能环境监测系统【嵌入式】

获取全套资料&#xff1a; 有偿获取&#xff1a;mryang511688 技术&#xff1a;C语言、单片机等 摘要&#xff1a; 此设计可分为三个主要部分。此中的温度和湿度的检测功能&#xff0c;通过操纵单总线型温湿度传感器DHT11以数字形式显示&#xff0c;实现了切确测得温湿度的功能…

五分钟“手撕”时间复杂度与空间复杂度

目录 一、算法效率 什么是算法 如何衡量一个算法的好坏 算法效率 二、时间复杂度 时间复杂度的概念 大O的渐进表示法 推导大O阶方法 常见时间复杂度计算举例 三、空间复杂度 常见时间复杂度计算举例 一、算法效率 什么是算法 算法(Algorithm)&#xff1a;就是定…

蓝桥杯单片机之模块代码《串口发数据》

过往历程 历程1&#xff1a;秒表 历程2&#xff1a;按键显示时钟 历程3&#xff1a;列矩阵按键显示时钟 历程4&#xff1a;行矩阵按键显示时钟 历程5&#xff1a;新DS1302 历程6&#xff1a;小数点精确后两位ds18b20 历程7&#xff1a;35定时器测量频率 历程8&#xff…

队列的讲解

队列的概念 队列:只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头 一端进另一端出 也就是可以做到&#xff0c;先…

[BJDCTF 2020]easy_md5、[HNCTF 2022 Week1]Interesting_include、[GDOUCTF 2023]泄露的伪装

目录 [BJDCTF 2020]easy_md5 ffifdyop [SWPUCTF 2021 新生赛]crypto8 [HNCTF 2022 Week1]Interesting_include php://filter协议 [GDOUCTF 2023]泄露的伪装 [BJDCTF 2020]easy_md5 尝试输入一个1&#xff0c;发现输入的内容会通过get传递但是没有其他回显 观察一下响应…