众所周知,MySQL主备库(两节点)一般通过异步复制、半同步复制(Semi-Sync)来实现数据高可用,但主备架构在机房网络故障、主机hang住等异常场景下,HA切换后大概率就会出现数据不一致的问题(简称RPO!=0),因此但凡业务数据有一定的重要性,都不应该选择MySQL主备架构(两节点)的数据库产品,推荐选择RPO=0的多副本架构。
MySQL社区,对于RPO=0的多副本技术演进:
PolarDB-X集中分布式一体化的理念:数据节点DN可以被独立出来作为集中式(标准版)形态,完全兼容单机数据库形态。当业务增长到需要分布式扩展的时候,架构原地升级成分布式形态,分布式组件无缝对接到原有的数据节点,不需要数据迁移,也不需要应用侧做改造,即可享受分布式带来的可用性与扩展性,架构说明:《集中分布式一体化》
MySQL的MGR和PolarDB-X的标准版DN,两者从最底层的原理上都采用了Paxos协议,那么在实际使用上,具体的表现和差异如何呢?本文从架构对比、关键差异、测试对比方面进行分别详细阐述。
MGR/DN简称说明:MGR代表MySQL MGR的技术形态、DN代表PolarDB-X 单DN集中式(标准版)的技术形态。
详细对比分析比较长,可以先看一下总结和结论,有兴趣的话可以顺着总结在后续文章中找一下线索。
MySQL MGR,一般的业务和公司都不建议使用,因为需要有专业的技术知识和运维团队才有机会用好,本文也复现了MySQL MGR三个业界流传已久的"暗坑":
PolarDB-X Paxos相比于MySQL MGR,在数据一致性、跨机房容灾、节点运维上都没有MGR类似的坑,但也有个别小缺点、以及容灾上的优点:
MGR/DN简称说明:
MGR 支持单主、多主模式,完全复用 MySQL 的复制系统,包括 Event、Binlog & Relaylog 、Apply、Binlog Apply Recovery、GTID。与DN的关键区别在于 MGR 事务日志多数派达成一致的切入点在主库事务提交之前。
MGR采用上述流程的原因,是因为MGR默认是多主模式,每个节点都可以写,所以单个Paxos Group内Follower节点需要将接受的日志先转成RelayLog,然后结合自身作为Leader接收的写事务提交,在两阶段组提交流程中生产Binlog文件提交最终事务。
DN 复用了 MySQL的基础数据结构和函数级代码,但是将日志复制、日志管理、日志回放、崩溃恢复环节跟X-Paxos 协议紧密结合在了一起,形成了自己的一套多数派复制和状态机机制。与MGR的关键区别在于 DN 事务日志多数派达成一致的切入点在主库事务提交过程中。
这种设计的原因是,DN当前只支持单主模式,所以X-Paxos 协议层面的日志就是 Binlog 本身,Follower也省去了 Relay Log,其持久化的日志和 Leader 等的日志的数据内容是等价相同的。
MGR
DN
理论上 Paxos 和 Raft 都可以保证数据一致性,以及 Crash Recovery 后已经达成多数派的日志不丢失,但是在具体工程上还是有区别。
MGR
XCOM 完全封装了 Paxos 协议,而它的所有协议数据又都是先缓存在内存中,默认情况事务达成多数派不要求日志持久化。 在多数派宕机、Leader故障的情况下,在会有 RPO != 0严重问题。假设一极端场景:
社区默认参数下,事务达成多数派不要求日志持久化,不保证RPO=0,可以认为是XCOM工程实现中为了性能的取舍。要想保证绝对的RPO=0, 需要将控制读写一致性的参数group_replication_consistency配置为AFTER,但这样的话,事务达成多数派除了需要1.5个RTT网络开销外,还需要一次日志IO开销,性能会很差。
DN
PolarDB-X DN采用X-Paxos实现分布式协议, 与MySQL的Group Commit流程深度绑定,在一个事务进行提交时,强制要求多数派确认落盘持久化后,才能允许真正提交。这里多数派落盘是指主库的Binlog落盘,备库的IO线程接收到主库的日志并落盘持久化到自己Binlog中。因此即使在极端场景下所有节点故障,数据也不会丢失,也能保证RPO=0。
RTO时间与系统本身冷重启的时间开销密切相关,反映到具体基础功能上,就是:故障检测机制->崩溃恢复机制->选主机制->日志追平
MGR
DN
MGR
DN
单主模式下, MGR的XCOM和DN X-Paxos 这种强 Leader 模式的选主,所遵循的基本原则是一样的--集群已共识的日志不能回退。 但是到未共识的日志时,存在差异
MGR
DN
日志追平是指主备之间日志存在日志复制延迟,备库需要追平日志。对于重启恢复的节点,通常都是以备库开始恢复,并且已经和主库产生了比较的的日志复制延迟,需要向主库追平日志。对于那些和 Leader 物理距离较远的节点,多数派达成通常和它们没关系,它们总是存在复制日志延迟一直在追平日志。这些情况都需要具体工程实现来确保日志复制延迟的及时解决。
MGR
DN
备库回放延迟是同一个事务在主库完成提交的时刻与在备库完成事务应用的时刻之间的延迟,这里考验的是备库Apply应用日志的性能。它影响的是异常发生时,备库成为新主后需要多久才能自身数据应用完毕可以提供读写服务。
MGR
DN
大事务不仅影响普通事务提交,在分布式系统中还影响整个分布式协议运行的稳定性,严重情况下一个大事务就会导致整个集群较长时间的不可用。
MGR
DN
MGR
DN
MGR | DN | ||
协议效率 | 事务提交耗时 | 1.5~2.5 RTT | 1个 RTT |
多数派持久化方式 | XCOM内存保存 | Binlog持久化 | |
可靠性 | RPO=0 | 默认不保证 | 完全保证 |
故障检测 | 所有节点相互检查,网络负载高 心跳周期无法调整 | 主节点周期检查其他节点 心跳周期参数可调 | |
多数派崩溃恢复 | 人工介入 | 自动恢复 | |
少数派崩溃恢复 | 大部分自动恢复,特殊情况人工介入 | 自动恢复 | |
选主 | 自由指定选主顺序 | 自由指定选主顺序 | |
日志追平 | 落后日志不能超过XCOM 1GB缓存 | BInlog文件不删除 | |
备库回放延迟 | 两阶段+双一,很慢 | 一阶段+双零,较快 | |
大事务 | 默认限制不超过143MB | 无大小限制 | |
形态 | 高可用成本 | 全功能三副本,3份数据存储开销 | Logger日志副本,2份数据存储 |
只读节点 | 借助主从复制实现 | 协议自带Leaner只读副本实现 |
MGR是MySQL 5.7.17开始引入的,但更多MGR相关特性都只在MySQL 8.0上才有,并且在MySQL 8.0.22及之后的版本,整体会更稳定可靠。因此我们选择双方的最新版8.0.32版本进行对比测试。
考虑到PolarDB-X DN和MySQL MGR在对比测试时,存在测试环境差异、编译方式差异、部署方式差异、运行参数差异、测试手段差异,进而可能导致测试对比数据不准确,本文在各项细节上采用如下方式进行:
测试准备 | PolarDB-X DN | MySQL MGR[1] |
硬件环境 | 使用同一台96C 754GB内存的物理机,SSD磁盘 | |
操作系统 | Linux 4.9.168-019.ali3000.alios7.x86_64 | |
内核版本 | 使用基于社区8.0.32版本的内核基线 | |
编译方式 | 使用相同的RelWithDebInfo编译 | |
运行参数 | 使用相同的PolarDB-X官网售卖32C128G规格相同参数 | |
部署方式 | 单主模式 |
注:
性能测试是大家在选型数据库时首先关注的一点。这里我们使用官方sysbench工具,构建16张表,每张表1千万数据,进行OLTP场景的性能测试,测试对比不同OLTP场景下不同并发时两者的表现。考虑到实际部署的不同情况, 我们分别模拟下面4种部署场景
说明:
a. 考虑4个部署场景性能的横向对比,两地三中心、三地三中心都采用3副本的部署模式,真实生产业务可以扩展到5副本的部署形态。
b. 考虑到实际使用高可用数据库产品时对RPO=0的严格限制,但是MGR默认配置RPO<>0,这里在各部署场景下,我们继续增加MGR RPO<>0和RPO=0的对比测试。
1 | 4 | 16 | 64 | 256 | ||
oltp_read_only | MGR_1 | 688.42 | 2731.68 | 6920.54 | 11492.88 | 14561.71 |
MGR_0 | 699.27 | 2778.06 | 7989.45 | 11590.28 | 15038.34 | |
DN | 656.69 | 2612.58 | 7657.03 | 11328.72 | 14771.12 | |
MGR_0 vs MGR_1 | 1.58% | 1.70% | 15.45% | 0.85% | 3.27% | |
DN vs MGR_1 | -4.61% | -4.36% | 10.64% | -1.43% | 1.44% | |
DN vs MGR_0 | -6.09% | -5.96% | -4.16% | -2.26% | -1.78% | |
oltp_read_write | MGR_1 | 317.85 | 1322.89 | 3464.07 | 5052.58 | 6736.55 |
MGR_0 | 117.91 | 425.25 | 721.45 | 217.11 | 228.24 | |
DN | 360.27 | 1485.99 | 3741.36 | 5460.47 | 7536.16 | |
MGR_0 vs MGR_1 | -62.90% | -67.85% | -79.17% | -95.70% | -96.61% | |
DN vs MGR_1 | 13.35% | 12.33% | 8.00% | 8.07% | 11.87% | |
DN vs MGR_0 | 205.55% | 249.44% | 418.59% | 2415.07% | 3201.86% | |
oltp_write_only | MGR_1 | 761.87 | 2924.1 | 7211.97 | 10374.15 | 16092.02 |
MGR_0 | 309.83 | 465.44 | 748.68 | 245.75 | 318.48 | |
DN | 1121.07 | 3787.64 | 7627.26 | 11684.37 | 15137.23 | |
MGR_0 vs MGR_1 | -59.33% | -84.08% | -89.62% | -97.63% | -98.02% | |
DN vs MGR_1 | 47.15% | 29.53% | 5.76% | 12.63% | -5.93% | |
DN vs MGR_0 | 261.83% | 713.78% | 918.76% | 4654.58% | 4652.96% |
从测试结果可以看出:
TPS对比 | 1 | 4 | 16 | 64 | 256 | |
oltp_read_only | MGR_1 | 695.69 | 2697.91 | 7223.43 | 11699.29 | 14542.4 |
MGR_0 | 691.17 | 2708.6 | 7849.98 | 11636.94 | 14670.99 | |
DN | 645.11 | 2611.15 | 7628.39 | 11294.36 | 14647.22 | |
MGR_0 vs MGR_1 | -0.65% | 0.40% | 8.67% | -0.53% | 0.88% | |
DN vs MGR_1 | -7.27% | -3.22% | 5.61% | -3.46% | 0.72% | |
DN vs MGR_0 | -6.66% | -3.60% | -2.82% | -2.94% | -0.16% | |
oltp_read_write | MGR_1 | 171.37 | 677.77 | 2230 | 3872.87 | 6096.62 |
MGR_0 | 117.11 | 469.17 | 765.64 | 813.85 | 812.46 | |
DN | 257.35 | 1126.07 | 3296.49 | 5135.18 | 7010.37 | |
MGR_0 vs MGR_1 | -31.66% | -30.78% | -65.67% | -78.99% | -86.67% | |
DN vs MGR_1 | 50.17% | 66.14% | 47.82% | 32.59% | 14.99% | |
DN vs MGR_0 | 119.75% | 140.01% | 330.55% | 530.97% | 762.86% | |
oltp_write_only | MGR_1 | 248.37 | 951.88 | 2791.07 | 5989.57 | 11666.16 |
MGR_0 | 162.92 | 603.72 | 791.27 | 828.16 | 866.65 | |
DN | 553.69 | 2173.18 | 5836.64 | 10588.9 | 13241.74 | |
MGR_0 vs MGR_1 | -34.40% | -36.58% | -71.65% | -86.17% | -92.57% | |
DN vs MGR_1 | 122.93% | 128.30% | 109.12% | 76.79% | 13.51% | |
DN vs MGR_0 | 239.85% | 259.96% | 637.63% | 1178.61% | 1427.92% |
从测试结果可以看出:
TPS对比 | 1 | 4 | 16 | 64 | 256 | |
oltp_read_only | MGR_1 | 687.76 | 2703.5 | 7030.37 | 11580.36 | 14674.7 |
MGR_0 | 687.17 | 2744.41 | 7908.44 | 11535.35 | 14656 | |
DN | 657.06 | 2610.58 | 7591.21 | 11174.94 | 14545.45 | |
MGR_0 vs MGR_1 | -0.09% | 1.51% | 12.49% | -0.39% | -0.13% | |
DN vs MGR_1 | -4.46% | -3.44% | 7.98% | -3.50% | -0.88% | |
DN vs MGR_0 | -4.38% | -4.88% | -4.01% | -3.12% | -0.75% | |
oltp_read_write | MGR_1 | 29.13 | 118.64 | 572.25 | 997.92 | 2253.19 |
MGR_0 | 26.94 | 90.8 | 313.64 | 419.17 | 426.7 | |
DN | 254.87 | 1146.57 | 3339.83 | 5307.85 | 7171.95 | |
MGR_0 vs MGR_1 | -7.52% | -23.47% | -45.19% | -58.00% | -81.06% | |
DN vs MGR_1 | 774.94% | 866.43% | 483.63% | 431.89% | 218.30% | |
DN vs MGR_0 | 846.07% | 1162.74% | 964.86% | 1166.28% | 1580.79% | |
oltp_write_only | MGR_1 | 30.81 | 145.54 | 576.61 | 1387.64 | 3705.51 |
MGR_0 | 28.68 | 108.86 | 387.48 | 470.5 | 476.4 | |
DN | 550.11 | 2171.64 | 5866.41 | 10381.72 | 14478.38 | |
MGR_0 vs MGR_1 | -6.91% | -25.20% | -32.80% | -66.09% | -87.14% | |
DN vs MGR_1 | 1685.49% | 1392.13% | 917.40% | 648.16% | 290.73% | |
DN vs MGR_0 | 1818.10% | 1894.89% | 1413.99% | 2106.53% | 2939.12% |
从测试结果可以看出:
TPS对比 | 1 | 4 | 16 | 64 | 256 | |
oltp_read_only | MGR_1 | 688.49 | 2747.69 | 7853.91 | 11722.71 | 15292.73 |
MGR_0 | 687.66 | 2756.3 | 8005.11 | 11567.89 | 15055.69 | |
DN | 656.06 | 2600.35 | 7657.85 | 11227.56 | 14562.86 | |
MGR_0 vs MGR_1 | -0.12% | 0.31% | 1.93% | -1.32% | -1.55% | |
DN vs MGR_1 | -4.71% | -5.36% | -2.50% | -4.22% | -4.77% | |
DN vs MGR_0 | -4.60% | -5.66% | -4.34% | -2.94% | -3.27% | |
oltp_read_write | MGR_1 | 26.01 | 113.98 | 334.95 | 693.34 | 2030.6 |
MGR_0 | 23.93 | 110.17 | 475.68 | 497.92 | 511.99 | |
DN | 122.06 | 525.88 | 1885.7 | 3314.9 | 5889.79 | |
MGR_0 vs MGR_1 | -8.00% | -3.34% | 42.02% | -28.19% | -74.79% | |
DN vs MGR_1 | 369.28% | 361.38% | 462.98% | 378.11% | 190.05% | |
DN vs MGR_0 | 410.07% | 377.34% | 296.42% | 565.75% | 1050.37% | |
oltp_write_only | MGR_1 | 27.5 | 141.64 | 344.05 | 982.47 | 2889.85 |
MGR_0 | 25.52 | 155.43 | 393.35 | 470.92 | 504.68 | |
DN | 171.74 | 535.83 | 1774.58 | 4328.44 | 9429.24 | |
MGR_0 vs MGR_1 | -7.20% | 9.74% | 14.33% | -52.07% | -82.54% | |
DN vs MGR_1 | 524.51% | 278.30% | 415.79% | 340.57% | 226.29% | |
DN vs MGR_0 | 572.96% | 244.74% | 351.15% | 819.15% | 1768.36% |
从测试结果可以看出:
为了明显对比不同部署方式下性能的变化差异,我们选择上述测试中oltp_write_only场景256并发下不同部署方式下的MGR和DN的TPS数据,以机房测试数据为基线,计算对比不同部署方式时TPS数据相比基线的比例,以此感知跨城部署时性能变化差异
MGR_1 (256并发) | DN (256并发) | DN相比于MGR的性能优势 | |
同机房 | 16092.02 | 15137.23 | -5.93% |
同城三中心 | 11666.16 (72.50%) | 13241.74 (87.48%) | +13.50% |
两地三中心 | 3705.51 (23.03%) | 14478.38 (95.64%) | +290.72% |
三地三中心 | 2889.85 (17.96%) | 9429.24 (62.29%) | +226.28% |
从测试结果可以看出:
实际使用中,我们不仅关注性能数据如何,还需要关注性能抖动情况。毕竟如果抖动像过山车一样,对实际用户的体验也非常差。我们对TPS实时输出数据监控展示,考虑到sysbenc工具本身不支持输出性能抖动的监测数据,于是采用数学上的变异系数作为对比指标:
以256并发下oltp_read_write场景为例,我们统计分析MGR_1(RPO<>0)、DN(RPO=0)在同机房、同城三中心、两地三中心、三地三中心五种部署模式下的TPS抖动情况。 实际抖动图如下,实际各场景抖动指标数据如下
CV | 同机房 | 同城三中心 | 两地三中心 | 三地三中心 |
MGR_1 | 10.04% | 8.96% | 6.02% | 8.63% |
DN | 3.68% | 3.78% | 2.55% | 4.05% |
MGR_1/DN | 272.83% | 237.04% | 236.08% | 213.09% |
从测试结果可以看出:
分布式数据库的核心特点就是高可用,集群中任何一个节点的故障都不影响整体的可用性。针对同机房场景部署一主两备的3节点典型部署形态,我们尝试对对一下三种场景进行可用性的测试:
无负载情况下, kill leader, 监控集群各节点状态变化以及是否可写,
MGR | DN | |
起始正常 | 0 | 0 |
kill leader | 0 | 0 |
发现异常节点时间 | 5 | 5 |
3节点降为2节点时间 | 23 | 8 |
MGR | DN | |
起始正常 | 0 | 0 |
kill leader,自动拉起 | 0 | 0 |
发现异常节点时间 | 5 | 5 |
3节点降为2节点时间 | 23 | 8 |
2节点恢复3节点时间 | 37 | 15 |
从测试结果可以看出,在无压力的情况下:
使用sysbench进行oltp_read_write场景下16个线程的并发压测,在图中第10s的时刻,手动kill一个备节点,观察sysbench的的实时输出TPS数据。
从测试结果图中可见:
继续测试,我们将备库重启恢复,观察主库TPS数据变化
从测试结果图中可见:
为了构造MGR多数派故障RPO<>0场景,我们使用社区自带的MTR Case方式,对MGR进行故障注入测试,设计的Case如下:
- --echo
- --echo ############################################################
- --echo # 1. Deploy a 3 members group in single primary mode.
- --source include/have_debug.inc
- --source include/have_group_replication_plugin.inc
- --let $group_replication_group_name= aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
- --let $rpl_group_replication_single_primary_mode=1
- --let $rpl_skip_group_replication_start= 1
- --let $rpl_server_count= 3
- --source include/group_replication.inc
-
- --let $rpl_connection_name= server1
- --source include/rpl_connection.inc
- --let $server1_uuid= `SELECT @@server_uuid`
- --source include/start_and_bootstrap_group_replication.inc
-
- --let $rpl_connection_name= server2
- --source include/rpl_connection.inc
- --source include/start_group_replication.inc
-
- --let $rpl_connection_name= server3
- --source include/rpl_connection.inc
- --source include/start_group_replication.inc
-
- --echo
- --echo ############################################################
- --echo # 2. Init data
- --let $rpl_connection_name = server1
- --source include/rpl_connection.inc
- CREATE TABLE t1 (c1 INT PRIMARY KEY);
- INSERT INTO t1 VALUES(1);
-
- --source include/rpl_sync.inc
- SELECT * FROM t1;
-
- --let $rpl_connection_name = server2
- --source include/rpl_connection.inc
- SELECT * FROM t1;
-
- --let $rpl_connection_name = server3
- --source include/rpl_connection.inc
- SELECT * FROM t1;
-
- --echo
- --echo ############################################################
- --echo # 3. Mock crash majority members
-
- --echo # server 2 wait before write relay log
- --let $rpl_connection_name = server2
- --source include/rpl_connection.inc
- SET GLOBAL debug = '+d,wait_in_the_middle_of_trx';
-
- --echo # server 3 wait before write relay log
- --let $rpl_connection_name = server3
- --source include/rpl_connection.inc
- SET GLOBAL debug = '+d,wait_in_the_middle_of_trx';
-
-
- --echo # server 1 commit new transaction
- --let $rpl_connection_name = server1
- --source include/rpl_connection.inc
- INSERT INTO t1 VALUES(2);
- # server 1 commit t1(c1=2) record
- SELECT * FROM t1;
- select CHANNEL_NAME,MEMBER_HOST,MEMBER_PORT,MEMBER_STATE,MEMBER_ROLE,MEMBER_VERSION,MEMBER_COMMUNICATION_STACK from performance_schema.replication_group_members order by MEMBER_PORT;
- --echo # server 1 crash
- --source include/kill_mysqld.inc
-
- --echo # sleep enough time for electing new leader
- sleep 60;
-
- --echo
- --echo # server 3 check
- --let $rpl_connection_name = server3
- --source include/rpl_connection.inc
- SELECT * FROM t1;
- select CHANNEL_NAME,MEMBER_HOST,MEMBER_PORT,MEMBER_STATE,MEMBER_ROLE,MEMBER_VERSION,MEMBER_COMMUNICATION_STACK from performance_schema.replication_group_members order by MEMBER_PORT;
- --echo # server 3 crash and restart
- --source include/kill_and_restart_mysqld.inc
-
- --echo # sleep enough time for electing new leader
- sleep 60;
-
- --echo
- --echo # server 2 check
- --let $rpl_connection_name = server2
- --source include/rpl_connection.inc
- SELECT * FROM t1;
- select CHANNEL_NAME,MEMBER_HOST,MEMBER_PORT,MEMBER_STATE,MEMBER_ROLE,MEMBER_VERSION,MEMBER_COMMUNICATION_STACK from performance_schema.replication_group_members order by MEMBER_PORT;
- --echo # server 2 crash and restart
- --source include/kill_and_restart_mysqld.inc
-
- --echo # sleep enough time for electing new leader
- sleep 60;
-
- --echo
- --echo ############################################################
- --echo # 4. Check alive members, lost t1(c1=2) record
-
- --echo # server 3 check
- --let $rpl_connection_name= server3
- --source include/rpl_connection.inc
- select CHANNEL_NAME,MEMBER_HOST,MEMBER_PORT,MEMBER_STATE,MEMBER_ROLE,MEMBER_VERSION,MEMBER_COMMUNICATION_STACK from performance_schema.replication_group_members order by MEMBER_PORT;
- --echo # server 3 lost t1(c1=2) record
- SELECT * FROM t1;
-
- --echo
- --echo # server 2 check
- --let $rpl_connection_name = server2
- --source include/rpl_connection.inc
- select CHANNEL_NAME,MEMBER_HOST,MEMBER_PORT,MEMBER_STATE,MEMBER_ROLE,MEMBER_VERSION,MEMBER_COMMUNICATION_STACK from performance_schema.replication_group_members order by MEMBER_PORT;
- --echo # server 2 lost t1(c1=2) record
- SELECT * FROM t1;
- !include ../my.cnf
-
- [mysqld.1]
- loose-group_replication_member_weight=100
-
- [mysqld.2]
- loose-group_replication_member_weight=90
-
- [mysqld.3]
- loose-group_replication_member_weight=80
-
- [ENV]
- SERVER_MYPORT_3= @mysqld.3.port
- SERVER_MYSOCK_3= @mysqld.3.socket
Case运行结果如下:
-
- ############################################################
- # 1. Deploy a 3 members group in single primary mode.
- include/group_replication.inc [rpl_server_count=3]
- Warnings:
- Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
- Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
- [connection server1]
- [connection server1]
- include/start_and_bootstrap_group_replication.inc
- [connection server2]
- include/start_group_replication.inc
- [connection server3]
- include/start_group_replication.inc
-
- ############################################################
- # 2. Init data
- [connection server1]
- CREATE TABLE t1 (c1 INT PRIMARY KEY);
- INSERT INTO t1 VALUES(1);
- include/rpl_sync.inc
- SELECT * FROM t1;
- c1
- 1
- [connection server2]
- SELECT * FROM t1;
- c1
- 1
- [connection server3]
- SELECT * FROM t1;
- c1
- 1
-
- ############################################################
- # 3. Mock crash majority members
- # server 2 wait before write relay log
- [connection server2]
- SET GLOBAL debug = '+d,wait_in_the_middle_of_trx';
- # server 3 wait before write relay log
- [connection server3]
- SET GLOBAL debug = '+d,wait_in_the_middle_of_trx';
- # server 1 commit new transaction
- [connection server1]
- INSERT INTO t1 VALUES(2);
- SELECT * FROM t1;
- c1
- 1
- 2
- select CHANNEL_NAME,MEMBER_HOST,MEMBER_PORT,MEMBER_STATE,MEMBER_ROLE,MEMBER_VERSION,MEMBER_COMMUNICATION_STACK from performance_schema.replication_group_members order by MEMBER_PORT;
- CHANNEL_NAME MEMBER_HOST MEMBER_PORT MEMBER_STATE MEMBER_ROLE MEMBER_VERSION MEMBER_COMMUNICATION_STACK
- group_replication_applier 127.0.0.1 13000 ONLINE PRIMARY 8.0.32 XCom
- group_replication_applier 127.0.0.1 13002 ONLINE SECONDARY 8.0.32 XCom
- group_replication_applier 127.0.0.1 13004 ONLINE SECONDARY 8.0.32 XCom
- # server 1 crash
- # Kill the server
- # sleep enough time for electing new leader
-
- # server 3 check
- [connection server3]
- SELECT * FROM t1;
- c1
- 1
- select CHANNEL_NAME,MEMBER_HOST,MEMBER_PORT,MEMBER_STATE,MEMBER_ROLE,MEMBER_VERSION,MEMBER_COMMUNICATION_STACK from performance_schema.replication_group_members order by MEMBER_PORT;
- CHANNEL_NAME MEMBER_HOST MEMBER_PORT MEMBER_STATE MEMBER_ROLE MEMBER_VERSION MEMBER_COMMUNICATION_STACK
- group_replication_applier 127.0.0.1 13002 ONLINE PRIMARY 8.0.32 XCom
- group_replication_applier 127.0.0.1 13004 ONLINE SECONDARY 8.0.32 XCom
- # server 3 crash and restart
- # Kill and restart
- # sleep enough time for electing new leader
-
- # server 2 check
- [connection server2]
- SELECT * FROM t1;
- c1
- 1
- select CHANNEL_NAME,MEMBER_HOST,MEMBER_PORT,MEMBER_STATE,MEMBER_ROLE,MEMBER_VERSION,MEMBER_COMMUNICATION_STACK from performance_schema.replication_group_members order by MEMBER_PORT;
- CHANNEL_NAME MEMBER_HOST MEMBER_PORT MEMBER_STATE MEMBER_ROLE MEMBER_VERSION MEMBER_COMMUNICATION_STACK
- group_replication_applier 127.0.0.1 13002 ONLINE PRIMARY 8.0.32 XCom
- group_replication_applier 127.0.0.1 13004 UNREACHABLE SECONDARY 8.0.32 XCom
- # server 2 crash and restart
- # Kill and restart
- # sleep enough time for electing new leader
-
- ############################################################
- # 4. Check alive members, lost t1(c1=2) record
- # server 3 check
- [connection server3]
- select CHANNEL_NAME,MEMBER_HOST,MEMBER_PORT,MEMBER_STATE,MEMBER_ROLE,MEMBER_VERSION,MEMBER_COMMUNICATION_STACK from performance_schema.replication_group_members order by MEMBER_PORT;
- CHANNEL_NAME MEMBER_HOST MEMBER_PORT MEMBER_STATE MEMBER_ROLE MEMBER_VERSION MEMBER_COMMUNICATION_STACK
- group_replication_applier NULL OFFLINE
- # server 3 lost t1(c1=2) record
- SELECT * FROM t1;
- c1
- 1
-
- # server 2 check
- [connection server2]
- select CHANNEL_NAME,MEMBER_HOST,MEMBER_PORT,MEMBER_STATE,MEMBER_ROLE,MEMBER_VERSION,MEMBER_COMMUNICATION_STACK from performance_schema.replication_group_members order by MEMBER_PORT;
- CHANNEL_NAME MEMBER_HOST MEMBER_PORT MEMBER_STATE MEMBER_ROLE MEMBER_VERSION MEMBER_COMMUNICATION_STACK
- group_replication_applier NULL OFFLINE
- # server 2 lost t1(c1=2) record
- SELECT * FROM t1;
- c1
- 1
复现丢数的Case大概逻辑是这样的:
根据以上Case可见,对于MGR,当多数派宕机,主库不可用,备库恢复后,存在数据丢失的RPO<>0的情况,原本已返回客户端commit成功的记录丢了。
而对于DN,多数派的达成需要日志在多数派中都持久化,所以即使在上述场景下,数据也不会丢失,也能保证RPO=0。
MySQL的传统主备模式下,备库一般会包含IO线程和Apply线程,引入了Paxos协议后替换了IO线程同步主备库binlog的工作,备库的复制延迟主要就看备库Apply回放的开销,我们这里成为备库回放延迟。
我们使用sysbench测试oltp_write_only场景,测试100并发下, 不同event数量时, 备库回放出现延迟的持续时间。备库回放延迟时间通过监控performance_schema.replication_applier_status_by_worker表的APPLYING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP列来实时查看各worker是否工作来断定复制是否结束
从测试结果图中可见:
MGR | DN | ||
性能 | 读事务 | 持平 | 持平 |
写事务 | RPO<>0时性能不如DN RPO=0时性能远不如DN 跨城部署性能下跌严重27%~82% | 写事务性能远高于MGR 跨城部署性能下降较小4%~37% | |
抖动 | 性能抖动厉害,抖动范围6~10% | 比较平稳3%,只有MGR一半 | |
RTO | 主库宕机 | 5s发现异常,23s降为两节点 | 5s发现异常,8s降为两节点 |
主库重启 | 5s发现异常,37s恢复三节点 | 5s发现异常,15s恢复三节点 | |
备库宕机 | 主库持续20s出现流量跌0 需要显式开启group_replication_paxos_single_leader后可缓解 | 主库持续高可用 | |
备库重启 | 主库持续10s出现流量跌0 显式开启group_replication_paxos_single_leader也无效 | 主库持续高可用 | |
RPO | Case复现 | 多数派宕机时RPO<>0 性能和RPO=0两者不能兼得 | RPO = 0 |
备库延迟 | 备库回放耗时 | 主备延迟很大, 性能和主备延迟两者不能兼得 | 整体备库回放的总耗时是MGR的4%, 是MGR的25倍 |
参数 | 关键参数 |
| 默认配置,不需要专业人员定制化配置 |
经过深入的技术剖析与性能对比,PolarDB-X DN凭借其自研的X-Paxos协议和一系列优化设计,在性能、正确性、可用性及资源开销等方面展现出对MySQL MGR的多项优势,但MGR在MySQL生态体系内也占据重要地位,但需要考虑备库宕机抖动、跨机房容灾性能波动、稳定性等各种情况,因此如果想用好MGR,必须配备专业的技术和运维团队的支持。
在面对大规模、高并发、高可用性需求时,PolarDB-X存储引擎以其独特的技术优势和优异的性能表现,相比于MGR在开箱即用的场景下,PolarDB-X基于DN的集中式(标准版)在功能和性能都做到了很好的平衡,成为了极具竞争力的数据库解决方案。