[Java微服务架构]7-1_事务处理——事务特性与本地事务
欢迎来到啾啾的博客🐱。
这是一个致力于构建完善的Java程序员知识体系的博客📚,记录学习的点滴,分享工作的思考、实用的技巧,偶尔也分享一些杂谈💬。
欢迎评论交流,感谢您的阅读😄。
这篇比我想的要长,分几篇发。
本篇先介绍“事务特性”与“单服务单数据源”
全盘将介绍如引言所述分布式系统中事务的处理。
下篇为[Java微服务架构]7-2_事务处理——全局事务与共享事务
引言
事务在每一个信息系统中都会涉及,数据库、分布式共享数据,只要是与信息、与数据相关的,都会涉及到事务。
Q:可我们要怎么表述事务呢?
事务是:“通过ACID特性确保数据安全与一致性的数据操作的逻辑单元”
现在需要保证数据一致性的场景很多,包括但不限于数据库、事务内存、缓存、消息队列、分布式存储等等……
事务要怎么保证这些场景的数据一致性呢?
这个问题可以拆分为以下问题,即:
”单服务单数据源“事务是怎么处理?
”单服务多数据源“事务是怎么处理呢?
”多服务单数据源“事务是怎么处理呢?
”多服务多数据源“事务(分布式事务)是怎么处理呢?
1.事务特性
ACID
原子性Atomicity 一致性Consistency 隔离性Isolation 持久性Durability。
一般来说,其中原子性 A、隔离性I、持久性D都是为了保证数据的一致性 C。即满足A、I、D,以实现数据的一致性I。
A、I、D for I
具体关于四个特性的描述,在MySQL事务开篇有提及,这里不再赘述。
内部一致性(Internal Consistency)
内部一致性指的是在分布式系统中的一个事务执行过程中,系统内部的数据状态保持逻辑上的正确性和完整性。
即,事务要么成功,要么失败。关注事务本身。
分布式系统中,内部一致性通常通过事务管理器、两阶段提交(2PC)或者日志回滚等机制来实现。
外部一致性(External Consistency)
当一个服务使用到多个不同的数据源,甚至多个不同服务同时涉及多个不同的数据源时,问题就变得困难了许多。此时,并发执行甚至是先后执行的多个事务,在时间线上的顺序并不由任何一个数据源来决定,这种涉及多个数据源的事务间一致性被称为“外部一致性”。
即,多个数据源在外部看来的一致性。全局视角。
外部一致性通常比内部一致性更难实现,因为它需要协调多个节点之间的数据同步,可能涉及 Paxos、Raft 这样的共识算法,或者使用强一致性协议。
2.本地事务(Local Transaction)
本地事务是适用于单个服务使用的单数据源场景的事务解决方案。
ARIES理论
ARIES是现代数据库的基础理论。
(Algorithms for Recovery and Isolation Exploiting Semantic,ARIES),直接翻译过来是“基于语义的恢复与隔离算法”。
其着重解决了原子性A和持久性D在算法层面上的实现。
还是算法,所以说程序就是=数据结构+算法
实现原子性和持久性
原子性和持久性在事务里是密切相关的两个属性:原子性保证了事务的多个操作要么都生效要么都不生效,不会存在中间状态;持久性保证了一旦事务生效,就不会再因为任何原因而导致其修改的内容被撤销或丢失。
实现原子性和持久性需要将数据“写入磁盘”,最大困难是“写入磁盘”这个操作并不是原子
的,不仅有“写入”与“未写入”状态,还客观存在着“正在写”的中间状态。
为了避免“未提交事务,写入后崩溃(事务未完成,部分数据写入磁盘)”、“已提交事务,写入前崩溃(事务已完成,未写入磁盘)”,需要进行数据恢复操作,即“恢复崩溃”。
有一种能“恢复崩溃”的事务实现方式为——“提交日志 (Commit Logging)”。
提交日志 Commit Logging
提交日志的核心思想是:在事务真正修改数据之前,先把事务的所有操作记录到一个持久化的日志中。这个日志通常存储在磁盘上(或高可用存储中),确保即使系统崩溃,事务的状态也能通过日志恢复。
主要作用是:
- 保证原子性:通过记录操作意图,确保事务要么全部完成,要么全部回滚。
- 提供持久性:一旦日志被写入并标记为“已提交”,即使系统故障,数据也能恢复。
- 支持一致性:日志记录了事务的完整状态,便于协调多个节点。
工作流程 :
- 1.日志记录阶段:
- 事务开始时,系统将事务的所有操作(如“更新 A=100”“插入 B=200”)写入日志。
- 这些操作以“意图”(intent)的形式记录,而不是直接修改数据库。
- 2.提交阶段:
- 当事务的所有操作都记录到日志后,系统写入一个“提交记录”(commit record),表示事务已成功。
- 提交记录写入后,事务被认为“持久化”,即使后续崩溃也能恢复。
- 3.应用阶段:
- 日志提交后,系统根据日志内容实际更新数据库(称为“重做”,redo)。
- 4.恢复阶段(如果崩溃):
- 系统重启后,读取日志:
- 如果有提交记录,就重做操作。
- 如果没有提交记录,就回滚(撤销未完成的操作,称为“undo”)。
- 系统重启后,读取日志:
以MySQL为例简单描述,其InnoDB使用一种叫WAL (Write-Ahead Logging,预写日志)的方式提交日志:
1.执行修改数据操作,先生成Undo Log记录数据修改前状态
2.将“更改记录”写入Redo Log
3.按事务顺序将DDL和DML操作记录到Binlog
4.事务打上commit标签,正式结束。
可以看出,核心是Redo Log与Undo Log,提交日志流程借助这两者进行数据崩溃恢复。
MySQL实际数据写入流程没这么简单,这里只是提取了提交日志部分。
实现隔离性
提起隔离,很容易想到使用锁的方式进行并发隔离。
以数据库为例,有3种锁——写锁、读锁、范围锁,其中写锁排他,读锁共享,范围锁是典型的加范围(范围内不能写入)。
也可以按照范围划分为行锁、表锁、间隙锁(Gap Lock)与Next-Key Lock.
还是以MySQL为例,8.0版本默认隔离级别为RR,可重复读。
实现机制中锁为:读取加共享锁(部分情况),写操作加排他锁,范围操作可能使用 Next-Key Lock(InnoDB 默认使用 Next-Key Lock,它是行锁和间隙锁的组合)。还有MVCC。
对于隔离性来说,隔离级别越高,隔离程度越高,并发能力越低,但脏读、幻读、不可重复度现象越少。
具体隔离级别与MVCC可以看这篇MySQL事务。