作者简介:王伟,京东基础平台数据库工程师,京东商城基础平台部门包括大规模容器集群调度、数据库与存储技术、消息系统与服务框架、架构与运维、机器学习与人工智能等技术方向。
一. MySQL 复制的三种模式MySQL 当前存在的三种复制模式有:异步模式、半同步模式和组复制模式,先了解一下三种模式的工作方式。
1. MySQL Asynchronous Replication(异步复制)异步复制是 MySQL 最早的也是当前使用最多的复制模式,异步复制提供了一种简单的主 - 从复制方法,包含一个主库(master)和备库(一个,或者多个)之间,主库执行并提交了事务,在这之后(因此才称之为异步),这些事务才在从库上重新执行一遍(基于 statement)或者变更数据内容(基于 row),主库不检测其从库上的同步情况。在服务器负载高、服务压力大的情况下主从产生延迟一直是其诟病。工作流程简图如下:
2. MySQL Semisynchronous Replication(半同步复制)MySQL5.5 的版本在一步同步的基础之上,以插件的形式实现了一个变种的同步方案,称之为半同步(semi-sync replication)。这个插件在源生的异步复制上,添加了一个同步的过程:当从库接收到了主库的变更(即事务)时,会通知主库。主库上的操作有两种:接收到这个通知以后才去 commit 事务;接受到之后释放 session。这两种方式是由主库上的具体配置决定的。当主库收不到从库的变更通知超时时,由半同步复制自动切换到异步同步,这样就极大了保证了数据的一致性(至少一个从库),但是在性能上有所下降,特别是在网络不稳定的情况下,半同步和同步之间来回切换,对正常的业务是有影响的。其工作流程简图如下:
3. Group Replication(组复制)不论是异步复制还是半同步复制,都是一个主下面一个从或是多个从的模式,在高并发下高负载下,都存在延迟情况,此时如果主节点出现异常,那么就会出现数据不一致的情况,数据可能会丢,在金融级数据库中是不能容忍的。在这种情况下,急需出现一种模式来解决这些问题。在 MySQL5.7.17 的版本中,带着这些期待,新的复制模式组复制产生并 GA 了(本文的测试等数据均基于 MySQL5.7.17)。
组复制的工作流程图如下:
二. 组复制的工作原理MySQL 组复制是一个 MySQL 插件,它建立在现有的 MySQL 复制基础结构上,利用了二进制日志,基于行的日志记录和全局事务标识符等功能。它集成了当前的 MySQL 框架,如性能模式、插件和服务基础设施等。
组复制(Group Replication)基于分布式一致性算法 (Paxos 协议的变体) 实现,一个组允许部分节点挂掉,只要保证绝大多数节点仍然存活并且之间的通讯是没有问题的,那么这个组对外仍然能够提供服务,它是一种被使用在容错系统中的技术。Group Replication(复制组)是由能够相互通信的多个服务器(节点)组成的。在通信层,Group replication 实现了一系列的机制:比如原子消息(atomic message delivery)和全序化消息(total ordering of messages)。这些原子化,抽象化的机制,为实现更先进的数据库复制方案提供了强有力的支持。MySQL Group Replication 正是基于这些技术和概念,实现了一种多主全更新的复制协议。简而言之,一个 Group Replication 就是一组节点,每个节点都可以独立执行事务,而读写事务则会在于 group 内的其他节点进行协调之后再 commit。因此,当一个事务准备提交时,会自动在 group 内进行原子性的广播,告知其他节点变更了什么内容 / 执行了什么事务。这种原子广播的方式,使得这个事务在每一个节点上都保持着同样顺序。这意味着每一个节点都以同样的顺序,接收到了同样的事务日志,所以每一个节点以同样的顺序重演了这些事务日志,最终整个 group 保持了完全一致的状态。然而,不同的节点上执行的事务之间有可能存在资源争用。这种现象容易出现在两个不同的并发事务上。假设在不同的节点上有两个并发事务,更新了同一行数据,那么就会发生资源争用。面对这种情况,Group Replication 判定先提交的事务为有效事务,会在整个 group 里面重放,后提交的事务会直接中断,或者回滚,最后丢弃掉。因此,这也是一个无共享的复制方案,每一个节点都保存了完整的数据副本。
从其工作的原理可以看出,Group Replication 基于 Paxos 协议的一致性算法校验事务执行是否有冲突,然后顺序执行事务,达到最终的数据一致性,也就意味着部分节点可以存在延迟。可以设置多主同时写入和单主写入,通过设置 group_replication_single_primary_mode 来进行控制是多主还是单主,官方推荐单主写入,允许延迟,但延迟过大,则会触发限流规则(可配置的),整个集群会变的很慢,性能大打折扣。
三. 组复制的程序结构在 MySQL 的底层,GR 增加了另外的 API 层来实现所需要的功能。程序结构上,GRAPI 主要分为三部分:
在这几个主要 API 层的下面,是统一的复制协议逻辑处理层,这一层主要是统一应用层的各种调用。在更下层,则是通用程度更高的分布式通讯层,处于调用便利,分布式通讯曾对上提供使用的 API,API 的下面,是 Paxos 实现的分布式通讯协议组件,这个组件与集群中其他节点一起,形成一个虚拟概念化的分布式集群。
四. 消息压缩(Message Compression)这个压缩主要是指 MySQL 的 bin-log 压缩,所使用的压缩算法是 LZ4。当网络带宽是瓶颈时,消息压缩可以在组通信级别提供高达 30-40%的吞吐量改进,这在网络传输压力比较大的组中是尤为重要的。LZ4 能很好的支持多线程环境,获得更高的压缩和解压速度。以下是压缩算法 LZ4 的压缩和解压情况:
压缩发生在组通信引擎级别,之前数据被交给组通信线程,所以它发生在 mysql 用户会话线程的上下文中。事务有效网络负载可以在被发送到组之前被压缩,并且在被接收时被解压缩。压缩是有条件的,并且取决于配置的阈值。默认情况下启用压缩,此外,它并不要求组中的所有服务器节点都启用压缩机制,在接收到消息时,成员检查消息信封以验证它是否被压缩,如果需要,则成员解压缩该事务,然后将其传递到上层。
默认情况下启用压缩,阈值为 1000000 字节(1MB)。压缩阈值(以字节为单位)可以设置为大于默认值。在这种情况下,只有具有大于阈值的有效负载的事务被压缩。下面是如何设置压缩阈值的示例。
- STOP GROUP_REPLICATION;
- SET GLOBAL group_replication_compression_threshold= 2097152;
- START GROUP_REPLICATION;
这将压缩阈值设置为 2MB。如果事务生成的有效内容大于 2MB 的复制消息,例如大于 2MB 的二进制日志事务条目,则会对其进行压缩。禁用压缩设置阈值为 0。注意:修改这个阈值是需要重启组复制的。
消息压缩流程图如下:
五. 组复制的要求和限制 1. 限制和要求依据组复制的要求和限制,以下设置根据 MySQL 组复制要求配置复制:
- server_id = 1
- gtid_mode = ON
- enforce_gtid_consistency = ON
- master_info_repository = TABLE
- relay_log_info_repository = TABLE
- binlog_checksum = NONE
- log_slave_updates = ON
- log_bin = binlog
- binlog_format = ROW
此时 my.cnf 文件可确保服务器配置,并指示实例化一个给定的配置下的复制基础设施。以下部分配置服务器的组复制设置。具体参数比较简单,不在这里赘述,可参见官方说明:
六. 组复制的多主和单主模式(Multi-Primary or Single-Primary Mode)
- transaction_write_set_extraction = XXHASH64
- loose-group_replication_group_name ="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
- loose-group_replication_start_on_boot = off
- loose-group_replication_local_address ="127.0.0.1:24901"
- loose-group_replication_group_seeds ="127.0.0.1:24901,127.0.0.1:24902,127.0.0.1:24903"
- loose-group_replication_bootstrap_group = off
组复制分为多主和单主两种模式,默认是单主模式,也是官方推荐的组复制模式。单个集群中不能同时使用两种模式,例如一个配置在多主模式,而另一个在单主模式。要在模式之间切换,需要使用不同的操作配置重新启动集群。无论部署模式如何,组复制不处理客户端故障切换,它必须由应用程序本身、连接器或中间件框架(如代理或路由器)等处理。
1. 单主模式在此模式下,组具有设置为读写模式的单主实例,主节点通常是用于解析组的第一个服务器,组中的其他其他节点都自动设置为只读模式(即,超级只读),所有其他加入的节点自动识别主节点并设置为自己为只读。
在单主机模式下,将禁用在多主机模式下部署的某些检查,因为系统会强制每次只有一个写入节点。例如,允许对具有级联外键的表进行更改,而在多主模式下不允许。在主节点故障时,自动选举机制选择下一个主节点。通过按字典顺序(使用其 UUID)并选择列表中的第一个节点来排序剩余的节点来选择下一个主节点。如果主节点从组中删除,则执行选择,并从组中的其余节点中选择新的主节点,这个选择按照词典顺序排序节点 UUID 并选择第一个来执行。一旦选择了新的主节点,其他节点将设置为从节点,从节点为只读。如下图:
2. 多主模式在多主模式下,没有单个主模式的概念,也没有选举程序,因为没有节点发挥任何特殊的作用。加入组时,所有服务器都设置为读写模式。
在多主要模式下部署时,将检查语句以确保它们与模式兼容。在以多主模式部署组复制时进行以下检查:
1: 如果事务在 SERIALIZABLE 隔离级别下执行,则在将其与组同步时,它的提交将失败。
2: 如果事务对具有级联约束的外键执行,则事务在与组同步时无法提交。
这些检查可通过设置选项停用 group_replication_enforce_update_everywhere_checks 到 FALSE。当在单主要方式部署,该选项必须设置为 FALSE。如下图:
七. 运维相关问题 1. 故障切换问题目前 MySQL 官方没有发布连接组复制专用的客户端(如 MongoDB 连接复制集的客户端),在实际的应用中如果发生故障,需要客户端自己来处理。对于单主模式的话,如果主节点发生故障,客户端需要判断新的主节点是谁,然后把写切换到新的主节点,基本上和当前的异步同步的主从切换一样,并且新的主节点是集群自动产生,不可控;多主模式需要在客户端进行节点可用性检查,当其中的一个写节点不可用时自动使用其他可用节点。
在实际生产中,综合两种组复制模式的故障切换,可以使用多主模式,指定其中一个节点为主节点,其他节点置为只读节点,这样主节点故障时,新的主节点可控。
2. 大事务支持问题目前版本测试并发进行大数据操作和 DDL 操作时,kill 掉大事务,有几率造成集群不可用;在 insert into …….select……limit…… 这种大事务支持不好,可能造成集群不用;多主模式进行 DDL 操作需要集群内所有节点都为 ONLINE 状态才可执行,处于 ERROR 和 RECOVERING 状态时有几率导致集群堵塞,严重时集群不可用。
3. 备份问题在组复制集群其中的一个节点上执行数据库备份时,不管使用 mysqldump(这个不能使用–single-transaction 参数,生产中不建议使用 mysqldump 备份集群数据)或是使用 xtrabackup 集群 MySQL 的 QPS 下降 40%,并且备份节点基本停止读写。在测试备份文件导入数据时,多主模式要比单主模式慢。推荐使用组复制 + 异步复制方式,在异步复制的从节点上进行数据库备份。
4. 二进制日志删除问题因为组复制同步还是基于二进制日志来进行同步的,清理某个节点 bin-log 时,必须判定这个日志文件是否还在使用,如果在使用,则绝对不能删除,如果删除,则整个集群直接 ERROR。
5. 同步延迟问题目前 MySQL5.7.17 的版本中无法直观查看节点同步延迟,也无法获取延迟多少,不管是时间或事物数,这个打开 MySQL 的 Debug 模式,可以获取到节点的延迟事务情况。
组复制的延迟对集群是有影响的,一旦出现延迟(默认延迟 25000 个事务),则启动流量控制(Flow Control),每个周期性能衰减当前的 10%, 直到集群不可用(但集群节点状态为 online),单个节点慢整个集群全慢。
集群中的每个节点都会验证并应用该组提交的事务,有关校验和应用程序过程的统计信息对于了解应用程序队列如何增长,已找到多少冲突,检查了多少事务,在哪里提交了哪些事务等等非常有用。表 performance_schema.replication_group_member_stats 提供与事务认证过程的相关信息,但没有延迟信息。相关字段解释如下:
6. 数据一致性问题不管是多写还是单写,都并非是强一致性,均允许有延迟,他在校验完事务是否冲突后把当前广播到各个节点并确定各个节点收到事务后即进入下一个事物的冲突检测,此时每个节点只是拿到了所有事务的执行序列,保证了事务最终顺序执行,从而保证数据的最终一致性,但同一时刻并非强一致性的。
7. 节点故障脑裂问题节点越多性能损耗越大,三个节点比较合适。节点故障可能有脑裂等问题:如 5 个节点分布在两个机房,机房间网络断掉分为两个部分,2 个集群的机房不可用,3 个节点的可用,而三个节点的机房网络有问题,此时如果想使两个节点的机房可用,需要重新对两个节点做集群重组,三个节点的就无法恢复到两个节点中去;三节点中其中一个节点宕机,其他两个正常节点可用,故障节点重启没有加入到集群时,此时这个节点以单实例存在可读写,此时会发生脑裂。
8. 网络延迟问题测试过程中使用 TC 命令来模拟网络延迟:
- tc qdisc add dev eth0 root netem delay 50ms 10ms 增加网络延迟50ms,10ms左右的浮动
- tc qdisc del dev eth0 root netem delay 50ms 10ms 删除网络延迟
经过测试网络延迟对比组复制 MySQL 的 QPS:网络延迟设置 50ms 和正常的对比,QPS 降低至少 1/3,甚至 1/2,网络延迟对性能影响挺大。以下是测试情况:
9. 弹性扩展问题MySQL 官方网站提到了组复制的弹性自动扩展,经过实际测试,这种扩展在生产中是不现实的。可用于生产的弹性扩展要求新加入一个集群,集群中的数据完全由集群来完成自动同步,但由于组复制是基于二进制日志来进行同步的,生产中是不可能完整保留全部的二进制日志,在新加入的节点需要先备份出集群的全量数据,然后根据同步位置去追事务达到数据的一致后节点状态 online 状态,其实和之前异步同步搭建主从一样。并且官方提示如果恢复时的延迟过大,可能也无法正常达到追到最新数据的位置。
10. 客户端连接问题官方说明中关于故障处理的时候有一句话:组复制不处理客户端故障切换,它必须由应用程序本身,连接器或中间件框架(如代理或路由器)处理。官方一再强调 MySQL 组复制提供了高可用、高弹性、可靠的 MySQL 服务,那么官方是否提供一套类似 MongoDB 复制集的客户端组件来支持那?
目前的解决方法就是和异步复制的切换差不多,使用域名切换或是自己实现一套高可用的客户端连接方式。但就目前来说效率最高的是结合自己的业务,修改组复制故障处理的源码,当检测到写节点故障时结合自己的域名切换来处理。但这样对 DBA 来说需要源码开发能力,相对要求比较高。
11. 查找主节点 IP 问题在单主模式下,不能直观的获取主库的 IP 地址,使用以下命令可以获取到主节点的 UUID:
- mysql> SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME ='group_replication_primary_member';
- + -------------------------------------- +
- | VARIABLE_VALUE |
- + -------------------------------------- +
- | 69e1a3b8-8397-11e6-8e67-bf68cbc061a4 |
- + -------------------------------------- +
- 1行(0,00秒)
使用 SELECT * FROM performance_schema.replication_group_members 可以查看到 UUID 对应到的 MEMBER_HOST,而 MEMBER_HOST 指的是主机名,需要在 MySQL 的配置文件中指定 report-host 为 IP 地址,这样就可以两个表关联查询到主库的 IP 地址。
八. 总结从测试的情况来看,对大事务等的支持不够,运维管理方面做的不友好,相关组复制的配套监控、客户端等不完善,有一部分问题是可以规避和曲线解决的,有一部分需要源码层面的支持;在性能上和 PXC 对比,要优于 PXC,这个和各自的复制协议不同分不开的。
MySQL 组复制提供了高可用、高弹性、可靠的 MySQL 服务,旨在打造金融级 MySQL 集群。在忽略网络延迟的情况,可以轻松的实现多活和异地调用就近写库,这一点是业务上比较期待的。组复制是 MySQL 未来的一个发展趋势,相信在未来的版本中会更加的完善,期待成熟版本。
参考文档:
来源: