分布式事务和共识

102 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情

本系列主要是《数据密集型应用系统设计》阅读笔记,本文记录分布式事务主题的笔记心得。

共识问题是分布式计算中最重要也是最基本的问题之一,表面上看,只是让几个节点就某件事情达成一致。这似乎很简单,但是许多失败的系统正是由于低估了这个问题所导致的。 在探讨了复制、事务、系统模型、线性化和全序关系广播等问题后,现在终于完成必要的准备,可以开始直面共识问题了。

场景

很多重要的场景需要节点达成某种一致,比如

  • 主节点选举
  • 原子事务提交 下面主要讲共识场景的原子事务提交。

原子事务提交和两阶段提交

原子事务的概念大家都很清楚了,要么成功要么终止。原子性可以防止失败的事务破坏系统,避免形成部分成功夹杂着部分失败。比如原子性可以保证二级索引和主数据保持一致

从单节点到分布式的原子提交

单个节点的事务通常由存储引擎负责,存储引擎会使得事务写入持久化

  • 预先写入事务
  • 追加写入日志 如果在该过程奔溃了,那么当节点重启后,事务可以从日志中恢复。如果奔溃之前提交记录已经写入磁盘则认为事务安全提交,否则回滚该事务的所有写入。 这就是在单一设备上实现原子提交的核心思路。

两阶段提交

两阶段提交是一种在多节点之间实现分布式原子提交的算法,用来确保所有节点要么全部提交要么全部中止。2PC在某些数据库内部使用,或者以XA事务形式提供给应用程序。 2PC的基本流程如下:

2pc2.png

  • 协调者使用事务ID,询问参与者是否prepare
  • 参与者使用事务ID,执行事务(尝试获取数据库锁)但不提交,并返回ready(确保可以提交事务,表态后不会反悔)
  • 协调者把结果存储到磁盘防止稍后系统奔溃,这个时刻称为提交点

2pc3.png

  • 协调者发起commit,如果请求失败或超时则一直重试直到成功。
  • 参与者A和B提交,并返回OK

可以看出该协议有两个不归路:

  1. 参与者投票‘是’,则做出了提交的承诺
  2. 协调者做出了提交的决定,这个决定也是不可以撤销的 正是这两个承诺保证了2PC的原子性。

故障

准备阶段,任何一个准备请求发生超时或失败则中止交易。但是如果在协调者做出了提交的决定后发生了故障,接下来发生什么还不太清楚,目前看来参与者只能等待。 如下图:

image.png

  • 该例子中,协调者实际做出了提交的决定。
  • 数据库2收到了提交的请求,于是提交
  • 数据库1没有收到提交请求,不知道该提交还是中止,即使超时也无法决定:
    • 如果决定中止,则和数据库2不一致
    • 如果单方面提交,万一有参与者投了否决票呢?

总之,2PC能够顺利完成的唯一方法是等待协调者恢复。

实践

所以实践中的2PC声誉混杂。一方面,被认为是一个其他方案难以企及的安全保证;另一方面,由于操作上的缺陷、性能问题、承诺不可靠等问题而遭受诟病。因为运维方面的问题很多云厂商不支持分布式事务。

异构的分布式

数据库内部事务由于不用考虑和其他系统的兼容,可以使用内部协议并采取措施支持分布式事务。但异构环境的则充满了挑战。

  • exactly-once消息处理 比如数据库和消息队列,可以比较轻松做到exactly-once消息处理。当且仅当数据库处理消息的事务成功提交,消息队列才会标记消息为ack。但是如果有副作用的一些处理比如发邮件,exactly-once消息处理也是不安全的。
  • XA交易 XA是异构环境实施两阶段提交的一个工业标准。目前很多关系数据,比如MYSQL和消息队列,比如ActiveMQ等都支持XA。XA主要就是相应的API接口给应用程序使用。

参与者停顿时持有锁

停顿的参与者带来了很多麻烦,不知道该提交还是中止,这个时候参与者往往是持有锁的。如果协调者20分钟才恢复,那么锁的对象会锁定20分钟。则其他事务则无法修改,这会导致上层应用处于不可用状态,所以必须解决处于停顿状态的事务。

从故障中恢复

那么怎么让协调者尽早从故障中恢复呢?

  • 首先管理员参与进来,决定让参与者提交或中止。
  • 系统有紧急避险的措施,参与者单方面停止或提交(违反了2PC协议,只是为了应急)。
  • 协调者尽量支持复制,避免单点,尽早恢复。