写点什么

MySQL-8.0 Group Replication 研究与改造汇总

作者:KunlunDB
  • 2022 年 5 月 23 日
  • 本文字数:6778 字

    阅读完需:约 22 分钟

2020 年 2 月下旬以来,本文作者(我)研究和改造了 Percona-MySQL-8.0.18-9 的若干新功能实现,主要是 MySQL Group Replication(MGR)和 clone 等功能,并且在 Percona-MySQL-8.0.18-9 在分布式事务容灾方面填补了功能空白,修复了其漏洞缺陷,以及做了其它若干针对昆仑分布式数据库整体规划的功能开发。现在把我的一些基于 Percona-MySQL-8.0.18-9 的发现和想法分享一下。本文不打算完整介绍 MySQL8.0 的任何新功能,因为网上已经有其他同行写的这方面的文章若干篇,并且最权威最完整的介绍始终是 MySQL 官方文档。本文假设读者已经熟知这些概念和功能,我着重介绍我为了昆仑分布式数据库的需求而对 Percona-MySQL-8.0.18-9 的 MGR 和 clone 功能做的探索和发现以及对 MGR 在分布式事务容灾方面的改进。

尽管仍然在分布式事务容灾恢复处理方面有诸多漏洞和功能空白,MGR single primary mode(单主模式)具备非常好的数据一致性保障以及容灾能力,并保持了很高的性能,适合在生产实践中广泛使用。MGR 多主模式(multi-primary mode)在写入负载很重的时候,会由于多个节点的数据写入冲突导致的大量事务回滚而产生比较大的 TPS 和延时的抖动,所以只适合写入非常少,主要是读负载的场景,比如少量参数配置的存储,类似 zookeeper 的使用场景。多主模式下如果为了避免写入冲突而在应用层巧妙的安排每个主节点写入或者修改的数据集合使它们没有交集,那么应用层的开发复杂度会大大提升,并且对已有应用来说也无法自动适应,所以相信不会有多少用户这么做的。最后,无论单主模式还是多主模式还是传统的异步复制模式,一个 MySQL 集群也只能处理若干个 TB 以内的数据,因为集群中所有节点都存储了完全相同的 N 份数据,集群的数据处理能力仍然受限于每个节点的数据处理能力,而单节点的能力又受限于服务器节点的计算资源以及硬件成本等因素,并不能持续增加其数据存储和处理能力。因此,当数据规模增大到一定规模以上之后,还是需要使用分布式数据库集群。

在昆仑分布式数据库中,我们将会使用 MGR 单主模式,每个 storage shard 就是一个 MGR 单主集群。全部数据存储在多个 storage shard 中,计算节点会确保不同 shard 的数据没有交集。

MGR 自动集群管理

MGR 的一个优点是其自动的集群管理能力。这不仅包括我们常说的自动的主备切换能力,还包括自动化的组员身份(group membership)管理和基于分布式恢复(distributed recovery)技术的 provisioning 能力,也就是可以在集群运行期间自动增加备机节点。

先说主备切换:集群所有节点通过定期运行 paxos 协议交换节点状态,MGR 的每个节点就可以自动发现其他节点的消失或者新增并且在集群内同步这一信息。如果发现主节点消失了,那么集群中当前所有节点就会各自独立运行选主算法从而选出一致的新的主节点并在集群内同步新主决议。如果发现若干个节点消失导致失去 quorum 了,那么集群会自动转为只读防止脑裂。这就把 TDSQL 或者各种分布式中间件多年以来使用 zookeeper/etcd 做的大量工作以更高的质量和可靠性给完成了。要知道在使用和维护 zookeeper 方面,没有谁敢说自己没踩过坑的。 MGR 通过在 MySQL 节点集群中运行 paxos 协议,避免了对外部 paxos 集群的依赖,可以减少运维工作量并且减少错误源。同时 MGR 把 paxos 与主备复制做了有机的融合,从而实现了事务 binlog 的原子广播,大大提升了集群的容灾能力。这部分后面会介绍。

通过 MGR 的组成员管理,MGR 集群可以跟踪节点的加入和离开,并且在集群内所有节点同步这些状态。在主节点离开后自动执行主选举;在集群因为网络分区或者节点宕机而失去 quorum 后,可以自动把主节点也设置为只读,避免脑裂。由于组员的加入或者离开而产生的每一个组成员集合,在 MGR 中被称为一个 view,view 的每一次变化就是一个 view change 事件,代表了一次组员加入或者离开。该事件被记录在 binlog 中,所以组拓扑结构变化是持久和全局一致的信息。因此 view change 事件在 MGR 的分布式恢复中被用作 group_replication_recovery channel 的数据同步的中止点。

通过分布式恢复(distributed recovery)我们可以用一条 SQL 语句就轻松加入一个新的备机节点,或者把一个老备机节点带到最新状态。这个功能意味着两个复杂而重要的事情被自动化和简化了。

首先,我们可以使用它来做数据全量备份,代替 Percona Xtrabackup — 只要新做一个 DB 实例,作为备机加入 MGR 集群,执行一条语句‘start group_replication’,完成 distributed recovery 后停机后,这个实例就具有了主节点当前的全部数据。然后归档存储其数据目录即可当作全量备份。

其次,重做备机这个 DBA 常用操作,现在可以非常简单地用一条命令就完成了。当然,在 MGR 单主模式下,相信 DBA 也不再需要经常重做备机了—过去重做备机通常是 MySQL DBA 的大杀器和终极武器——当备机复制线程由于各种原因卡住走不下去时,当主备断连后备机无法连上主继续复制时(比如该备机所需的主的部分 binlog 被 purge 了),当使用了 MyISAM 表然后备机非正常退出导致备机数据错乱时,或者当备机复制太慢与主节点数据版本的距离越来越远已经没有希望追上主的时候,DBA 都需要重做备机。可以说对于一个管理着几百个 MySQL 集群的 DBA 来说,重做备机是他的日常工作。如果哪天他没有重做任何一个备机,那才是一个怪事,他一定会想会不会是哪里发生了什么问题。以前 DBA 重做备机使用的工具基本都是 Percona Xtrabackup 配合 mysqlbinlog 工具去追(apply) binlog;在 TDSQL 中还有更炫酷的玩法来追 binlog — 把从主机复制过来的 binlog 当作 relay log,对使用全量数据做的 DB 节点做一个 change master to 操作,把它模拟成一个备机去追(apply)这些 binlog(这还需要对 MySQL server 开发一处特定功能)。

现在所有的这些奇技淫巧现在都不需要了,用户只需要新建一个空的 DB 实例,配置好 MGR 的参数,然后运行‘start group_replication’ 即可。 MGR 会做 distributed recovery:如果发现备机全新或者与主机最新 binlog 距离太远或者任何组节点都没有新节点所需的 binlog,就克隆(clone)一个 donor 的全量 InnoDB 数据(这一步类似于以前使用 Persona Xtrabackup 做全量复制),然后使用传统的异步复制与 MGR 相结合的方式来重放事务 binlog 完成 DB 实例的 recovery。这一步类似于以前的追 binlog,但是分为两小步,先做异步复制追 binlog 到本节点加入时刻(view change 标识),然后重放前面两个阶段期间收到的 binlog。

与业界以前的做备机节点的方法不同的是,MySQL MGR 的 distributed recovery 更加高效,更加用户友好。比如 binlog 和 clone 过程都可以多线程并发传输 binlog,还都可以压缩传输 binlog 和 InnoDB 数据文件,还自带限流配置避免耗尽磁盘和网络带宽而影响业务请求处理。同时,clone 的过程中完全不阻塞 DML 语句(会阻塞 DDL 语句),也就是不像 Percona Xtrabackup 那样需要在全量备份期间一段时间内做 FTWRL 操作来锁住一切 DML 写操作以及正要提交的事务,因此对业务请求处理的影响很小。之所以能避免 FTWRL,得益于 MySQL-8.0 的 transactional data dictionary(数据字典存储在 InnoDB 表中),以及所有元数据表都存储在 InnoDB 中的现实。不过 MySQL-8.0 的 clone 功能与 Percona Xtrabackup 相比的劣势就是不支持其它存储引擎,只支持 InnoDB,它是绑定 innodb 的。

MGR 的事务原子广播

MGR 的另一大优点是它基于 paxos 协议实现的原子广播功能,也就是确保了任何一个事务在 master 上面提交时,MGR 会先确保简单多数(quorum)的节点都收到了这个事务的完整的 binlog,然后各个节点才真正开始本地提交。这样就避免了以前异步复制模式下的诸多主备容灾问题,这些问题本质上都是因为主节点 crash 时刻,那些正在提交的事务的 binlog 在主和一部分或者全部备节点上面可能不一致。比如主节点 crash 时候一个正在提交的事务 T,有的备节点收到了完整的 T 的 binlog,有的备节点完全没有收到 T 的 binlog,另一些备节点收到了 T 的一部分 binlog。如果 T 是 XA 事务,则 MySQL-5.7 中 XA 事务不完整的情况下如果主备断链,备机的事件分发线程在一些条件下会持续等待该事务剩余的 binlog 事件却不能回滚该事务并重新执行它,导致备机复制彻底卡死,这个备节点就无法使用了。如果此备机此刻要被选为主节点时,这个备机就会因为无法完成 stop slave 而无法转为主节点,从而导致这个 storage shard 不可写入。这类问题我在 TDSQL 中都已经解决了。而在 MySQL 8.0 中这类问题已经被 MGR 的原子广播能力彻底解决了。

MGR 对备机复制性能的提升

MySQL-5.7 的备机复制基于逻辑时钟算法,有一个潜在的缺陷不易察觉,就是备机复制的并发度受限于主机的客户端连接数。这是因为 logical-clock 算法中任意时刻其序列号不大于当前系统全局序列号的事务总数无法超过客户端连接数。比如如果主机始终只有 20 个连接在执行事务,那么备机的并发度一定不会超过 20,这就导致如果备机需要重放少量连接产生的大量 binlog,就会很慢。另外如果表没有主键和唯一索引那么备机复制遇到删除或者更新大量行的事务,其复制会很慢,导致备机落后于主机越来越多,然后就没有备机可以迅速做主备切换了。一旦主节点故障,那么集群一段比较长的时间内就不可写入了。每到此时 DBA 就会很紧张,有时对于重要业务会新做一个备机节点来压压惊。

MGR 基于 write set 可以轻松判别事务的依赖关系,从而做到只要两个事务不冲突就可以并发执行。如果事务 T1 和 T2 先后修改了同一行 R,那么事务 T2 依赖于 T1,也就是只有 T1 执行完毕后才可以执行 T2。如果 T1 和 T2 的 writ set 没有交集,那么 T1 和 T2 就没有依赖关系,就可以并发执行。MGR 甚至可以可选地并发执行同一个连接上面产生的不冲突的事务(binlog_transaction_dependency_tracking = writes_set),不过考虑到这样做并不符合时序逻辑,特别是如果备机会用做备机读的话这么做并不适合,还是推荐使用 binlog_transaction_dependency_tracking =write_set_session。基于 write_set 和 write_set_session 的备机复制完全摆脱了客户端并发数的限制,可以达到非常高的复制性能。再加上 MGR 的强制表有主键或者唯一索引的要求,这就使得备机落后于主机无法追上这个问题被彻底解决了。可以预期生产系统中备机与主机的距离基本上总可以保持很近,这就让主备切换在任何情况下都可以快速实施。不会出现没有比较新的备机可以立即使用导致无法迅速完成主备切换的问题。再加上完全使用事务存储引擎和 MGR 的原子广播能力,可以预期以后 DBA 很少需要重做备机了,一切都自动化之后不知道 DBA 们是该高兴呢还是高兴呢 😉

MGR single primary 的代价

当然,任何好东西都有代价。使用 MGR single primary 模式比传统的 binlog 复制模式的性能开销和延时略大,并且有一些功能约束。这些额外开销主要是因为 certify 过程导致的—除了需要传输事务的 binlog 之外,certify 在主节点上还需要计算和传输 write set,slave 端需要接收和存储 write set(即使单主模式也需要计算,传输和存储 write set);另外还有 paxos 协议运行导致的额外的网络延时,这部分延时会导致每个事务提交的延时略大。并且为了保持 MGR 的高性能,要求半数的备机必须与主机同机房,否则 paxos 协议导致的延时会更大 — 当然,这个要求并不算过分。

MGR 的功能约束是指表必须要有主键或者唯一索引,并且使用 MGR 事实上由于其 distributed recovery 所使用的 clone 插件的要求因此只能使用 InnoDB 存储引擎。不过这些约束完全可以接受 — 即使从 replication 的性能角度,每个表也应该有主键或者唯一索引,TDSQL 也有此要求。从 MySQL-8.0 开始由于数据字典存储在了 InnoDB 中,InnoDB 已经完全与 MySQL 绑定了。那么其他存储引擎存在的意义,似乎只剩下当作临时表或者 log 表(general log, slow log 如果存储在表中的好,表是存储在 csv 存储引擎的)。 其他满足特定需求的事务引擎—比如 myrocks 的高压缩率适合存储历史数据—就需要在 clone 插件中加入 myrocks 引擎的支持,否则就无法在 MGR 中被使用到了。但问题是 clone 的工作原理与 InnoDB 有比较深的绑定,比如 InnoDB 的 undo log 专门做了修改来支持 clone 功能,其他引擎该如何接入到 clone 插件中还是一个问题,至少是需要比较大的工作量的。

最后,MGR 对事务 binlog 的数据量有约束,这是因为如果事务 binlog 传输的时耗超过了 paxos 协议的超时,那么其他节点就会发现自己超时没有受到主节点的任何消息,误以为主节点已经宕机了。基于完全相同的原因,尽管 TDSQL 不使用 paxos 协议,这个约束在 TDSQL 中也是存在的 —如果正在提交的事务的 binlog 太大导致传输超时,那么 DB 实例的 agent 就会误以为主节点与备机断连了,于是会误触发主备切换。因此必须限制事务 binlog 的规模。

我对 MySQL 8.0 的改造

不过遗憾的是,MySQL8.0 的 MGR 仍然有 XA 事务的 binlog 容灾恢复的诸多缺陷,这些 bug 我在 TDSQL-Percona-MySQL-5.7.17-11 中已经做了修复并且报给了 MySQL 官方,还提交了修复的 patch。这些修复经过 TDSQL 团队的充分测试以及腾讯公司内外大量用户的长期验证证明是可靠的。但是 MySQL 官方团队截止 MySQL8.0.18 并没有做修复,这些 bug 在 MySQL-8.0 中仍然存在,并且由于一些新功能的加入而有了新的问题需要处理。我花了很多时间修复了这些 bug,并且通过了 MySQL 的测试包以及容灾测试。这样,昆仑分布式数据库就有了坚不可摧的容灾能力。

除此之外还做了一些 MySQL 功能来适应昆仑分布式数据库整体需求,此处不再赘述。

MGR 的 single primary 模式并不适合用户直接使用,这是因为用户的程序需要去适配主备切换导致的主节点变化,以及需要完成 shard 集群管理(比如集群启停就有不少讲究)等任务,会导致开发难度增大并且容易出错。MySQL 官方文档也是因此推荐配合 MGR 使用 MySQL router 或者 InnoDB cluster。但是由于 MySQL 官方版本的 XA 事务处理方面的缺失(即容灾能力有缺陷),所以使用 MySQL router 或者 InnoDB cluster 并不能可靠地在同一个事务中写入到多个 shard 中。我没有使用过这两个工具,不过想必它们也并没有支持分布式事务。不知道是不是为了避免与 Oracle 数据库产生公司内的产品竞争。

昆仑数据库中将会使用 MGR single primary 模式,并且自动处理 MGR 的主备切换适配,分布式事务容灾和恢复,集群管理等所有通用任务,成为用户和 DBA 的一个简单易用并且具备完整的容灾能力的分布式数据库系统。

MGR 的地位和贡献

我认为 MGR 进一步巩固了 binlog 子系统在 MySQL 生态中的地位,避免了 binlog 子系统的边缘化。要知道本来随着 MySQL 逐步转向完全使用 InnoDB 存储引擎(数据字典使用 innodb,干掉了 frm 文件; 业界逐步弃用 MyISAM),binlog 系统的必要性变的很低了,用户完全可以使用 InnoDB redo log 复制来挂载备机,也可以做到 HA 和备机读,完全可以代替传统异步复制(async replication)。而且这么做还可以避免 binlog 写入和存储的资源消耗,特别是它对事务提交产生的延时是显著的 —主节点为了产生 binlog 事务序列,把本可以并发执行的多事务提交给串行化了,单线程执行 binlog flush 阶段和可选的单线程执行引擎层提交阶段。尽管 innodb redo log 占用存储空间预期比 binlog 更大(主要是 btree 页面分裂等页级别的 redo log 导致的)等缺点,也可以通过压缩来抵消。再考虑到传统异步复制时常出现的备机卡住或者落后主机太多无法追上等问题,而 innodb redo log 的重放(replay)会因为只做页面级别的修改而非常快速,省去了上层代码的开销,于是 innodb redo log 复制就变的更有吸引力了。在去年规划昆仑分布式数据库期间我曾经考虑过只使用 InnoDB 不使用 MySQL 这个选项。要知道 innodb 还自带一个简单的 SQL 处理器,完全可以执行单表查询和简单的表连接。不过最终就是考虑到 MGR 的上述诸多优点而选择使用 MGR。

相信 MGR 在未来会不断发展,进一步提升 MySQL 在数据库业界的地位和价值。我也相信昆仑数据库能够把 MySQL MGR 的价值充分发挥出来,并且基于 MGR 实现更大规模的高效而且自动化的数据管理。

END

昆仑数据库是一个 HTAP NewSQL 分布式数据库管理系统,可以满足用户对海量关系数据的存储管理和利用的全方位需求。应用开发者和 DBA 的使用昆仑数据库的体验与单机 MySQL 和单机 PostgreSQL 几乎完全相同,因为首先昆仑数据库支持 PostgreSQL 和 MySQL 双协议,支持标准 SQL:2011 的 DML 语法和功能以及 PostgreSQL 和 MySQL 对标准 SQL 的扩展。同时,昆仑数据库集群支持水平弹性扩容,数据自动拆分,分布式事务处理和分布式查询处理,健壮的容错容灾能力,完善直观的监测分析告警能力,集群数据备份和恢复等 常用的 DBA 数据管理和操作。所有这些功能无需任何应用系统侧的编码工作,也无需 DBA 人工介入,不停服不影响业务正常运行。昆仑数据库具备全面的 OLAP 数据分析能力,通过了 TPC-H 和 TPC-DS 标准测试集,可以实时分析最新的业务数据,帮助用户发掘出数据的价值。昆仑数据库支持公有云和私有云环境的部署,可以与 docker,k8s 等云基础设施无缝协作,可以轻松搭建云数据库服务。请访问 http://www.zettadb.com/ 获取更多信息并且下载昆仑数据库软件、文档和资料。

KunlunDB 项目已开源

【GitHub:】https://github.com/zettadb

【Gitee:】https://gitee.com/zettadb

发布于: 刚刚阅读数: 4
用户头像

KunlunDB

关注

还未添加个人签名 2022.03.09 加入

还未添加个人简介

评论

发布
暂无评论
MySQL-8.0 Group Replication 研究与改造汇总_国产数据库_KunlunDB_InfoQ写作社区