持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情
本系列主要是《数据密集型应用系统设计》阅读笔记,本文记录分布式事务主题的笔记心得。
共识问题是分布式计算中最重要也是最基本的问题之一,表面上看,只是让几个节点就某件事情达成一致。这似乎很简单,但是许多失败的系统正是由于低估了这个问题所导致的。 在探讨了复制、事务、系统模型、线性化和全序关系广播等问题后,现在终于完成必要的准备,可以开始直面共识问题了。
场景
很多重要的场景需要节点达成某种一致,比如
- 主节点选举
- 原子事务提交 下面主要讲共识场景的原子事务提交。
原子事务提交和两阶段提交
原子事务的概念大家都很清楚了,要么成功要么终止。原子性可以防止失败的事务破坏系统,避免形成部分成功夹杂着部分失败。比如原子性可以保证二级索引和主数据保持一致。
从单节点到分布式的原子提交
单个节点的事务通常由存储引擎负责,存储引擎会使得事务写入持久化
- 预先写入事务
- 追加写入日志 如果在该过程奔溃了,那么当节点重启后,事务可以从日志中恢复。如果奔溃之前提交记录已经写入磁盘则认为事务安全提交,否则回滚该事务的所有写入。 这就是在单一设备上实现原子提交的核心思路。
两阶段提交
两阶段提交是一种在多节点之间实现分布式原子提交的算法,用来确保所有节点要么全部提交要么全部中止。2PC在某些数据库内部使用,或者以XA事务形式提供给应用程序。 2PC的基本流程如下:
- 协调者使用事务ID,询问参与者是否prepare
- 参与者使用事务ID,执行事务(尝试获取数据库锁)但不提交,并返回ready(确保可以提交事务,表态后不会反悔)
- 协调者把结果存储到磁盘防止稍后系统奔溃,这个时刻称为提交点
- 协调者发起commit,如果请求失败或超时则一直重试直到成功。
- 参与者A和B提交,并返回OK
可以看出该协议有两个不归路:
- 参与者投票‘是’,则做出了提交的承诺
- 协调者做出了提交的决定,这个决定也是不可以撤销的 正是这两个承诺保证了2PC的原子性。
故障
准备阶段,任何一个准备请求发生超时或失败则中止交易。但是如果在协调者做出了提交的决定后发生了故障,接下来发生什么还不太清楚,目前看来参与者只能等待。 如下图:
- 该例子中,协调者实际做出了提交的决定。
- 数据库2收到了提交的请求,于是提交
- 数据库1没有收到提交请求,不知道该提交还是中止,即使超时也无法决定:
- 如果决定中止,则和数据库2不一致
- 如果单方面提交,万一有参与者投了否决票呢?
总之,2PC能够顺利完成的唯一方法是等待协调者恢复。
实践
所以实践中的2PC声誉混杂。一方面,被认为是一个其他方案难以企及的安全保证;另一方面,由于操作上的缺陷、性能问题、承诺不可靠等问题而遭受诟病。因为运维方面的问题很多云厂商不支持分布式事务。
异构的分布式
数据库内部事务由于不用考虑和其他系统的兼容,可以使用内部协议并采取措施支持分布式事务。但异构环境的则充满了挑战。
- exactly-once消息处理 比如数据库和消息队列,可以比较轻松做到exactly-once消息处理。当且仅当数据库处理消息的事务成功提交,消息队列才会标记消息为ack。但是如果有副作用的一些处理比如发邮件,exactly-once消息处理也是不安全的。
- XA交易 XA是异构环境实施两阶段提交的一个工业标准。目前很多关系数据,比如MYSQL和消息队列,比如ActiveMQ等都支持XA。XA主要就是相应的API接口给应用程序使用。
参与者停顿时持有锁
停顿的参与者带来了很多麻烦,不知道该提交还是中止,这个时候参与者往往是持有锁的。如果协调者20分钟才恢复,那么锁的对象会锁定20分钟。则其他事务则无法修改,这会导致上层应用处于不可用状态,所以必须解决处于停顿状态的事务。
从故障中恢复
那么怎么让协调者尽早从故障中恢复呢?
- 首先管理员参与进来,决定让参与者提交或中止。
- 系统有紧急避险的措施,参与者单方面停止或提交(违反了2PC协议,只是为了应急)。
- 协调者尽量支持复制,避免单点,尽早恢复。