PolarDB-X作为PolarDB分布式版,是阿里巴巴自主设计研发的高性能云原生分布式数据库产品,为用户提供高吞吐、大存储、低延时、易扩展和超高可用的云时代数据库服务。PolarDB-X在架构上可以简单分为CN节点和DN节点。计算节点CN负责SQL的解析和执行,存储节点DN负责数据的分布式事务和高可用存储。本文主要对存储引擎核心中高可用部分详细技术解读。
背景
传统的存储引擎高可用主要采用主备同步的方式,但是主备同步方式天然存在以下限制:
- 高性能与数据一致性的取舍:主备复制采用强同步方式会导致主库写入性能降低,采用异步方式无法保证主库宕机时备库无损提供一致性保证,半同步方式虽然能有效缓解性能降低,但同样无法提供严格的一致性保证
- 系统可用性:主备库方式在面临网络故障或延迟较大场景时,可能会存在主库迟迟无法提交事务,不能及时主备切换,从而影响系统的可用性
针对主备库方式的问题,当前业界主要采用基于多副本的高可用存储引擎方案。PolarDB-X同样基于该路线,自研了分布式一致性协议模块 X-Paxos,并将其与MySQL引擎深度结合,形成了PolarDB-X的多副本高可用存储引擎。
特性
下图是PolarDB-X多副本存储节点的基本架构,展示了一个部署三个副本的PolarDB-X DN集群。PolarDB-X DN集群是一个单点写入,多点可读的集群系统。在同一时刻,整个集群中至多会有一个Leader节点来承担数据写入的任务。PolarDB-X DN节点的每个实例都是一个单进程的系统,X-Paxos被深度整合到了数据库内核之中,替换了原有的复制模块。集群节点之间的增量数据同步通过X-Paxos来驱动完成,不再需要外部手动指定复制位点。
总的来说,PolaDB-X多副本存储引擎具备以下特性:
- 强一致性保障:X-Paxos基于强Leadership的Multi-Paxos实现,大量理论和实践已经证明了强Leadership的Multi-Paxos,性能好于Multi-Paxos/Basic-Paxos。PolarDB-X在崩溃恢复时的一致性问题上更是做了深入的保障
- 高性能的数据同步:具备Batching/Pipelining方式的高效日志传输,具备多线程异步的高效数据传输,具备X-Paxos日志和Binlog融合的极致精简统一
- 灵活的运维容灾:在线添加/删除/权重化配置任意节点,具备手动/自动选主的灵活策略
- 低成本的数据存储:创新引入Logger角色副本,让三副本具备最低两份存储的数据存储开销
下面针对以上特性分别进行深入解读。
原理
强一致性保障
强一致性保障主要依赖X-Paxos模块,上图展示的X-Paxos整体架构,整体可分为网络层、服务层、算法模块、日志模块4个部分:
- 网络层:基于libeasy网络库实现。libeasy的异步框架和线程池非常契合我们的整体异步化设计,同时我们对libeasy的重连、日志等逻辑进行了修改,以适应分布式协议的需求
- 服务层:驱动整个Paxos运行的基础,为Paxos提供了事件驱动,定时回调等核心的运行功能。每一个paxos实现都有一个与之紧密相关的驱动层,驱动层的架构与性能和稳定性密切相关。
- 算法模块:一致性协议的核心,包括基于强Leadership的Multi-Paxos实现的选主模块,采用预读缓冲区/热缓冲区的数据管理模块,复用Enhanced Multi-Threaded Slave技术的同步传输应用模块,以及扩展sync_relay_log_info系统表的集群元数据模块。
- 日志模块,原本是算法模块的一部分,出于对极致性能要求的考虑,我们把日志模块独立出来,并实现了一个默认的高性能的日志模块。
如果说算法模块是运行时强一致性的保证,那么日志模块就是容灾时的强一致性保证。 日志模块和Binlog极致耦合,同时也是维护崩溃恢复逻辑的关键部分。Paxos多副本强一致算法网上介绍很多,这里不再阐述。我们重点说明下PolarDB-X中的日志模块实现。
日志模块
在原有的MySQL主备复制模式中,Master节点负责写入binary log,并提交事务。Slave节点通过IO线程从Master节点发起dump协议拉取binary log,并存储到本地的relay log中。最后由Slave节点的SQL线程负责回放relay log。 默认情况Slave节点除了产生relay log,还会根据log-slave-updates有一份冗余的binary log。
PolarDB-X存储节点整合了binary log和relay log,实现了统一的consensus log,节省了日志存储的成本。当某个节点是Leader的时候,consensus log扮演了binary log的角色;同理当某个节点被切换成Follower/Learner时,consensus log扮演了relay log的角色。新的consensus log基于一致性协议和State Machine Replication理论,保证了多个节点之间的数据一致性。
日志模块也接管consensus log的同步逻辑,取消Relay IO线程,复用Relay SQL线程和Relay Worker线程,支持MTS多线程并行回放。同时提供对外的接口来实现日志写入和状态机回放。
根据算法模块和Binlog的依赖,日志模块引入了四种日志类型:
- Consensus Log Event:标记在每个事务的Anonymous_GTID日志之前,用于记录当前事务对应协议层index、term、flag等元信息
- Previous Consensus Index Event:写在每个binary log文件的最开始,Format_description events之后,Previous_gtids events之前,用于标记当前文件起始的事务index
- Consensus Cluster Info Log Event:记录多副本集群配置变更的信息
- Consensus Empty Log Event:记录X-Paxos协议选主日志
PolarDB-X日志模块同样改造了MySQL原有事务提交的流程。MySQL的Group Commit分为三个阶段:flush stage、sync stage、commit stage。对于多副本的Leader节点,PolarDB-X DN节点在 Binlog 的 Flush 和 Sync 过程中将携带Consensus Log Event的Binlog 内容同时广播到所有 Follower。所有进入commit stage的事务会被统一推送到一个异步队列中,进入quorum决议的判定阶段,等待事务日志同步到多数节点上,满足quorum条件的事务才允许commit,以保证数据的强一致。
此外,为了保证高性能,Leader上consensus log的本地写入和日志同步可以并行执行对于Follower节点,SQL线程读取consensus log,等待Leader的通知。Leader会定期同步给Follower每一条日志的提交状态,达成多数派的日志会被分发给worker线程并行执行。
在现实应用场景中,Follower 和 Leader 的状态机难免会存在回放延迟,比如一个大的 DDL 会导致 Follower 的回放延迟被无限放大,而如果在回放延迟存在的情况下 Leader 挂掉新主选出时,新主无法对外提供服务,而此时老 Leader 可能已经重启恢复,所以在这种情况下 X-Paxos 会主动探测状态机的健康状况,如果在一段时间内回放延迟无法追平,则会尝试 Leader 主动回切,让没有回放延迟的老 Leader 对外提供服务。
恢复逻辑
PolarDB-X的多副本存储引擎的恢复流程和原生MySQL大不相同,不同在Binlog参与的恢复流程,需要X-Paxos协议层参与,并且需要重点解决原生MySQL XA 的不完整性问题。
PolarDB-X的多副本存储引擎的恢复的流程如下:
1. 扫描Binlog文件
-
- 扫描consensus日志的完整性, 不完整的日志需要整体切除。
- 收集记录完整提交Binlog的事务,构建提交事务的hash表
- 确认最后一次实例存活时产生的Binlog文件。以用作恢复悬挂事务的依据
- 必须保证所有悬挂的事务都是在最后一个Binlog中,即使发生过切主
- relay log 也必须保证没有悬挂事务, 因为这里会涉及到前一个作为主的任期的悬挂事务
2. 扫描InnoDB,获得所有的悬挂事务, 如果不在提交事务hash表,那么回滚
3. 等待X-Paxos启动,推进回放位点,根据回放位点确定悬挂事务的是否提交
a. 如果回放之前节点是处于Binlog WORKING 状态,并且协议层也是leader role(leader 宕机恢复),则意味着协议层没有降级,那么可以判断为是在写入的状态下宕机恢复, 此时需要从系统表的last_leader_term 字段中根据最后是leader的term来遍历日志, 从此term最后一条日志后开始作为回放起点。
b. 如果回放前节点是处于Binlog WORKING 状态,协议层已经不是leader role(协议leader降级,或者是降级后server状态未变更时宕机),则意味着协议层已经降级,在降级的时候把commit index 写入了start_apply_index。 此时就从start_apply_index后面的一条日志开始回放。
c. 如果回放前节点是处于RELAY LOG WORKING状态,协议层也是非leader(每次重启非leader的回放线程),则这意味着此实例一直是一个非leader, 那么找到slave_relay_log_info 表中记录的consensus_apply_index 作为回放位点
d. 如果回放前节点是处于RELAY LOG WORKING状态,但是协议层已经切换到了leader (协议follower升级,或者是升级过程中server状态未变更时宕机),则这意味着此实例一直是一个非leader, 那么找到slave_relay_log_info 表中记录的consensus_apply_index 作为回放位点
4. 启动apply 线程,完成恢复流程
5. 打开MySQL监听端口
MySQL XA 实现了一套两阶段提交协议,以便在分布式事务系统中使用,但原生的 MySQL XA 存在完整性问题,假如在分布式场景中,Node1, Node2 ... NodeN 作为节点参与方,XA 使用 2PC 保证分布式事务的原子性,但对于每个节点中的 Binlog Storage 和 InnoDB Storage 两个参与方,MySQL 目前没有机制保证在每个阶段(Prepare 或者 Commit) 不同 Storage 参与方之间的一致性,这也源于Binlog Storage 本身没有 UNDO 的机制保证有关,这样 2PC 的崩溃恢复协议也就无法获得有效的节点事务状态。
为了保证严格的强一致性保证,PolarDB-X多副本存储引擎除了使用两阶段提交协议的 External Coordinator 以外,引入了使用 GTID 补偿协议的 Internal Coordinator 来保证 Storage 之间的一致性。
GTID 补偿协议的主要工作原理:
- 在单个节点崩溃恢复阶段,根据 Binlog File 构建 Binlog GTID 集合;
- 在单个节点崩溃恢复阶段,根据 InnoDB GTID_EXECUTED 以及 TRANSACTION UNDO HISTORY 构建 InnoDB GTID 集合
- 构建集合的差集,使用 Binlog Event 进行补偿 InnoDB 丢失的事务,恢复单个节点
- 完成单个节点的补偿后,XA 组件开始分布式集群的两阶段提交事务的崩溃恢复
高性能数据同步
高吞吐的日志传输
X-Paxos针对高延迟网络做了大量的协议优化尝试和测试,并结合学术界现有的理论成果通过合理的Batching和Pipelining,设计并实现了一整套自适应的针对高延迟高吞吐和低延迟高吞吐网络的通信模式,极大地提升了X-Paxos的性能。
Batching是指将多个日志合并成单个消息进行发送;Batching可以有效地降低消息粒度带来的额外损耗,提升吞吐。但是过大Batching容易造成单请求的延迟过大,导致并发请求数过高,继而影响了吞吐和请求延迟。
Pipelining是指在上一个消息返回结果以前,并发地发送下一个消息到对应节点的机制,通过提高并发发送消息数量(Pipelining数量),可以有效的降低并发单请求延迟,同时在transmission delay小于propagation delay的时候(高延迟高吞吐网络),有效提升性能。
R为网络带宽,D为网络传播延迟(propagation delay,约为RTT/2),经推导可知 Batching(消息大小:M)和Pipeling(消息并发:P)在`M/R * P = D`关系下,达到最高吞吐。
X-Paxos结合以上理论,通过内置探测,针对不同节点的部署延迟,自适应地调整针对每个节点的Batching和Pipeling参数,达到整体的最大吞吐。因Pipeling的引入,需要解决日志的乱序问题,特别是在异地场景下,window加大,加大了乱序的概率。X-Paxos实现了一个高效的乱序处理模块,可以对底层日志实现屏蔽乱序问题,实现高效的乱序日志存储。
高效的数据传输
由于Paxos的内部状态复杂,实现高效的单实例多线程的Paxos变成一个非常大的挑战。比如开源产品phxpaxos、Oracle MySQL Group Replication中使用的xcom,都是单线程的实现。phxpaxos采用了单分配单线程,多实例聚合的方式提升总吞吐,但是对单分区的性能非常的有限;xcom是一个基于协程的单线程实现。单线程的Paxos实现,在处理序列化/反序列化,分发、发包等逻辑的时候都为串行执行,性能瓶颈明显。
X-Paxos完全基于多线程实现,可以在单个分区Paxos中完全地使用多线程的能力,所有的任务都由通用的worker来运行,消除了CPU的瓶颈。依赖于服务层的多线程异步框架和异步网络层,X-Paxos除了必要的协议串行点外,大部分操作都可以并发执行,并且部分协议串行点采用了无锁设计,可以有效利用多线程能力,实现了Paxos的单分片多线程能力,单分区性能远超竞品,甚至超过了竞品的多实例性能。
融合的日志模块
X-Paxos和现有的大部分Paxos库很大的不同点就是X-Paxos支持可插拔的日志模块。日志模块是Paxos中一个重要的模块,它的持久化关系到数据的一致性,它的读写性能很大程度上会影响协议整体的读写性能。当前大部分独立Paxos库都是内置日志模块,并且不支持插拔的。这会带来2个弊端:
- 默认的日志模块提供通用的功能,很难结合具体的系统做针对性的优化
- 现有的系统往往已经存在了WAL(Write Ahead Log),而Paxos协议中需要再存一份。这使得
- 单次commit本地需要sync 2次,影响性能
- 双份日志浪费了大量的存储,增加了成本
PolarDB-X存储节点整合了binary log和relay log,实现了统一的consensus log,既节省了日志存储的成本,又提高了性能。日志融合的实际改动,前面强一致性保障中已经详细描述,这里不再展开。
灵活的运维容灾
在线添加节点/删除节点/切主
X-Paxos在标准Multi-Paxos的基础上,支持在线添加/删除多种角色的节点,支持在线快速将leadership节点转移到其他节点(有主选举)。这样的在线运维能力,将会极大地方便分布式节点的有计划性的运维工作,将RTO降低到最低。
语法 | 含义 |
call dbms_consensus.change_leader("127.0.0.1:15700") | 将 Leader 切换到指定节点 |
call dbms_consensus.upgrade_learner("127.0.0.1:15700") | 将指定 Learner 升级成 Follower |
call dbms_consensus.downgrade_follower("127.0.0.1:15700") | 将指定 Follower 降级成 Learner |
call dbms_consensus.add_follower("127.0.0.1:15700") | 加 Follower,内部会先加为 Learner,追平日志之后自动转成 Follower; |
call dbms_consensus.add_learner("127.0.0.1:15700") | 将指定节点以 Learner 身份添加到集群 |
call dbms_consensus.drop_learner("127.0.0.1:15700") | 将指定节点以 Learner 身份剔除出集群 |
策略化多数派/权重化选主
目前阿里多地架构会有中心机房的诉求,比如:应用因其部署的特点,往往要求在未发生城市级容灾的情况下,仅在中心写入数据库,数据库的leader节点在正常情况下只在中心地域;同时又要求在发生城市级容灾的时候(同一个城市的多个机房全部不可用),可以在完全不丢失任何数据的情况下,将leader点切换到非中心。
而经典的Multi-Paxos并不能满足这些需求。经典理论中,多数派强同步以后即可完成提交,而多数派是非特定的,并不能保证某个/某些节点一定能得到完整的数据,并激活服务。在实际实现中,往往地理位置较近的节点会拥有强一致的数据,而地理位置较远的节点,一直处于非强一致节点,在容灾的时候永远无法激活为主节点,形同虚设。 同时当中心单节点出现故障需要容灾的时候,往往需要将主节点就近切换到同中心的另外一个节点,而经典理论中同样没有类似的功能。
PolarDB-X在X-Paxos协议中实现了策略化多数派和权重化选主:
- 基于策略化多数派,用户可以通过动态配置,指定某个/某些节点必须保有强一致的数据,在出现容灾需求的时候,可以立即激活为主节点
- 基于权重化选主,用户可以指定各个节点的选主权重,只有在高权重的节点全部不可用的时候,才会激活低权重的节点
低成本的数据存储
在经典的Multi-Paxos实现中,一般每个节点都包含了Proposer/Accepter/Learner三种功能,每一个节点都是全功能节点。但是某些情况下我们并不需要所有节点都拥有全部的功能,例如: 1. 经典的三个副本部署中,我们可以裁剪其中一个节点的状态机,只保留日志(无数据的纯日志节点,但是在同步中作为多数派计算),此时我们需要裁剪掉协议中的Proposer功能(被选举权),保留Accepter和Learner功能。 2. 我们希望可以有若干个节点可以作为下游,订阅/消费协议产生的日志流,而不作为集群的成员(不作为多数派计算,因为这些节点不保存日志流),此时我们裁剪掉协议的Proposer/Accepter功能,只保留Learner功能。
多副本的技术虽然比主备同步的方式更加高性能和高可用,但多副本本身引入的多份数据存储开销难以避免的痛点。PolarDB-X多副本存储引擎深入研究理解协议发现,在经典的Multi-Paxos实现中,一般每个节点都包含了Proposer/Accepter/Learner三种功能,每一个节点都是全功能节点。但是某些情况下我们并不需要所有节点都拥有全部的功能,例如:
- 经典的三个副本部署中,我们可以裁剪其中一个节点的状态机,只保留日志(无数据的纯日志节点,但是在同步中作为多数派计算),此时我们需要裁剪掉协议中的Proposer功能(被选举权),保留Accepter和Learner功能。
- 我们希望可以有若干个节点可以作为下游,订阅/消费协议产生的日志流,而不作为集群的成员(不作为多数派计算,因为这些节点不保存日志流),此时我们裁剪掉协议的Proposer/Accepter功能,只保留Learner功能。
PolarDB-X创新地引入Learner/Logger角色副本,通过对节点角色的定制化组合,我们可以开发出很多的定制功能节点,既节约了成本,又丰富了功能。
典型的三节点复制模式如下图所示。通过对节点角色的定制化组合,在三副本部署,一个节点继续作为全功能副本Leader,一个节点继续作为Follower,另外一个Follower节点配置为仅做Logger角色,这样可以将三副本的三份数据成本优化为只有两份,同时不影响高可用的保障。再借助PolarDB-X完整兼容的MySQL Binlog,可以无缝兼容OSS、DTS、Canal等业内常用的Binlog增量订阅工具,实现数据的异地低成本备份。
作者:冷香、严华
点击立即免费试用云产品 开启云上实践之旅!
本文为阿里云原创内容,未经允许不得转载。