背景
PolarDB-X是一个云原生的分布式数据库,整体架构如下,SQL的执行流程由CN节点进行接入进行分布式计算,底层的存储引擎负责数据库的持久化和一致性保障。有关PolarDB-X的历史可以参考PolarDB-X的诞生和发展这篇文章。其中DN节点利用自研一致性协议库X-Paxos,构建了高可用的存储集群,并且在阿里集团得到了全面的发展和验证,相关实践可以参考PolarDB-X 存储架构之“基于Paxos的最佳生产实践”这篇文章。本文主要对DN节点的一致性协议进行概要介绍和进行相关的源码导读,同时提供最小化的搭建案例。

整体架构
先来看一下PolarDB-X存储节点的基本架构:

上图展示的是一个部署三个实例的PolarDB-X存储集群。PolarDB-X存储集群是一个单点写入,多点可读的集群系统。在同一时刻,整个集群中至多会有一个Leader节点来承担数据写入的任务。PolarDB-X存储节点的每个实例都是一个单进程的系统,X-Paxos被深度整合到了数据库内核之中,替换了原有的复制模块。集群节点之间的增量数据同步通过X-Paxos来驱动完成,不再需要外部手动指定复制位点。PolarDB-X存储节点为了追求最高的性能,选择了自己实现Consensus日志这种接入X-Paxos的方式,在MySQL Binlog的基础上实现了一系列X-Paxos日志接口,赋予X-Paxos操纵Binlog的能力。

X-Paxos的整体架构如上图所示,主要可分为网络层、服务层、算法模块、日志模块4个部分。
- 网络层:基于阿里内部非常成熟的网络库libeasy实现。libeasy的异步框架和线程池非常契合我们的整体异步化设计。
- 服务层:服务层是驱动整个X-Paxos运行的基础,为X-Paxos提供了事件驱动,定时回调等核心的运行功能。
- 算法模块:基于强leadership的Multi-Paxos实现,在基础算法的基础上,结合阿里业务的场景以及高性能和生态的需求,X-Paxos做了很多的创新性的功能和性能的优化,使其相对于基础的Multi-Paxos,功能变的更加丰富,性能也有明显的提升。
- 日志模块:出于对极致性能要求的考虑,我们把日志模块独立出来,并实现了一个默认的高性能的日志模块,用户可以结合已有的日志系统,对接日志模块接口,以获取更高的性能和更低的成本。
角色区别
Paxos将系统中的角色分为提议者 (Proposer),决策者 (Acceptor),和最终决策学习者 (Learner):
- Proposer:负责提议Proposal。Proposal包含Proposal Number(简称PN)和Value两部分,一个PN=n且Value=v的Proposal记作Proposal{n, v}。
- Acceptor:负责决策,即根据自己所知的信息,回应Proposer发来的请求(下文将看到,有PrepareReq和AcceptReq两种请求)。
- Learner:不参与决策,从Proposers/Acceptors学习最新达成一致的提案(Value)。
在X-Paxos一致性协议中,将整个一致性协议的持久存储分两块:日志和状态机。日志代表了对状态机的更新操作,状态机存放了外部业务读写的实际数据。基于日志和状态机,与Paxos的角色定义不同,X-Paxos将系统中的角色分为Leader(主节点)、Follower(从节点)、Logger(日志节点)、Learner(只读节点)。
- Leader:主节点是集群的唯一写节点。它给集群所有节点发送新写入的日志,达成多数派后允许提交,并回放到本地的状态机。Leader节点的定义也是为了解决Paxos中活锁的问题,避免两个Proposer交替发起Prepare请求成功,而Accept失败引起的活锁(LiveLock)问题。
- Followe:从节点用于收集Leader发送的日志,并负责把达成多数派的日志回放到状态机。当Leader发生故障时,集群中的剩余节点会选一个新的Follower升级成Leader接受读写请求。
- Logger:日志节点是一种特殊类型的Follower,不对外提供服务。Logger做两件事:存储最新的日志用于Leader的多数派判定;选主阶段行使投票权。Logger不回放状态机,会定期清理老旧的日志,占用极少的计算和存储资源。因此,基于Leader/Follower/Logger的部署方式,三节点相比双节点高可用版,只额外增加很少的成本。
- Learner:只读副本的一种模式,这种模式下Leader节点没有投票权,不参加多数派的计算,仅从Leader同步已提交的日志,并回放到状态机。在实际使用中,我们把Learner作为只读副本,用于应用层的读写分离。此外,X-Paxos支持Learner和Follower之间的节点变更,基于这个功能可以实现故障节点的迁移和替换。
日志复制
首先回顾MySQL双节点高可用版本的复制模式。其中Master节点负责写入binary log,并提交事务。Slave节点通过IO线程从Master节点发起dump协议拉取binary log,并存储到本地的relay log中。最后由Slave节点的SQL线程负责回放relay log。双节点复制模式可以用下图表示:

一般情况下,Slave节点还需要开启log-slave-updates来保证从库也可以为下游提供日志同步,因此Slave线程除了relay log,还会有一份冗余的binary log。
PolarDB-X存储节点整合了binary log和relay log,实现了统一的consensus log,节省了日志存储的成本。当某个节点是Leader的时候,consensus log扮演了binary log的角色;同理当某个节点被切换成Follower/Learner时,consensus log扮演了relay log的角色。X-Paxos一致性协议层接管consensus log的同步逻辑,同时提供对外的接口来实现日志写入和状态机回放。新的consensus log基于一致性协议和State Machine Replication理论,保证了多个节点之间的数据一致性。此外,PolarDB-X存储节点日志的实现遵循了MySQL binary log的标准,可以无缝兼容aliyun DTS、Canal等业内常用的binlog增量订阅工具。
三节点复制模式如下图所示:

状态机
PolarDB-X存储节点的状态机实现改造了MySQL原有事务提交的流程。MySQL组提交(Group Commit)相关的技术文章网上有很多,原有Group Commit分为三个阶段:flush stage、sync stage、commit stage。对于Leader节点,PolarDB-X存储节点修改了其中commit stage的实现方式。所有进入commit stage的事务会被统一推送到一个异步队列中,进入quorum决议的判定阶段,等待事务日志同步到多数节点上,满足quorum条件的事务才允许commit。另外,Leader上consensus log的本地写入和日志同步可以并行执行,保证了高性能。对于Follower节点,SQL线程读取consensus log,开始等待Leader的通知。Leader会定期同步给Follower每一条日志的提交状态,达成多数派的日志会被分发给worker线程并行执行。
Learner节点相对Follower的逻辑更加简单,一致性协议保证了它不会接收到未提交的日志,SQL线程不用等待任何条件,只需分发最新的日志给worker线程即可。此外,PolarDB-X存储节点使用特殊版本的Xtrabackup进行实例备份和恢复。我们基于X-Paxos的snapshot接口改进了Xtrabackup,支持创建带有一致性位点的物理备份快照,可以十分快捷的孵化一个全新的Learner节点,并加入到集群中提供读能力的扩展。

源码导读
模块划分
X-Paxos一致性协议的代码在 https://github.com/polardb/polardbx-engine/tree/main/extra/IS/consensus路径,主要涵盖algorithm(算法模块)、net(网络处理)、protocol(协议处理)、服务管理(service)、uniteest(单元测试)等。

模块介绍
- algorithm目录为核心的一致性协议算法,X-Paxos当前的算法基于强leadership的Multi-Paxos实现,Multi-Paxos在写入过程中会先用Paxos协议选举出一个Leader,之后只由这个leader写入。核心的Paxos流程实现过程主要见paxos.cc。

- example目录为如果使用X-Paxos协议库的案例

- net目录为X-Paxos使用的libeasy协议的封装,libeasy是一个基于libev处理tcp连接的事件驱动的网络框架,框架本身封装好了底层的网络操作,只需要开发者处理其中的各种事件。libeasy也随着X-Paxos的开源一起开放了代码,主要代码见
- https://github.com/polardb/polardbx-engine/tree/main/extra/IS/dependency/easy/src

- protocol目录为X-Paxos协议通信的结构体,X-Paxos使用protocol buffer进行数据的结构化封装。

- service目录基于libeasy封装之后的client/service网络服务调用

service.cc主要包含对不同类型的消息的处理,包含RequestVote、AppendLog等,详细可以见Service::process函数

- unitetest目录为一致性协议相关的单元测试case

源码编译&部署测试
编译源码
详细源码编译按照步骤见文档
准备配置文件
为了支持三节点我们新增了对应的配置参数如下
仅限my.cnf和命令行启动参数
参数名
含义
cluster-learner-node
learner节点设为ON
cluster-log-type-node
日志节点设为ON
cluster-info
对于leader和follower节点,格式为ip1:port1;ip2:port2...@N,前面列出集群所有ip:port,并保证集群内所有节点配置一致。每个节点配置的区别在@N,N为当前节点在列表里的index,从1开始计。
对于learner节点,格式为ip:port。
可以搭配cluster-force-change-meta使用,订正consensus_info的cluster-info或cluster-learner-info信息。
注意1:这里的port是协议层的port,不是3306,是11306。
注意2:learner节点没有@N
注意3:通过命令行配置时加引号,即--cluster-info="ip1:port1;ip2:port2...@N"
举个例子:
三个节点ip分别为IP1,IP2,IP3
统一使用11306作为协议层port
那么三个节点的cluster-info的写法分别是:
IP1的my.cnf cluster-info=IP1:11306;IP2:11306;IP3:11306@1 IP2的my.cnf cluster-info=IP1:11306;IP2:11306;IP3:11306@2 IP3的my.cnf cluster-info=IP1:11306;IP2:11306;IP3:11306@3cluster-current-term
搭配cluster-force-change-meta使用,订正consensus_info的term信息。
cluster-force-recover-index
备份恢复时使用,主库备份依赖,从库备份会忽略该参数。
cluster-force-change-meta
强制刷新consensus_info表的数据,一般用于非标操作导致内部元数据错乱后的fix。只有命令行指定了cluster-force-change-meta,系统才会重新更新并持久化元数据,否则不会读取my.cnf里的cluster-info和cluster-current-term。
cluster-dump-meta
导出consensus_info表数据,存放在data目录下,文件名是“consensus.meta”。用于实例起不来的时候,排查元数据是否正确。用法类似于cluster-force-change-meta。
cluster-force-single-mode
强制单节点,等价于change consensus_node to consensus_force_single_mode的SQL。
cluster-mts-recover-use-index
并行复制使用index为位点依据,必须设为ON。
cluster-start-index
备份恢复时使用,备份恢复的节点没有旧的binlog日志。该index之前的日志都需要走mock逻辑,协议层在判定一致时可以不作校验。
根据以上参数准备三个节点的my.cnf文件参数如下
- cluster-id: 集群标识ID
- cluster-info: 根据自己的集群信息配置对应的IP和PORT,还有对应的节点标识
准备好原有的my.cnf文件,新增加三节点配置如下,对于不同的节点需要修改对应的INDEX值
# galaxyengine三节点相关配置 cluster-id=872339378 cluster-info=IP1:PORT1;IP2:PORT2;IP3:PORT3@INDEX consensus_io_thread_cnt=8 consensus_worker_thread_cnt=8 consensus_max_delay_index=10000 consensus_election_timeout=10000 consensus_max_packet_size=131072 consensus_max_log_size=20M cluster-mts-recover-use-index=ON #loose_async_commit_thread_count=128 #async_commit=OFF replicate-same-server-id=on #loose_commit_lock_done_count=1 binlog_order_commits=OFF #cluster-log-type-node=OFF初始化数据
配置好对应的配置文件之后进行初始化
mysqld --defaults-file=my.cnf --initialize-insecure --user=mysql
启动实例
分别启动多副本集群里的几个实例:
mysqld --defaults-file=my.cnf &启动完成之后查看对应的mysql日志,如果有以下选主成功的日志输出则代表集群创建成功
[2022-04-26 15:14:29.141505] [ERROR] Server 2 : Paxos state change from FOLL to CAND !! [2022-04-26 15:14:29.141505] [ERROR] Server 2 : Start new requestVote: new term(2) [2022-04-26 15:14:44.088529] [ERROR] Server 2 : Paxos state change from CAND to CAND !! [2022-04-26 15:14:44.088529] [ERROR] Server 2 : Start new requestVote: new term(3) [2022-04-26 15:14:58.821423] [ERROR] Server 2 : Paxos state change from CAND to CAND !! [2022-04-26 15:14:58.821423] [ERROR] Server 2 : Start new requestVote: new term(4) [2022-04-26 15:15:00.537734] [ERROR] EasyNet::onConnected server 1 [2022-04-26 15:15:13.165122] [ERROR] Server 2 : Paxos state change from CAND to CAND !! [2022-04-26 15:15:13.165122] [ERROR] Server 2 : Start new requestVote: new term(5) [2022-04-26 15:15:13.168488] [ERROR] Server 2 : server 1 (term:5) vote me to became leader. [2022-04-26 15:15:13.168488] [ERROR] Server 2 : Paxos state change from CAND to LEDR !! [2022-04-26 15:15:13.168488] [ERROR] Server 2 : become Leader (currentTerm 5, lli:1, llt:5)!!查看集群状态
使用mysql客户端链接三节点集群mysql -P端口 -uroot -h127.0.0.1,可以使用以下SQL查看集群的状态
select * from information_schema.alisql_cluster_global;
select * from information_schema.alisql_cluster_local;

如果要切换leader可以使用以下命令进行切换
call dbms_consensus.change_leader("IP:PORT");

总结
PolarDB-X融合了基于X-Paxos的数据库存储技术,通过经历阿里集团多年双十一的技术积累和稳定性验证,PolarDB-X在稳定性、易用性、高可用特性上都会有不错的表现。文章主要对X-Paxos一致性协议的整体架构、节点角色、集群管理、日志和状态机的机制进行了介绍,同时对源码进行了导读,后续我们还会深入介绍X-Paxos相关的源码技术。




