深入JTS事务引擎:理论与实践相结合,掌握高效事务管理的秘诀

news2024/11/27 12:12:09

事务是可靠应用程序的构建块

如果您阅读过任何有关 J2EE 的介绍性文章或者书籍,那么就会发现,只有一小部分资料是专门针对 Java Transaction Service(JTS)或 Java Transaction API(JTA)的。这并不是因为 JTS 是 J2EE 中不重要的部分或者可选部分 —— 恰恰相反。JTS 受到的关注之所以会比 EJB 技术少,是因为它为应用程序提供的服务非常透明 —— 很多开发人员甚至没有注意到在他们的应用程序中事务在哪里开始和结束。在某种意义上,JTS 的默默无闻恰恰是它的成功:因为它非常有效地隐藏了事务管理的很多细节,因此,我们没有听说过或者谈论过很多关于它的内容。但是,您可能想了解它在幕后都为您执行什么功能。

毫不夸张地说,没有事务就不能编写可靠的分布式应用程序。事务允许采用某种控制方式修改应用程序的持久性状态,以便使应用程序对于各种各样的系统故障(包括系统崩溃、网络故障、电源故障甚至自然灾害)更加健壮。事务是构建容错、高可靠性以及高可用性应用程序所需的基本构建块之一。

事务的动机

假设您正在从一个账户向另一个账户进行转账个账户差额由数据库表中的某一行来表示。如果您想从账户 A 转账到账户 B,则可能执行如下这些 SQL 代码:

SELECT accountBalance INTO aBalance
FROM Accounts WHERE accountId=aId;
IF (aBalance >= transferAmount) THEN
UPDATE Accounts
SET accountBalance = accountBalance - transferAmount
WHERE accountId = aId;
UPDATE Accounts
SET accountBalance = accountBalance + transferAmount
WHERE accountId = bId;
INSERT INTO AccountJournal (accountId, amount)
VALUES (aId, -transferAmount);
INSERT INTO AccountJournal (accountId, amount)
VALUES (bId, transferAmount);
else
FAIL "Insufficient funds in account";
END if

到目前为止,这段代码看起来非常简单易懂。如果手头的资金充足,则从一个账户中减去资金,并添加到另一个账户中。但是,如果出现系统电源故障或者崩溃,又会发生什么情况呢?表示账户 A 和账户 B 的行可能不会存储在同一个磁盘块中,这意味着要完成转账需要进行多个磁盘 IO。如果在已写入第一个磁盘块之后,在写入第二个磁盘块之前,系统发生故障,又会发生什么情况呢?A 账户中的资金已经划走,但是没有出现在账户 B 中(A 和 B 客户都不会愿意),或者资金将出现在 账户 B 中,但是没有记入账户 A 的借出账中(银行不会愿意)。如果账户已正确更新,而账户日记账没有更新,又会发生什么情况呢?那么账户 A 和账户 B 的每月银行结账单将与它们账户的余额不一致。

不仅不可能同时将多个数据块写入磁盘,而且每当进行修改时马上将每个数据块写入磁盘,也对系统性能有不利影响。将磁盘写入延迟到比较适宜的时间可能会大大改善应用程序的吞吐量,但是,需要采用不损害数据完整性的方式执行。

甚至在系统没有发生故障时,上面讨论的代码还有另一种风险 —— 并发性。如果账户 A 中有 100 美元,但是却同时开始向它的两个不同的账户分别转账 100 美元,那么会发生什么情况呢?如果时间上凑巧,并且没有适当的锁定机制,两次转账都可能成功,从而使账户 A 的余额为负值。

这些情况似乎都是非常可能发生的,因此希望企业数据系统能够解决这些问题是理所应当的。我们希望在发生火灾、洪水、电源故障、磁盘以及系统出现故障时,银行都能够保持正确的账户记录。可以通过冗余(冗余的磁盘、计算机以及数据中心)来提供容错,但是事务 使得构建容错的软件应用程序成为可能。事务提供了一个框架,用于在系统或组件发生故障时保持数据一致性和完整性。

什么是事务?

那么到底什么是事务呢?在定义这个术语之前,我们首先定义应用程序状态 的概念。应用程序的状态包含影响应用程序操作的所有内存和磁盘中的数据项目 —— 应用程序 “知道” 的所有内容。应用程序状态可以存储在内存、文件或者数据库中。如果系统发生故障,例如应用程序、网络或者计算机系统崩溃,则我们想确保当重新启动系统时,可以恢复应用程序的状态。

现在,我们将事务 定义为对应用程序状态的相关操作的集合。事务具有原子性一致性隔离性 以及持久性 这几个属性。这些属性统称为 ACID 属性。

原子性 意味着要么所有事务操作都应用于应用程序状态,要么都不应用;事务是不可拆分的工作单元。

一致性 意味着事务代表应用程序状态的正确转换 —— 即事务不能违反应用程序中固有的任何完整性限制。实际上,一致性的概念是特定于应用程序的。例如,在记账应用程序中,一致性可能包括所有资产账户的总和始终等于所有负债账户的总和这个不变式。在本系列的第 3 部分中讨论事务划分时,我们将详细讨论这个需求。

隔离性 意味着一个事务的效果不影响正在同时执行的其他事务。从事务的角度讲,它意味着事务按顺序执行而不是并行执行。在数据库系统中,通常通过使用锁机制来实现隔离性。为了使应用程序获得最佳性能,有时也会对某些事务放松隔离性的要求。

持久性 意味着一旦成功完成某个事务,对应用程序状态所做的更改将 “经得起失败”。

什么是 “经得起失败”呢?它由什么组成?这取决于系统,一个设计良好的系统将明确地标识可以从哪些故障中恢复过来。在我的桌面工作站上运行的事务数据库,对于系统崩溃和电源故障非常稳定健壮,但是对于我的办公大楼发生大火灾却没有任何作用。银行可能不仅仅在数据中心具有冗余的磁盘、网络以及系统,而且还可能在别的城市有冗余的数据中心,该冗余数据中心通过冗余的通信链路连接,目的是允许从严重的故障(如自然灾害)中进行恢复。军用的数据系统甚至可能有更严格的容错要求。

事务的剖析

典型事务有几个参与者 —— 应用程序、事务监视器(TPM)以及一个或多个资源管理器(RM)。资源管理器存储应用程序状态,常常是数据库,但也可能是消息队列服务器(在 J2EE 应用程序中,它们将是 JMS 提供者)或其他事务性资源。TPM 协调 RM 的活动,以确保事务 “要么全有要么全无” 属性。

当应用程序请求容器或事务监视器启动新的事务时,事务开始。由于应用程序访问各种各样的 RM,因此,在事务中对它们进行征用。RM 必须使对应用程序状态所做的任何更改与请求更改的事务相关联。

当发生以下事件之一或者两个事件都发生时,事务结束:事务应用程序提交 该事务;通过应用程序或者由于其中一个 RM 失败,回滚 该事务。如果事务成功提交,则将写入与该事务相关联的更改,以使更改持久化并使其对于新的事务可见。如果事务被回滚,则该事务所做的所有更改都将被丢弃;就好像该事务从来没有发生过一样。

事务日志 —— 持久性的关键

事务 RM 通过在一个事务日志中记录多个事务的结果,获得持久性以及可接受的性能。事务日志存储为连续的磁盘文件(有时存储在原始分区中),并且一般只是用于写入而不用于读取,回滚或恢复的情况例外。在我们的银行账户示例中,与账户 A 和账户 B 相关联的余额将在内存中进行更新,新的余额和旧的余额将被写入到事务日志中。编写事务日志的更新记录不需要将全部数据都写入磁盘(只需要写入已更改的数据,而不需要写入全部磁盘块),而且所需的磁盘寻道时间也会更少(原因是所有更改都包含在日志中连续的磁盘块中)。此外,与多个并发事务关联的更改可以合并到一起,一次写入事务日志,这意味着每次磁盘写入时我们可以处理多个事务,而不需要 每个事务进行几次磁盘写入。之后,RM 将根据所更改的数据更新实际的磁盘块。

重新启动时进行恢复

如果系统出现故障,重新启动时要做的第一件事就是重新应用所有已提交事务的作用,所有这些已提交的事务都 位于日志中,但是它们的数据块尚未更新。采用这种方式,日志保证了故障之间的持久性,而且还能够减少所执行的磁盘 IO 操作的数量,或者至少使它们延迟到对系统性能影响更小的时间。

两阶段提交

很多事务只涉及一个 RM —— 通常是数据库。在这种情况下,RM 通常执行提交或回滚事务所需的大部分工作。(几乎所有事务 RM 都有它们自己的内置的事务管理器,这个管理器可以处理本地事务 —— 只涉及该 RM 的事务)。但是,如果事务涉及两个 RM 或多个 RM —— 可能是两个单独的数据库,或者是一个数据库和一个 JMS 队列,或者是两个单独的 JMS 提供者 —— 我们想确保 “要么全有要么全无” 的语义不仅仅应用于这个 RM 中,而且还应用于事务中的所有 RM。在这种情况下,TPM 将组织一个两阶段提交。在两阶段提交中,TPM 首先向每个 RM 发送一个 “准备” 消息,询问它是否准备就绪以及是否能够提交事务;如果它收到来自所有 RM 的确认应答,则将事务在其自己的事务日志中标记为已提交,然后指示所有 RM 提交事务。如果某个 RM 失败,则重新启动时它将向 TPM 询问有关失败时未处理的所有事务的状态,并提交它们或者对它们执行回滚操作。

两个阶段提交类似于社会上的结婚典礼 —— 牧师或神父询问双方 “您愿意让这个男人/女人作为您的丈夫/妻子吗?” 如果双方都回答是,则将宣布他们成为夫妻;否则,双方不能结婚。不管双方中的哪一方首先说 “我愿意”,在一方没有回答时,另一方决不能完成结婚。

事务作为处理异常的机制

您可能观察到,事务向对块进行同步的应用程序数据提供很多与内存中数据相同的功能 —— 保证原子性、更改的可见性以及显而易见的排序。但是,当同步主要是并发控制的机制时,则事务主要是处理异常的机制。如果在一个磁盘不会发生故障、系统和软件不会崩溃以及电源是百分百可靠的世界中,我们将不需要事务。事务在企业应用程序中所起的作用与合同法在社会上所起的作用一样 —— 它们规定,如果一方不能履行他那一部分合同,则交易将失效。当我们编写合同时,我们通常希望它是多余的,令人感到欣慰的是大部分时候都是如此。

与比较简单的 Java 程序进行类比,事务在应用程序级别所提供的一些优势与 catch 和 finally 块在方法级别所提供的优势相同;它们使我们不用编写很多错误复原代码,即可执行可靠的错误复原。考虑下面这个方法,该方法将一个文件复制到另一个文件:

public boolean copyFile(String inFile, String outFile) {
InputStream is = null;
OutputStream os = null;
byte[] buffer;
boolean success = true;

try {
is = new FileInputStream(inFile);
os = new FileOutputStream(outFile);
buffer = new byte[is.available()];
is.read(buffer);
os.write(buffer);
}
catch {IOException e) {
success = false;
}
catch (OutOfMemoryError e) {
success = false;
}
finally {
if (is != null)
is.close();
if (os != null)
os.close();
}

return success;
}

忽略为整个文件分配一个缓冲区是一个不好的想法,但是在这个方法中哪里错了呢?有很多东西。输入文件可能不存在,或者该用户可能没有这个文件的读权限。用户可能没有输出文件的写权限,或者该文件被另一个用户锁定。可能没有足够的磁盘空间来完成该文件的写操作,或者由于没有足够的内存可用,分配缓冲区可能失败。幸运的是,所有这些都由 finally 语句来处理,该语句释放了 copyFile()所使用的所有资源。

如果您使用原来的 C 语言编写这个方法,则对于每个操作(打开输入、打开输出、malloc、读、写),必须测试返回状态,如果操作失败,则取消以前成功的所有操作,并返回适当的状态代码。由于需要这么多错误处理代码,该代码可能更大,因此更难阅读。同时在错误处理代码(它也是最难测试的部分)中很容易出错,比如不能释放资源、对一个资源释放两次或者释放尚未分配的资源。更复杂的方法可能涉及更多资源,而不仅仅是两个文件和一个缓冲区,这使得问题变得更加复杂。在大量错误复原代码中,很难发现实际的程序逻辑。

现在,假设您正在执行一个复杂的操作,该操作涉及在多个数据库中插入或更新多个行,其中一个操作违反了完整性约束并失败了。如果您管理自己的错误复原,则必须跟踪已经执行的操作,并知道在随后的操作失败的情况下如何取消每个操作。如果工作单元分布在多个方法或组件上,则会更加困难。借助事务来构造应用程序,就可以将所有这些清理工作委托给数据库(即进行 ROLLBACK),并取消自从事务开始所执行的所有操作。

结束语

通过借助事务构造应用程序,我们定义一组正确的应用程序状态转换,并确保应用程序始终处于正确的状态,甚至在系统或组件发生故障之后也是如此。事务使我们能够将很多异常处理和恢复工作委托给 TPM 和 RM,从而简化了我们的代码,并使我们能够空出更多时间来考虑应用程序逻辑。

在此系列的第 2 部分中,我们将探讨这对于 J2EE 应用程序意味着什么 —— J2EE 如何使我们能够将事务语义告知 J2EE 组件(EJB 组件、servlet 以及 JSP 页面);它如何使资源征用对应用程序(甚至对于 bean 管理的事务)完全透明;单个事务如何 透明地遵循从一个 EJB 组件到另一个 EJB 组件,或者从一个 servlet 到一个 EJB 组件,甚至跨越多个系统的控制流程。

尽管 J2EE 提供了相当透明的对象事务服务,但是应用程序设计者仍然必须仔细考虑在哪里划分事务,以及如何在应用程序中使用事务资源 —— 不正确的事务划分可能会使应用程序处于不一致的状态,而不正确地使用事务资源可能会造成非常严重的性能问题。在此系列的第 3 部分中,我们将讨论这些问题并提供一些关于如何构造应用程序的建议。

7e6ee27100758030a7ba2e1cea25e78b.jpeg

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[补题记录] Atcoder Beginner Contest 295(E)

URL:https://atcoder.jp/contests/abc295 目录 E Problem/题意 Thought/思路 Code/代码 E Problem/题意 给定长度为 N 的数组 A。进行如下操作: 若 Ai 0,将 Ai 等概率地变为 1 ~ M 中的任意一个数;对 A 排序; …

SQL及数据库基础知识点总结

一. SQL(Structured Query Language): 结构化查询语言。SQL语法不区分关键字的大小写,多条SQL语句必须以;分隔。 二. SQL的作用: SQL可以访问和处理数据库,包括数据的增删改查(插…

【实战项目之网页聊天室】

目录 项目背景 需求分析 1.用户管理模块 注册 登录 2.主界面 个人信息模块 会话列表模块 好友列表模块 消息区域模块 消息传输模块 添加好友模块 编写项目 1.创建项目添加依赖 2.配置项目信息 3.功能实现 用户管理模块 个人信息模块 好友列表模块 消息区…

FPmarkets:MT4中Renko图表工具有哪些?怎么用

以下FPmarkets总结的在MT4中使用Renko图表的最有趣的工具: 第一个是KT Renko实时图表指标,这是一个简单的指示器,仅显示砖块,未添加其他元素,因此与其他自定义指标和顾问兼容。 第二个是Renko Live Chart开发人员提供…

Kafka消费者使用案例

本文代码链接:https://download.csdn.net/download/shangjg03/88422633 1.消费者和消费者群组 在 Kafka 中,消费者通常是消费者群组的一部分,多个消费者群组共同读取同一个主题时,彼此之间互不影响。Kafka 之所以要引入消费者群组…

源码选择指南:比较流行的同城外卖跑腿系统解决方案

随着现代生活的快节奏和数字化转型的浪潮,外卖和跑腿服务成为了不可或缺的一部分。不仅在城市,就连小镇和乡村也可以享受到这些便捷的服务。如果您计划开发或改进一个同城外卖和跑腿系统,选择适合的源码解决方案是至关重要的。在本文中&#…

Jenkins 结合 ANT 发送测试报告

目录 全局变量配置 新建任务 插件安装 HTML 报告配置 邮件配置 全局变量配置 点击 ManageJenkins进入Jenkins 管理 点击 Global Tool Configuration 进入全局变量配置 配置 Ant ,Name 自己定义一个比较好理解的名称。 去掉 Install automatically 勾选&#x…

毕业设计选题Java+springboot校园新闻资讯系统源码 开题 lw 调试

💕💕作者:计算机源码社 💕💕个人简介:本人七年开发经验,擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等,大家有这一块的问题可以一起交流! 💕&…

VBA技术资料MF69:添加和删除工作表中的分页符

我给VBA的定义:VBA是个人小型自动化处理的有效工具。利用好了,可以大大提高自己的工作效率,而且可以提高数据的准确度。我的教程一共九套,分为初级、中级、高级三大部分。是对VBA的系统讲解,从简单的入门,到…

黑马JVM总结(三十三)

(1)运行期优化-逃逸分析 在运行期间java虚拟机会对我们代码做一些优化,时间会变短: 字节码反复调用,到达一定的阈值,会启用编译器对自己饿吗编译执行,从0层上升为1层C1 C1和C2他俩的区别是解释…

C语言进阶第六课-----------字符分类函数和内存的开辟

作者前言 🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂 ​🎂 作者介绍: 🎂🎂 🎂 🎉🎉&#x1f389…

【Python从入门到进阶】39、使用Selenium自动验证滑块登录

接上篇《38、selenium关于Chrome handless的基本使用》 上一篇我们介绍了selenium中有关Chrome的无头版浏览器Chrome Handless的使用。本篇我们使用selenium做一些常见的复杂验证功能,首先我们来讲解如何进行滑块自动验证的操作。 一、测试用例介绍 我们要通过sel…

websocket逆向-protobuf序列化与反序列化

系列文章目录 训练地址:https://www.qiulianmao.com 基础-websocket逆向基础-http拦截基础-websocket拦截基础-base64编码与解码基础-protobuf序列化与反序列化视频号直播弹幕采集实战一:Http轮询更新中 websocket逆向-protobuf序列化与反序列化基础 系…

操作系统 内存对齐

文章目录 内存管理内存对齐为什么需要内存对齐内存对齐的规则举例说明两个函数 内存管理 内存是计算机的重要组成部分,内存是与cpu沟通的桥梁,用来暂存cpu中的运算数据。在早期,程序直接运行在物理内存中,直接操作物理内存&#…

MathType7.5最新版本升级教程

MathType7.5是MathType6.9a的升级版本,这是一款好用的数学公式编辑器,软件支持win、mac等操作系统,可以与各类办公软件兼容,能够快速在office文档中进行各类数学公式、符号的输入和运算等操作,coco玛奇朵小编为大家带来…

LiveGBS流媒体平台GB/T28181常见问题-如何禁用删除已注册设备国标设备如何删除

LiveGBS常见问题-如何禁用删除已注册设备国标设备如何删除 1、禁用删除设备2、找到需要删除的设备3、接入控制黑名单4、配置到黑名单5、删除设备6、搭建GB28181视频直播平台 1、禁用删除设备 有的时候,需要将接入到平台的某些设备禁用,并删除。改如何操…

池化技术在真实业务中的实践

一些废话 作为一名Java开发人员,池化技术或多或少在业务代码中使用。常见的包括线程池、连接池等。也是因为Java语言超级丰富的基建,基本上这些池化能力都有着相对成熟的“工具”。比如,需要使用线程池的时候常常会选择Spring提供的 ThreadP…

DL Homework 4

目录 1 整理一下理论收获 1.1 基础理论 1.2 应用到机器学习 1.3 参数学习 1.4 反向传播算法 2.激活函数 3.神经网络流程推导(包含正向传播和反向传播) 4.数值计算 - 手动计算 5.代码实现 - numpy手推 6.代码实现 - pytorch自动 7.激活函数Sigmoid用PyTorch自带函数torc…

蓝桥杯(七段码,C++)

思路&#xff1a; 1、把灯管的连接转为图结构&#xff0c;相邻的灯管即认为有边。 2、用深度搜索&#xff0c;去计算有多少种不同字符。 3、因为有每种字符都会重复算两遍&#xff0c;最后的结果需要数以2。 #include <iostream> using namespace std;int graph[7][7…

震惊!阿里卷成这样?不吃饭了,上厕所、团建都要聊工作,人均上厕所小于一天三次...

上一篇&#xff1a;雷军被小米封号 你敢相信吗&#xff1f;最近一篇名为《坐标阿里 我们组已经不吃饭了》的帖子在大厂社区上引发热议。 作者爆料&#xff1a;坐标阿里&#xff0c;组里已经卷到不吃饭了&#xff0c;之前卷到上厕所要聊工作、团建都要聊工作&#xff0c;现在已经…