☰
Java-分布式事务基本概念、解决方案和实现原理
2026/9/30 5:41:30 网站建设 项目流程

一.分布式事务基本概念

分布式事务解决的是跨服务、跨数据库操作的一致性问题

在CAP理论下,通常需要在强一致性和高可用性之间做权衡,BASE理论则强调的是基本可用、软状态和最终一致性。

事务参与角色:

  • 事务协调者:协调所有参与者,管理全局事务
  • 事务发起者:发起全局事务,接收TC协调
  • 事务参与者:参与全局事务。

二.分布式事务解决方案

1. XA协议(数据库协议)

实现原理:

事务协调者先让所有参与者各自本地操作,根据所有参与者的操作结果,再统一提交或者回滚。

  • 么个事务参与者,先执行本地事务,执行完成后不提交,通知事务协调者
  • 事务协调者汇总事务执行状态,根据结果进行处理
    • 所有参与者执行成功,事务协调者会通知所有参与者一起提交事务。
    • 如果有一个参与者没有执行成功,协调者会通知所有参与者回滚事务。

特点:强一致性,但是回阻塞,性能低,适用于金融核心等并发场景。

2. TCC(Try-Confirm-Cancel)

对应有三个接口

  • Try 预留或者锁定资源
  • Confirm 确认执行
  • Cancel 释放资源并你想补偿

分为两阶段提交

  • 准备阶段:TC调用所有服务的Try接口
  • 提交阶段:
    • TC根据结果,如果所有成功,则调用所有服务的Confirm接口
    • 如果存在失败,则调用所有服务的Cancel接口

特点:高性能、无数据库长时间的事务等待,业务侵入强

3. Seata AT模式

基于数据源代理拦截 SQL,自动生成 undo_log,按全局事务状态提交或回滚

  • AT 自动事务处理,Seata 创新的一种非侵入式的分布式事务解决方案
  • 实现方式
    • 在各个事务的数据库中建表 undo_log表
    • 第一阶段: 直接执行业务,seata代理数据源(DataSource),拦截sql执行,提取sql执行前后的数据镜像,将前后数据镜像转化成一条sql,加入到当前本地事务,执行提交,插入到undo_log表中
    • 第二阶段:
      • 如果所有参与者都处理成功,TC 协调所有参与者直接删除undo_log记录
      • 如果存在参与者处理失败,TC协调所有的参与者回滚,参与者从undo_log表中提取前后镜像,计算得到逆向补偿sql,执行sql实现补偿

特点:无侵入、易操作,适合微服务下关系型数据库场景

4. Saga

拆成多个本地事务,有一个事务失败失败时执行补偿。

特点:适合长事务,无长时间锁,但补偿逻辑复杂

三.Seata AT 模式全局事务执行过程

Seata AT 模式依赖三个角色(前面提到过):

TC:事务协调者,维护全局事务状态,驱动提交或回滚

TM:事务发起者,开启全局事务,决定提交或回滚

RM:事务参与者,管理分支事务,生成 undo_log,执行本地事务和二阶段操作

执行流程如下:

  1. TM 开启全局事务,TC 生成 XID。
  2. XID 传递到下游服务,各服务的 RM 识别到 XID 后注册分支事务。
  3. 一阶段执行本地事务:RM 拦截 SQL,查询前镜像,执行业务 SQL,查询后镜像,生成 undo_log,将业务数据和 undo_log 在同一个本地事务中提交。
  4. 提交前获取全局锁:RM 在本地事务提交前向 TC 申请全局锁,获取成功后提交本地事务,释放本地数据库锁,获取失败则回滚本地事务并重试或超时放弃。
  5. 二阶段提交:若全局事务提交,TC 通知各 RM 异步删除 undo_log,过程很快。
  6. 二阶段回滚:若全局事务回滚,TC 通知 RM 根据 undo_log 的前镜像生成反向 SQL;回滚前会校验当前数据是否与后镜像一致,一致则回滚,不一致则说明存在外部脏写,可能需要人工处理。

四.Seata AT 模式如何实现写隔离?

AT 模式的写隔离不是靠长时间持有数据库锁实现的,而是通过全局锁和本地锁配合完成的。

当两个全局事务修改同一行数据时:

  1. 事务 A 先执行,获取数据库行锁,更新数据,生成前后镜像。
  2. 事务 A 在本地事务提交前,向 TC 申请该行数据的全局锁。
  3. 申请成功后,事务 A 提交本地事务,释放数据库行锁,但继续持有全局锁。
  4. 事务 B 随后执行,也能获取数据库行锁并更新数据,但在提交前申请全局锁时会发现该行已被事务 A 持有。
  5. 事务 B 只能等待全局锁释放;如果事务 A 全局提交,B 继续提交;如果事务 A 全局回滚,A 会根据 undo_log 反向补偿,B 最终超时或等待后重新执行。

这样就避免了“事务 A 已提交本地事务但全局事务尚未结束”时,事务 B 基于中间状态数据继续修改造成的脏写。

五.全局锁与本地锁的区别

  • 本地锁:数据库自身的行锁,由数据库管理,在本地事务执行期间持有,事务提交或回滚后立即释放,只能保证单个数据库本地事务的隔离性。
  • 全局锁:Seata TC 维护的逻辑锁,由 RM 在本地事务提交前申请,持有到全局事务二阶段结束才释放,用于保证跨全局事务的写隔离。

六.缓存与数据库不同步如何解决?

缓存与数据库不一致的根本原因是两者无法原子更新,且存在并发读写、网络延迟和缓存失效时序问题。通常以数据库为唯一可信数据源,追求最终一致性。

常用方案:

  1. Cache-Aside 旁路缓存
    读:先查缓存,未命中再查数据库并回填缓存。
    写:先更新数据库,再删除缓存。
    这是最常用方案,实现简单,但存在短暂不一致窗口。

  2. 延迟双删
    更新数据库后删除缓存,再延迟一段时间再次删除缓存,用于清除并发读请求回填的旧数据。延迟时间需大于读库回填缓存的耗时及可能的主从同步延迟。

  3. 消息队列补偿
    数据库更新成功后发送 MQ 消息,由消费者异步删除缓存。支持重试和死信兜底,适合分布式系统,但增加了链路复杂度。

  4. Binlog 监听
    通过 Canal 等工具订阅 MySQL Binlog,解析数据变更后异步删除或更新缓存。业务代码侵入小,适合微服务架构和数据异构同步。

  5. 兜底策略
    给缓存设置合理 TTL,即使删除失败也能自动过期刷新;核心强一致场景可减少缓存依赖,或直接读主库。

工程实践中,“先更新数据库,再删除缓存 + 合理 TTL + MQ/Binlog 兜底”是较常见的组合;如果一致性要求很高,再叠加延迟双删或版本号控制。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询