零、文章目录
架构03-事务处理
1、本地事务实现原子性和持久性
(1)事务类型
- **本地事务:**单个服务、单个数据源
- **全局事务:**单个服务、多个数据源
- **共享事务:**多个服务、单个数据源
- **分布式事务:**多个服务、多个数据源
(2)事务意义
- **一致性:**保证系统中的数据正确,不同数据间不会产生矛盾。
- ACID特性:
- **原子性(Atomicity):**多个对数据的修改要么同时成功,要么一起被撤销。
- **隔离性(Isolation):**不同业务处理过程中的数据读写互相独立,不会彼此影响。
- **持久性(Durability):**成功提交的数据修改能够正确地被持久化,不丢失数据。
(3)事务场景
- **事务的概念:**最初源于数据库,但现代信息系统中,包括数据库、缓存、事务内存、消息、队列、对象文件存储等场景都可能涉及事务处理。
- **复杂性:**单个服务使用单个数据源时较容易实现一致性,但涉及多个数据源或多个服务时变得困难。
(4)本地事务
- **定义:**仅操作特定单一事务资源的事务,不需要全局事务管理器进行协调。
- **适用场景:**单个服务使用单个数据源。
- 实现原理
- **原子性:**保证事务的多个操作要么都生效,要么都不生效。
- **持久性:**保证事务生效后,修改的内容不会因任何原因被撤销或丢失。
(5)实现方法
- Commit Logging:
- **原理:**先将修改数据的操作记录到日志中,日志记录成功后,再对数据进行修改。
- **优点:**简单清晰,易于实现。
- **缺点:**性能较低,不允许在事务提交前写入变动数据。
- Write-Ahead Logging:
- **原理:**允许在事务提交前写入变动数据,通过 Undo Log 和 Redo Log 来实现崩溃恢复。
- 步骤:
- **分析阶段(Analysis):**扫描日志,找出待恢复的事务集合。
- **重做阶段(Redo):**重演历史,将包含 Commit Record 的日志写入磁盘。
- **回滚阶段(Undo):**根据 Undo Log 回滚未提交的事务。
- **优点:**性能较高,允许提前写入数据。
- **缺点:**实现复杂,涉及多种日志类型和复杂的恢复机制。
(6)ARIES理论
- **定义:**基于语义的恢复与隔离算法,现代主流关系型数据库在事务实现上深受其影响。
- **核心思想:**通过 Write-Ahead Logging 实现高效且严谨的日志记录与故障恢复。
- 组合策略:
- **FORCE:**事务提交后,要求变动数据必须同时完成写入。
- **STEAL:**事务提交前,允许变动数据提前写入。
- **最佳组合:**NO-FORCE + STEAL,性能最高但实现复杂。
2、数据库事务隔离性实现原理
(1)隔离性与并发
- **定义:**隔离性保证每个事务各自读、写的数据互相独立,不会彼此影响。
- **并发关系:**如果没有并发,所有事务全都是串行的,不需要任何隔离。但在现实中,并发是不可避免的,因此需要通过加锁等方式实现串行的数据访问。
(2)数据库提供的三种锁
- 写锁(Write Lock / X-Lock)
- 只有持有写锁的事务才能对数据进行写入操作。
- 数据加持着写锁时,其他事务不能写入数据,也不能施加读锁。
- 读锁(Read Lock / S-Lock)
- 多个事务可以对同一个数据添加多个读锁。
- 数据被加上读锁后不能被加上写锁,其他事务不能对该数据进行写入,但仍然可以读取。
- 持有读锁的事务可以将读锁升级为写锁,然后写入数据。
- 范围锁(Range Lock)
- 对某个范围直接加排他锁,在这个范围内的数据不能被读取,也不能被写入。
- 范围锁不仅禁止修改已有数据,还禁止在该范围内新增或删除任何数据。
(3)本地事务的四种隔离级别
- 可串行化(Serializable)
- **隔离强度:**最高
- **实现方式:**对事务所有读、写的数据全都加上读锁、写锁和范围锁。
- **性能影响:**并发能力最低,吞吐量最低。
- 可重复读(Repeatable Read)
- **隔离强度:**次高
- **实现方式:**对事务所涉及的数据加读锁和写锁,持续到事务结束,但不加范围锁。
- **问题:**幻读问题(Phantom Reads),即在事务执行过程中,两个完全相同的范围查询得到了不同的结果集。
- 读已提交(Read Committed)
- **隔离强度:**中等
- **实现方式:**对事务涉及的数据加的写锁持续到事务结束,但读锁在查询操作完成后立即释放。
- **问题:**不可重复读问题(Non-Repeatable Reads),即在事务执行过程中,对同一行数据的两次查询得到了不同的结果。
- 读未提交(Read Uncommitted)
- **隔离强度:**最低
- **实现方式:**对事务涉及的数据只加写锁,持续到事务结束,完全不加读锁。
- **问题:**脏读问题(Dirty Reads),即在事务执行过程中,一个事务读取到了另一个事务未提交的数据。
(4)多版本并发控制(MVCC)
- 基础原理:
- **版本管理:**对数据库的任何修改都不会直接覆盖之前的数据,而是产生一个新版副本与老版本共存。
- **版本字段:**每一行记录都有两个看不见的字段:CREATE_VERSION 和 DELETE_VERSION,记录事务 ID。
- 读取策略:
- 可重复读:总是读取 CREATE_VERSION 小于或等于当前事务 ID 的记录,取最新版本。
- 读已提交:总是取最新的版本即可,即最近被 Commit 的那个版本的数据。
- **适用场景:**主要针对“读 + 写”场景的优化,如果是“写 + 写”场景,仍需加锁。
(5)乐观加锁与悲观加锁
- **悲观加锁:**认为事务之间数据存在竞争是普遍情况,因此一开始就加锁。
- **乐观加锁:**认为事务之间数据存在竞争是偶然情况,因此不一开始就加锁,而是出现竞争时再采取补救措施。
- **性能影响:**竞争剧烈时,乐观锁可能更慢。
3、全局事务和共享事务
(1)全局事务
- **定义:**全局事务(Global Transactions)适用于单个服务使用多个数据源的场景,是一种在分布式环境中追求强一致性的事务处理方案。
- 实现方式:
- XA 协议:
- **定义:**1991年X/Open组织提出的事务处理框架,定义了全局事务管理器(Transaction Manager)和局部资源管理器(Resource Manager)之间的通信接口。
- 核心组件:
- 事务管理器:协调全局事务。
- 资源管理器:驱动本地事务。
- JTA(Java Transaction API):
javax.transaction.TransactionManager
:用于容器事务管理。javax.transaction.UserTransaction
:用于手动开启、提交和回滚事务。javax.transaction.xa.XAResource
:资源定义接口,实现该接口的资源支持JTA。
- XA 协议:
- 两段式提交(2PC):
- 阶段:
- **准备阶段:**协调者询问参与者是否准备好提交。
- **提交阶段:**协调者根据参与者回复决定提交或回滚。
- **优点:**保证了事务的一致性。
- 缺点:
- **单点问题:**协调者宕机会影响所有参与者。
- **性能问题:**多次远程调用和数据持久化导致性能较差。
- **一致性风险:**网络不稳定可能导致数据不一致。
- 阶段:
- 三段式提交(3PC):
- 阶段:
- CanCommit:协调者询问参与者事务是否可以完成。
- PreCommit:参与者准备提交。
- DoCommit:协调者发送提交指令。
- **优点:**改善了单点问题和回滚时的性能问题。
- **缺点:**并未解决一致性风险问题,甚至可能增加一致性风险。
- 阶段:
(2)共享事务
- **定义:**共享事务(Share Transactions)是指多个服务共用同一个数据源。
- 实现方式:
- **数据源与数据库的区别:**数据源是提供数据的逻辑设备,不一定与物理设备一一对应。
- **场景:**多个微服务共用同一个数据库。
- 方案:
- **共享数据库连接:**同一应用进程内的不同持久化工具共享数据库连接。
- **交易服务器:**新增中间角色,各服务通过交易服务器与数据库交互。
- **消息队列:**使用消息队列服务器代替交易服务器,通过消息驱动更新数据库。
- 局限性:
- **性能问题:**数据库是系统中的瓶颈,难以扩展。
- **伪需求:**实际应用中较少使用,更多是理论上的可行性。
4、分布式事务
(1)分布式事务的挑战
- 分布式事务的复杂性
- **DTP 模型:**分布式事务相对于数据源而言,不涉及服务
- **分布式服务环境下的事务处理机制:**涉及多个服务同时访问多个数据源
- **事例场景:**Fenix’s Bookstore
- **服务拓扑:**账号、商家、仓库服务集群
- CAP 问题:
- **一致性问题:**数据未及时同步导致错误交易
- **可用性问题:**数据同步过程中暂停服务
- **分区容忍性问题:**网络分区导致服务不正确
- CAP 取舍
- **CP 系统:**放弃可用性,保证一致性和分区容忍性
- **RDBMS 集群:**通过共享磁盘避免网络分区
- **HBase:**RegionServer 宕机导致数据离线
- **AP 系统:**放弃一致性,保证可用性和分区容忍性
- **NoSQL 库:**节点独立提供服务,可能出现不一致数据
- **Redis 集群:**节点独立提供服务,可能出现不同数据
- **CP 系统:**放弃可用性,保证一致性和分区容忍性
(2)CAP 理论与 ACID 原则的矛盾
- CAP 理论
- **一致性(C):**所有节点数据一致
- **可用性(A):**系统不间断提供服务
- **分区容忍性(P):**部分节点失联时系统仍能正确提供服务
- ACID 原则
- **原子性(A):**事务中的所有操作要么全部成功,要么全部失败
- **一致性(C):**事务执行前后,数据库保持一致状态
- **隔离性(I):**并发事务互不干扰
- **持久性(D):**事务一旦提交,结果永久保存
- 矛盾点
- **CAP 理论:**分布式系统中最多只能同时满足两个特性
- **ACID 原则:**要求事务具有强一致性
(3)可靠事件队列
- 弱一致性
- **定义:**牺牲一致性,追求可用性和分区容忍性
- **最终一致性:**数据在一段时间内未被修改,最终达到一致状态
- 实现方式:可靠事件队列
-
- **BASE 模型:**基本可用、软状态、最终一致性
- 操作时序:
- 用户发送交易请求
- 系统评估操作顺序
- 账户服务扣款并写入消息表
- 消息服务轮询消息表,发送消息
- 服务节点处理消息并返回结果
- 消息服务重试直至成功
- 关键点
- **幂等性:**消息重复发送不会导致重复操作
- **最大努力交付:**通过持续重试保证可靠性
(4)TCC (Try-Confirm-Cancel)
- 特点:
- **强隔离性:**通过Try阶段预留资源,确保事务的隔离性。
- **业务侵入性:**需要将业务逻辑拆分为Try、Confirm和Cancel三个阶段,对业务代码有较高侵入性。
- **高性能:**在业务执行时只操作预留资源,减少锁和资源争用,具有较高的性能潜力。
- 实现过程:
- **Try阶段:*** 检查业务可行性。* 预留业务资源。
- **Confirm阶段:*** 使用Try阶段准备的资源完成业务处理。* 满足幂等性,可能重复执行。
- **Cancel阶段:*** 释放Try阶段预留的业务资源。* 满足幂等性,可能重复执行。
- 适用场景:
- **强隔离性要求:**如在线书店的库存管理,需要避免超售问题。
- **高性能要求:**在业务逻辑允许的情况下,可以通过TCC提高系统吞吐量。
(5)SAGA
- 特点:
- **灵活性:**将大事务拆分为多个小事务,每个小事务都有对应的补偿操作。
- **低业务侵入性:**不需要为资源设计冻结状态和撤销冻结的操作,补偿操作相对容易实现。
- **最终一致性:**通过正向恢复或反向恢复策略,确保最终一致性。
- 实现过程:
- 子事务拆分:
- 将大事务T拆分为多个子事务T1, T2, …, Tn。
- 补偿操作设计:
- 为每个子事务Ti设计对应的补偿操作Ci。
- 满足幂等性和交换律。
- 恢复策略:
- **正向恢复:**如果Ti提交失败,重试直至成功。
- **反向恢复:**如果Ti提交失败,执行Ci补偿操作直至成功。
- 子事务拆分:
- 适用场景:
- **灵活性要求高:**如银行转账场景,无法要求银行冻结款项。
- **最终一致性要求:**在业务逻辑允许的情况下,可以通过SAGA确保最终一致性。
(6)AT事务(异步提交模式)
- 特点:
- **异步提交:**每个数据源单独提交,立即释放锁和资源,提高系统吞吐量。
- **牺牲隔离性:**可能产生脏写和脏读,需要通过全局锁机制来避免。
- **自动补偿:**通过保存SQL快照,自动生成逆向SQL进行补偿。
- 实现过程:
- SQL拦截:
- 自动拦截所有SQL,保存修改前后结果的快照,生成行锁。
- 本地事务提交:
- 通过本地事务提交快照和行锁。
- 分布式事务提交/回滚:
- 成功提交时,清理日志数据。
- 需要回滚时,根据日志数据生成逆向SQL进行补偿。
- SQL拦截:
- 适用场景:
- **高吞吐量要求:**在业务逻辑允许的情况下,通过异步提交提高系统吞吐量。
- **最终一致性要求:**在可以接受一定程度的脏写和脏读的情况下,确保最终一致性。