2

Distributed transaction - DiscreteTom's Blog

 2 years ago
source link: https://discretetom.github.io/posts/distributed-transaction/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Distributed transaction

分布式事务的解决方案

关系型数据库中本地事务的ACID特性(本地事务同时具有ACID四个属性):

  • Atomic - 原子性。事务中的所有操作要么全部执行,要么全部不执行
  • Consistency - 一致性。不会破坏关系型数据库中的数据一致性约束
  • Isolation - 隔离性。事务可以并发,不同事务互不干扰,无法看到其他事务的中间状态。避免脏读等问题
  • Durability - 持久性。一旦事务执行完毕就会持久化落盘

分布式事务的场景

分布式事务的产生场景:

  • 一个事务涉及到多个数据库。即一个事务操作多个数据库
  • 一个事务涉及到多个服务器进程。即多个服务器进程操作同一个事务

所以并不是只有多个数据库的场景才会需要使用分布式事务

分布式事务的CAP理论(最多只能同时满足3个中的两个):

  • Consistency - 一致性。写后读要么返回最新的数据,要么报错,绝对不会返回旧数据
    • 例:主从数据库,从库要么返回最新数据要么报错
  • Availability - 可用性。任何事务要么返回新数据,要么返回旧数据,但是不会超时报错
    • 例:主从数据库,从库允许返回旧数据
  • Partition tolerance - 分区容忍性。由于网络问题导致服务器之间通信失败时仍然可以对外提供服务。数据可能不一致。异步
    • 例:主从数据库,主从数据不一致(比如从库返回旧数据),但是仍然可以向外提供服务

组合方式:

  • AP - 允许返回旧数据。很常见。如商品管理
  • CP - 允许报错但是数据必须准确。比如zookeeper。常见于银行系统
  • CA - 不分区,也就是不使用分布式系统

BASE是对CAP中AP的扩展,牺牲强一致性提升可用性,数据可以短时间不一致。满足BASE的事务称为柔性事务

  • Basically Available - 基本可用。部分系统故障不会导致整个系统无法使用
    • 例:电商,付款系统坏了,但是仍然可以使用其他功能,比如加入购物车
  • Soft state - 软状态。允许出现中间状态
    • 例:电商,允许出现“支付中”等状态,以等待付款系统的修复
  • Eventually consistent - 最终一致性
    • 例:电商,付款系统修复后实现事务

两阶段提交(2-phase commit),适用于一个事务涉及多个数据库的场景

引入新的主体:事务协调者(Transaction Coordinator, TC)

  1. 客户端向TC发起请求
  2. 准备阶段(Prepare phase)
    1. TC向多个数据库发出prepare请求,包含要执行的事务
    2. 所有数据库执行事务,但是不提交
    3. 所有数据库向TC返回ok
  3. 提交阶段(Commit phase)
    1. TC向所有数据库发出commit请求,向客户端返回ok响应(不需要等待数据库的响应)
    2. 所有数据库持久化事务

如果准备阶段有任何数据库出现异常或超时,TC通知所有数据库取消事务。如果TC发出了commit请求,此时有数据库还没有收到commit就down了,数据库重启的时候可以向TC或其他数据库发起请求,询问全局事务是否被commit

很多常用数据库自身就支持2PC,比如Oracle、MySQL

2PC是在数据库层面实现的。为了统一标准,需要标准化处理模型和接口。国际开放组织(Open Group)定义了分布式事务处理模型(DTP, Distributed Transaction Processing Reference Model)

DTP中的主体:

  • 应用程序AP(Application Program)
  • 事务管理器TM(Transaction Manager),AP使用接口调用TM,控制全局事务
  • 资源管理器RM(Resource Manager),位于数据库上,控制分支事务

执行流程:

  1. AP调用TM的接口发起全局事务
  2. TM向所有RM发送prepare请求,发起分支事务
  3. 所有数据库执行操作,锁定资源,不提交事务,响应TM
  4. TM收到执行回复,然后向所有RM发起commit请求提交事务,释放资源锁

其中TM和RM之间通信的接口规范叫XA

优点:简单

  1. 需要数据库的支持
  2. 性能较差,需要所有数据库都上锁,执行完毕之后才能释放锁,降低数据库效率

Seata方案

是阿里中间件团队的开源项目Fescar发展而来。优化了2PC,是在应用层的实现,不需要数据库支持2PC

两种模式:

  • AT模式(即2PC模式)
  • TCC模式
  • 事务管理器TM(Transaction Manager)
    • 以jar包的方式嵌入应用程序
    • 负责发起全局事务、全局提交、全局回滚
  • 事务协调器TC(Transaction Coordinator)
    • 独立的中间件,需要独立部署
    • 其他TM没有实现的调度功能都由TC实现,以此减少对应用程序的侵入性
    • 维护全局事务的运行状态、接收TM的指令、负责和RM通信进行分支事务的提交与回滚
  • 资源管理器RM(Resource Manager)
    • 接收TC的指令,管理分支事务
    • 以jar包的方式嵌入应用程序
    • 执行TC的指令的时候需要在本地分支事务里面写入undo_log,undo_log是需要手动创建的一个表
    • 使用一个数据库代理来连接数据库,数据库代理自动实现写undo_log,其中包含修改前的数据和修改后的数据
  1. 应用通过TM向TC申请开启全局事务,TC创建全局唯一的事务ID:XID
  2. 应用控制所有RM向TC注册分支事务并执行、写入undo_log提交,释放锁、向TC上报分支事务执行结果
  3. 应用通过TM向TC发起针对XID的全局提交或回滚决议
    1. 如果全局提交,则删除undo_log
    2. 如果全局回滚,则TC向RM请求根据undo_log回滚、向TC汇报回滚状态
  4. TC完成XID的提交与回滚
  • 性能较好,不会长时间占用连接资源
  • 不需要数据库支持2PC
  • 易于使用,对业务代码0侵入

相比传统2PC的差别:

  • 架构方面
    • 传统2PC的RM是数据库自身,使用XA协议来实现
    • Seata的RM也是以jar包的形式作为中间件部署在应用程序
  • 2PC方面
    • 传统2PC的资源锁需要等到所有数据库都准备好了才能释放
    • Seata在Phase 1执行完毕之后立即释放资源锁,提升效率

TCC要求每个分支事务实现三个操作:Try-Confirm-Cancel

  • try - 业务检查与资源隔离
  • confirm - 只要所有数据库的try返回了ok,就confirm。且confirm被视为不会失败的。如果失败,需要手动重试或人工处理
  • cancel - 在try阶段只要有数据库没有返回ok,就cancel。释放资源。且cancel被视为不会失败的,如果失败,需要手动重试或人工处理

即每个服务都要有以上三个方法。所以分布式事务是在每个独立服务的业务逻辑上实现的,三个阶段中的事务都是独立的事务,执行完就可以在分支事务中confirm

事务管理器TM可以是独立的服务,也可以是全局事务发起方。confirm和cancel由于需要支持重试,所以需要幂等

需要注意的三种异常处理(TM没有处理好try/confirm/cancel的顺序或数量):

  • 空回滚 - 某个服务没有被try就被通知cancel。此服务的cancel方法需要能够识别这是空回滚并直接返回ok。通过在服务本地记录全局事务ID+分支事务ID来实现
  • 幂等 - TCC的三个操作都要幂等,来保证不会重复锁/释放资源。通过在分支事务记录中记录执行状态,每次执行TCC之前都检查状态来实现
  • 悬挂 - 服务先收到cancel再收到try,导致资源无法释放。解决方法是通过分支事务记录在try之前检查是否已经被cancel

例:银行系统

账户A的TCC三个方法为

  • try
    • 检查余额是否大于30
  • confirm
  • cancel

账户B的TCC三个方法为

  • confirm
  • cancel

为了实现异常处理,正确的操作是

  • try
    • 幂等校验,检查try是否已经被执行
    • 悬挂校验,检查是否已经被cancel
    • 检查余额是否大于30
  • confirm
  • cancel
    • 幂等校验,检查cancel是否已经被执行
    • 空回滚校验,检查try是否已经执行
  • try
    • 无操作。不应该在try阶段加30元,防止B花掉这30元
  • confirm
    • 幂等校验,检查confirm是否已经被执行
  • cancel

TCC的协议是固定的,有很多实现框架

  • tcc-transaction
  • Hmily
  • ByteTCC
  • EasyTransaction
  • Seata(TCC模式对SpringCloud没有提供支持)

Hmily

  • 轻量,不需要独立的TM,但是需要一个数据库(或文件)进行日志存储
  • 支持嵌套事务
  • 基于AOP拦截方法,实现try-confirm-cancel和重试

可靠消息最终一致性

需要解决的问题:

  • 消息发送方的本地事务和消息发送的原子性。确保消息的发送和本地事务能够同时commit或cancel
  • 消息的接收是可靠的
  • 重复消息问题。需要保证消费操作的幂等性,需要自行在业务逻辑实现

本地消息表

最初由eBey提出

分布式事务的发起方需要保存一个消息日志表,MQ定时扫描事务发起方的日志,获得消息之后通知消费者。消费者执行完毕后通知MQ删除消息(ACK)

  • 事务发起方的本地事务保证了原子性
  • ACK保证了消息一定被消费了

RocketMQ

是阿里巴巴的分布式消息中间件。自带“事务消息”,是本地消息表在MQ的封装

  1. MQ发送方向MQ Server发送Half消息(即事务消息)
  2. MQ Server返回成功。此时此消息无法被消费
  3. MQ发送方执行本地事务
  4. MQ发送方把本地事务结果告知到MQ Server(commit or rollback)
  5. 如果MQ Server接收到commit,才会设置消息为“可消费”
  6. 如果MQ Server没有收到commit/rollback,则会主动询问MQ发起方,MQ发起方可以查询本地事务以判断事务是否已经执行

最大努力通知

事务发起方完成本地事务后告诉客户端“正在执行”,然后尽最大努力通知事务接收方。事务发起方一直重试(但是可以使用退避策略,比如第一次失败后等1分钟,第二次失败后等2分钟。。。)。除此之外,消息接收方可以对消息发起方的事务状态进行主动查询(需要提供查询接口)

  • 消息重试机制

不可靠,通常不用于交易,而是用于交易之后的通知事务

可以使用RocketMQ的普通消息来实现

  • 2PC
    • 数据库原生支持
    • 阻塞,锁资源的时间会比较长,不适用于高并发
    • 简单好实现
  • TCC
    • 在应用层处理而不是在数据库层。程序需要写三个阶段的对应方法
    • 三个阶段都是独立的本地事务,应用程序可以自行定义锁的粒度,从而降低锁冲突,提升吞吐
    • 对应用的侵入性较强,实现难度高
  • 可靠消息最终一致性
    • 适用于周期长且实时性要求不高的场景
    • 异步操作避免阻塞
  • 最大努力通知
    • 实时性要求不高
    • 适用于各种通知的场景(充值结果、支付结果。。。)
2PC TCC 可靠消息 最大努力 一致性 强一致性 最终一致性 最终一致性 最终一致性 吞吐量 低 中 高 高 实现复杂度 易 难 中 易

Saga事务

把一个长事务拆分为多个短事务,由一个协调器控制分别执行。如果执行失败就逆序执行短事务的恢复操作。所有短事务不需要任何try的阶段,直接执行即可

缺点:短事务之间没有锁资源,需要在业务层面实现锁

三个阶段:CanCommit, PreCommit, DoCommit。在CanCommit阶段判断数据库是否OK,在PreCommit阶段执行局部事务并锁资源、写log,最后在DoCommit阶段提交事务


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK