六种常用事务解决方案 没有最好只有更好

本文探讨了分布式事务的概念,分析了在分布式系统中保证数据一致性的挑战。介绍了事务的ACID特性,并详细阐述了本地事务、分布式事务的场景和问题。文章进一步讨论了CAP理论和BASE理论,以及刚柔事务的概念。最后,文章列举了常见的分布式事务解决方案,如2PC、3PC、TCC和本地消息表,分析了各自的优缺点和适用场景。

1 事务概念

在分布式系统中,为了保证数据的高可用,通常,我们会将数据保留多个副本(replica),这些副本会放置在不同的物理的机器上。为了对用户提供正确的 CRUD 等语义,我们需要保证这些放置在不同物理机器上的副本是一致的。分布式事务在现在遍地都是分布式部署的系统中几乎是必要的。

我们的项目用到了数据库,也和事务有关,我们先分析一下项目的问题,再描述一下事务。

如上图,如果用户打车成功,需要修改司机状态、下单、记录支付日志,而每个操作都是调用了不同的服务,比如此时 hailtaxi-driver​ 服务执行成功了,但是 hailtaxi-order 有可能执行失败了,这时候如何实现跨服务事务回滚呢?这就要用到分布式事务。

1.1 事务简介

事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据库中,一个事务由一组SQL语句组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

原子性(atomicity) :事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

一致性(consistency) :事务必须是使数据库从一个一致性状态变到另一个一致性状态,事务的中间状态不能被观察到的。

隔离性(isolation) :一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。隔离性又分为四个级别:读未提交(read uncommitted)、读已提交(read committed,解决脏读)、可重复读(repeatable read,解决虚读)、串行化(serializable,解决幻读)。

持久性(durability) :持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

任何事务机制在实现时,都应该考虑事务的ACID特性,包括:本地事务、分布式事务,及时不能都很好的满足,也要考虑支持到什么程度。

1.2 本地事务

大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务( Local Transaction )。本地事务的ACID特性是数据库直接提供支持。本地事务应用架构如下所示:

很多java应用都整合了spring,并使用其声明式事务管理功能来完成事务功能。一般使用的步骤如下:

​ 1、配置事务管理器。spring提供了一个 PlatformTransactionManager 接口,其有2个重要的实现类:

DataSourceTransactionManager​ :用于支持本地事务,事实上,其内部也是通过操作 java.sql.Connection 来开启、提交和回滚事务。

JtaTransactionManager :用于支持分布式事务,其实现了JTA规范,使用XA协议进行两阶段提交。需要注意的是,这只是一个代理,我们需要为其提供一个JTA provider,一般是Java EE容器提供的事务协调器(Java EE server's transaction coordinator),也可以不依赖容器,配置一个本地的JTA provider。

​ 2、 在需要开启的事务的bean的方法上添加 @Transitional 注解

可以看到,spring除了支持本地事务,也支持分布式事务,下面我们先对分布式事务的典型应用场景进行介绍。

1.3 分布式事务

当下互联网发展如火如荼,绝大部分公司都进行了数据库拆分和服务化(SOA)。在这种情况下,完成某一个业务功能可能需要横跨多个服务,操作多个数据库。这就涉及到到了分布式事务,用需要操作的资源位于多个资源服务器上,而应用需要保证对于多个资源服务器的数据的操作,要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同资源服务器的数据一致性。

1.3.1 跨库事务

跨库事务指的是,一个应用某个功能需要操作多个库,不同的库中存储不同的业务数据。下图演示了一个服务同时操作2个库的情况:

1.3.2 分库分表事务

通常一个库数据量比较大或者预期未来的数据量比较大,都会进行水平拆分,也就是分库分表。如下图,将数据库B拆分成了2个库:

​ 对于分库分表的情况,一般开发人员都会使用一些数据库中间件来降低sql操作的复杂性。如,对于sql: insert into user(id,name) values (1,"gupaoedu"),(2,"gpvp") 。这条sql是操作单库的语法,单库情况下,可以保证事务的一致性。

但是由于现在进行了分库分表,开发人员希望将1号记录插入分库1,2号记录插入分库2。所以数据库中间件要将其改写为2条sql,分别插入两个不同的分库,此时要保证两个库要不都成功,要不都失败,因此基本上所有的数据库中间件都面临着分布式事务的问题。

1.3.3 跨应用事务

微服务架构是目前一个比较一个比较火的概念。例如上面提到的一个案例,某个应用同时操作了9个库,这样的应用业务逻辑必然非常复杂,对于开发人员是极大的挑战,应该拆分成不同的独立服务,以简化业务逻辑。拆分后,独立服务之间通过RPC框架来进行远程调用,实现彼此的通信。下图演示了一个3个服务之间彼此调用的架构:

Service A完成某个功能需要直接操作数据库,同时需要调用Service B和Service C,而Service B又同时操作了2个数据库,Service C也操作了一个库。需要保证这些跨服务的对多个数据库的操作要不都成功,要不都失败,实际上这可能是最典型的分布式事务场景。

上述讨论的分布式事务场景中,无一例外的都直接或者间接的操作了多个数据库。如何保证事务的ACID特性,对于分布式事务实现方案而言,是非常大的挑战。同时,分布式事务实现方案还必须要考虑性能的问题,如果为了严格保证ACID特性,导致性能严重下降,那么对于一些要求快速响应的业务,是无法接受的。

2 分布式理论

分布式事务可以有多种分类,比如柔性事务和强一致性事务,这些事务操作会遵循一定的定理,比如CAP原理、BASE理论。

2.1 CAP原理

CAP 定理又被

### 分布式事务的四种常用解决方案 #### 1. **两阶段提交 (Two-Phase Commit, 2PC)** 两阶段提交是一种经典的分布式事务协议,其核心思想是通过协调者(Coordinator)和参与者(Participants)之间的交互来完成全局事务的一致性。该方案分为准备(Prepare)和提交(Commit)两个阶段。 - 准备阶段:协调者向所有参与者发送预提交请求,询问是否可以执行提交操作并锁定必要的资源。 - 提交阶段:如果所有参与者都同意,则协调者发出正式提交指令;若有任意一个参与者拒绝,则整个事务被回滚。 尽管此方法能够很好地保障数据一致性[^2],但它存在性能瓶颈问题,尤其是在大规模分布式环境中表现不佳,因为所有的资源都会被长时间锁定直到第二阶段结束。 ```java // 假设有一个分布式事务管理器类 public class DistributedTransactionManager { public void prepare() throws Exception { /* ... */ } public void commit() throws Exception { /* ... */ } public void rollback() throws Exception { /* ... */ } } ``` --- #### 2. **基于消息队列的最终一致性 (Message Queue Based Eventual Consistency)** 这种方法利用消息中间件作为媒介,在本地事务完成后发布一条消息到消息队列中,其他服务订阅这条消息后继续处理后续逻辑。它依赖于消息传递机制确保跨多个系统的更新达到一致状态。 优点在于解耦了各个子系统间的直接调用关系,并允许一定程度上的延迟容忍度以换取更高的可用性和扩展能力[^1]。然而需要注意的是,为了防止丢失事件或者重复消费等情况发生,通常还需要设计幂等性的接口以及死信队列等功能模块加以辅助保护措施。 ```json { "event": "order_created", "data": { "orderId": "1234567890", "amount": 100, "currency": "USD" } } ``` --- #### 3. **TCC模式 (Try-Confirm-Cancel Pattern)** TCC 是一种补偿型事务模型,要求业务层提供三种操作函数——尝试(Try),确认(Confirm),取消(Cancel)[^1]。其中 Try 阶段负责预留资源或检查可行性而不实际改变任何持久化存储中的记录;Confirm 步骤则是不可逆地落实之前所做的准备工作;而 Cancel 动作用于释放那些已经被占用却未能顺利完成流程的部分。 虽然 TCC 能够灵活适应复杂的商业场景需求,但由于增加了额外开发成本并且对于异常情况下的分支路径覆盖不够全面等原因使其推广起来相对困难一些。 ```sql -- 尝试阶段 SQL 示例 INSERT INTO reservation_table (...) VALUES (...); UPDATE stock_table SET reserved_stock = reserved_stock + ? WHERE product_id = ?; -- 确认/取消阶段 SQL 示例 DELETE FROM reservation_table WHERE id = ?; UPDATE stock_table SET available_stock = available_stock - ?, reserved_stock = reserved_stock - ? WHERE product_id = ? AND reserved_stock >= ?; ``` --- #### 4. **Saga模式** Saga 模式适用于长期运行的任务序列分解成若干个小步骤的情况之下。每一个单独的动作都可以看做是一个独立的服务调用链路节点,当某个环节出现问题时可以通过反向操作来回退前面已经做出的效果从而恢复原状[^3]。 相比起传统的 XA 协议来说 Saga 更加轻量化同时也更容易维护调试,不过同样面临着如何高效检测错误来源以及制定合理的重试策略等方面的挑战。 ```python def saga_orchestration(): try: step_1_result = call_service_a() step_2_result = call_service_b(step_1_result) finalize_transaction(step_2_result) except Exception as e: compensate_step_2(e) compensate_step_1(e) raise ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值