MGR 实践及常见问题分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MGR 实践及常见问题分析相关的知识,希望对你有一定的参考价值。

参考技术A 来也科技使用 MGR (mysql Group Replication)作为私有部署时 MySQL 的高可用架构,一年多以来,服务众多用户,稳定性得到了极大的保障。

本文记录自内部分享,需要一定的 MySQL 基础。欢迎大家在评论区讨论、交流~

MySQL Group Replication(简称 MGR), MySQL 组复制。

MGR 是 MySQL 官方推出的一种基于 paxos 协议的状态机复制,实现了分布式下数据的最终一致性。MySQL 组复制提供了高可用、高扩展、高可靠的 MySQL 集群解决方案。

MGR 支持两种模式:单主、多主。其中单主模式是官方推荐的,也是在来也科技内部广泛应用的,我们接下来的内容都是以单主模式为背景的。

可能有同学会质疑,公有云上的高可用架构大多是主从啊,为什么来也科技要选择 MGR ?

那首先,我们就要知道 “主从” 和 MGR 的优缺点分别是什么?

来也科技作为一家重心放在 ToB 的企业,我们着重考虑私有部署时,如何保证客户环境的可用性。客户的环境,我们看不到,更无法控制,在这样的情况下,我们会尽量求稳。

维护主从高可用架构的应用,通常面向大规模集群运维,往往会引入复杂的交互和新的中间件。对于我们这种只需要部署一套集群的 ToB 需求,显然是不合适的。

综合考虑之下,我们选择 MGR 作为私有部署时的高可用架构。

    · 最少 3 个节点,最多 9 个节点

    · 节点越多,容错性越强,但交互越多,可能会影响效率

    · 存储引擎只能使用 InnoDB

    · 表中必须带有主键

    · 一次只能加入一个节点。如果一次性添加多个节点,可能能执行成功,也可能会报错

    · 只支持 IPv4 网络

    · 必须使用 row-base 格式的binlog

    · MGR 模式下不支持过滤指定信息的操作

来也科技在给客户做私有部署时,如果客户提供 MySQL 高可用环境,那么会直接使用客户的环境。

否则,部署团队将为客户搭建三节点的 MGR 高可用架构。整体架构图:

如图所示,在应用和 MGR 集群的中间层选用多节点 proxysql 做路由,配置读写分离和故障检测机制,确保读写流量分发正确。proxysql 只要有一个节点存活,即可正常提供服务。(除非扛不住压力!)

在这种架构下,我们可以实现:

    · 如果主库发生故障 -- 自动切换    

    · 如果从库发生故障 -- 将其下线

对于底层的 MGR 集群,宕机一个 mysql 实例时,不影响业务正常使用;宕机两个 mysql 实例时,集群只可读,不可写(因为此时已经无法满足 paxos 的多数投票要求)

首先,我们以一个“一波三折”的场景为例,切实感受下 MGR 是如何运作的。

假设存在一个三节点的 MGR 集群,三个节点分别部署在三台实例上。

此时,应用发起一个事务执行请求。 单主模式下,只有primary node 可以接收请求:

   1. primary 接受到请求后,想组所有成员同步请求,进行事务认证。事务认证包含 3 个部分:

        1-1. 冲突检测

        1-2. gtid分配器

        1-3. 事务组提交信息分配器

    2. 如果检测失败

        2-1. primary:回滚

        2-2. secondary:丢弃  binlog event (冲突检测时带来的)

    3. 如果检测成功:

        3-1. primary :记 binlog, 分配 GTID

        3-2. secondary:将 binlog event (冲突检测时带来的)信息写入 relay log

    4. secondary 应用 relay log ,执行 sql ,并记录日志(通常情况下,我们建议将集群内所有 node 都配置在 seed 中,即:每个节点都可能成为 donor ,所以都要记 binlog)

以上,就是事务在 MGR 中完整的执行流程。

假设,运行一段时间后,其中某个 secondary 节点因服务器宕机原因脱离集群,且长时间未被发现。直至节前巡检时,才被报出来。

补充说明:来也监控项目已经提上日程了,部署后,便不会存在这个风险了。

此时,售后同学重启服务器,启动 mysql 实例(假设顺利启动),执行 start group_replication 操作。

这个命令会执行什么操作呢?

本地恢复:应用自己本地 relay log 中的日志

全局恢复:从集群中活跃的节点中任选一个作为 donor,用 recovery 线程 dump 它的binlog,来恢复自己的数据

恢复线程 requestdump 建立与 donor 的复制关系,该函数携带了待恢复节点的 gtid_executed 信息。

donor 端逆序遍历 binlog 文件,通过判断 binlog 文件的 Previous-GTIDs 来找到第一个不属于 gtid_executed 的事务,从该事务开始进行数据复制。

命令执行过后,观察 replication_group_members 表的数据,如果目标节点的状态是 ONLINE,则视为集群恢复成功。

但我们假设此操作并未成功,日志中有报错:the master has purged binary logs containing GTIDs that the slave requires

这个错误的意思是:节点脱离集群时间过长,已无法通过现存节点的 binlog 直接恢复了。

重做数据。当然是重做数据!

我们需要清除这个下线节点的信息,创建一个新的实例,选择任一正常节点进行数据全量备份,并将其应用至新实例上。

全量数据恢复完成后,执行 start group_replication 命令,开启 MGR 同步,追平数据后,MGR 会将节点状态置为 ONLINE,然后开始正常接收流量请求。

在传统的主从复制中,DBA 需要在 change master 时手动设置增量同步开始的 GTID(更早版本需要指定 binlog file 和 position)。

但是部署过 MGR 的同学会发现,MGR 同步时只需要指定用户名密码即可。那它是怎么找点的呢?

不同的备份方式,对应着不同的找点方式,接下来我们以 mysqldump 为例,详细的讲解下这个过程。

mysqldump 全量数据,使用 gtid_purged 记录备份时已经执行过的事务:

mysqldump --set-gtid-purged=ON --single-transaction --all-databases -uroot -p -h 127.0.0.1 > mgr.sql

查看生成的 sql 文件,可以看到开始处有相关标识:

在新节点上使用如下命令清空 GTID 信息:

STOP GROUP_REPLICATION;

reset master;

接下来,应用文件 source mgr.sql,红框圈起来的语句会被一起执行。

完成后,开启 MGR 同步,就会自动跳过这些标识为 purged 的事务了。

当有成员加入或退出时,组会自动调整自身结构:

    · 成员的加入和退出需走 paxos  协议,由多数成员同意后方可执行成功

    · 如果多数节点已经处于离线状态了,那么不可执行主动离组操作(投票会无法通过)。

离组分为主动离组和被动离组:

    · 主动离组:

            只有执行 stop replication 命令才算主动离组

            相当于总数变为 n-1

            无论主动离组多少成员,都不会影响投票 

    · 被动离组:

            因故障等原因导致意外下线

            总数不变

            影响投票

            当有多数节点被动离组后,集群不可用

    1. group_replication_applier:该通道用于回放本地 relay log 中的日志。

    2. group_replication_recovery:当有节点加入 Group 时,需要用到另一个复制通道 group_replication_recovery,它是一个传统的 Master-Slave 异步复制通道。

MGR 常见的调优方式有三种,分别是:

    并行复制

    压缩

    调整 GCT

如果设置了并行复制 slave_parallel_workers,那么这些参数也要被设置,用于确保所有的组成员按照相同的顺序执行并提交事务:

    slave_preserve_commit_order=1

    slave_parallel_type=LOGICAL_CLOCK

压缩语句:

STOP GROUP_REPLICATION;

SET GLOBALgroup_replication_compression_threshold = 2097152;

START GROUP_REPLICATION;

超出这个指定范围,就不压缩了。

官方文档中提示:当网络带宽成为瓶颈时,通过压缩消息,可以提高集群整体 30% - 40% 的性能。

mysql> SET GLOBAL group_replication_poll_spin_loops= 10000;

GCT接收来自组和 MGR 插件的消息,处理与仲裁和故障检测相关的任务,发送一些保活的通讯消息,还处理 MySQLServer 与组之间传入和传出的事务。GCT 会等待队列中的传入消息。当队列中没有消息时,GCT将会进行等待。

在某些情况下,通过将这个等待配置得稍微长一些(进行主动等待),可以减少操作系统执行上下文切换时从处理器中换出GCT线程的次数。

常见的安全手段有两种:

    1. 控制节点白名单。设置 group_replication_ip_whitelist 参数,不再此范围的节点,不允许加入

    2. 配置 SSL:

         new_member> SET GLOBAL group_replication_recovery_use_ssl=1;

        new_member> SET GLOBAL group_replication_recovery_ssl_ca= '.../cacert.pem';

        new_member> SET GLOBAL group_replication_recovery_ssl_cert= '.../client-cert.pem';

        new_member> SET GLOBAL group_replication_recovery_ssl_key= '.../client-key.pem';

MySQL 5.7.22 之前,可能会出现数据不一致的风险。这种情况发生在成员短暂离组,在组感知前,自己又重新加入组内的时候,官方文档描述如下:

In this situation, the rejoining member forgets its previousstate, but if other members send it messages that are intended for itspre-crash state, this can cause issues including possible data inconsistency.

To counter this possibility, from MySQL 5.7.22, servers aregiven a unique identifier when they join a group . This enables GroupReplication to be aware of the situation where a new incarnation of the sameserver (with the same address but a new identifier) is trying to join the groupwhile its old incarnation is still listed as a member. The new incarnation isblocked from joining the group until the old incarnation can be removed by areconfiguration. If Group Replication is stopped and restarted on the server,the member becomes a new incarnation and cannot rejoin until the suspiciontimes out.

精准的定位问题是解决问题和优化系统的前提!当集群故障时,我们首先要判断集群当前处于什么状态,优先恢复使用,尽可能地保留现场进行故障排查和恢复。

所有节点上执行查看集群成员信息命令:

Q: 为什么要在所有节点上执行?

A: 因为可能会发生网络分区,出现若干节点互联,分成若干集群的情况。集群一旦分裂,互相感知不到对方存在。

以三个节点为例,常见分裂拓扑图:

如果大多数节点存活(n=2f + 1 ) 且在同一个 group 中提供服务,我们认为,这仍是一个可用集群。

即:同一个 group 中 node 数大于 f ,且状态都是 ONLINE 的情况下,认定集群可用。

此时,优先使用可用集群提供服务,并进行后续问题排查。

主节点提供读写服务,从节点提供只读服务。

无论是应用直连,还是通过代理层(proxy 等)进行连接,当务之急都是确认主节点是谁。

命令:select MEMBER_HOST, MEMBER_PORT,MEMBER_STATE from performance_schema.replication_group_members m inner join performance_schema.global_status g on m.MEMBER_ID=g.VARIABLE_VALUE and VARIABLE_NAME = 'group_replication_primary_member';

知晓谁是主节点,谁是从节点后,应用或者代理,就可以进行相应的配置。

错误日志!错误日志!错误日志!

有故障,第一时间查看错误日志。错误日志存放位置:

打开日志文件,查看错误信息。

注意: 错误信息,不仅要在出故障的实例上查看。因为 MGR 是”大多数原则“,所以每一个实例上的错误信息,都可能不是完全的。

比如实例意外 down 掉,此时故障实例本身就可能来不及记录错误日志。

除了实例级别的错误日志外,MGR 视图还可能自己记录最后的错误信息,通常日志中会有相关提示。比如,日志中存在:

[ERROR] Plugin group_replication reported: 'For details please check performance_schema.replication_connection_status table and error log messages of Slave I/O for channel group_replication_recovery.'

常见错误:[GCS]Connection attempt from IP address 192.168.9.208 refused

出现场景:MGR 白名单设置不正确

解决办法:查看该实例所在的网段,是否在 MGR 的白名单中

命令:show global variables like 'group_replication_ip_whitelist';

通常我们建议直接设置成全网段访问,由其他层(region、安全组等)控制连通性。

不排除私有部署的客户对于安全性要求极高,要求设置指定网段访问,那么此时,就要查看一下实例是否存在于这个白名单中。

注意:MGR 中的节点,每个白名单配置都可能不一样(强烈不建议!建议配置成一致的!),所以,要把集群中所有的节点都查一遍,避免出现后续问题。

常见错误:This member has more executed transactions than those present in the group.

出现场景:

    1. 有应用直连,往里写数据

    2. 有人误在从库上直接操作

    3. 错误的恢复手段:这个最为常见

解决办法:

    1. 查看哪些数据被错误应用,进行相应处理

    2. 找到最后同步的 GTID 位点

    3. reset master

    4. 设置最后 gtid_purged 为要继续同步的 GTID 位点

常见错误:Member was expelled from the group due to network failures, changing member status to ERROR

出现场景:网络延迟

解决方法:等待网络恢复正常后,重新加入集群即可

常见错误:the master has purged binary logs containing GTIDs that the slave requires

出现场景:从库脱离群组时间太长,无法通过已有的 binlog 进行恢复了

解决方法:重做数据(mysqldump 或 xtrabackup)后,加入群组

select TABLE_SCHEMA, TABLE_NAME from information_schema.tables where TABLE_SCHEMA not in ('information_schema','mysql', 'performance_schema', 'test', 'sys') and  TABLE_NAME not in (select  table_name from information_schema.TABLE_CONSTRAINTS  where TABLE_SCHEMA not in ('information_schema','mysql', 'performance_schema', 'test', 'sys') and CONSTRAINT_TYPE = 'PRIMARY KEY');

select TABLE_SCHEMA, TABLE_NAME from information_schema.tables where TABLE_SCHEMA not in ('information_schema','mysql', 'performance_schema', 'test', 'sys')  and engine <> 'InnoDB';

本文作者、编辑:刘桐烔

欢迎大家点赞、在看、关注~

MGR中gtid_executed不连续的问题分析

* GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。

* 阅读文章,参与下方抽奖,有机会赢取精美礼品一份。

1、问题描述

在做MGR测试的时候偶尔遇到gtid_executed事务ID不连续的问题,但是并不影响数据库的正常运行。现象如下

GreatDB Cluster[sysbench]> select @@gtid_executed;
+-----------------------------------------------------------------+
| @@gtid_executed                                                 |
+-----------------------------------------------------------------+
| 5cd1a64d-7358-11ec-b349-080027fa2d35:1-1286:2052-2290:3052-3277 |
+-----------------------------------------------------------------+
1 row in set (0.00 sec)

2、确认原因

查看官方文档发现以下参数:group_replication_gtid_assignment_block_size

以下是对官方文档的翻译和理解

group_replication_gtid_assignment_block_size为每个成员保留的连续GTID数。每个成员从中进行消耗,并在其需要的时候获取更多的GTID数(类似于分布式事务中的全局序列,该系统变量设置的值表示每个成员每一次从全局序列中获取多大范围的连续数字范围来作为自身写事务的GTID号)。

该系统变量是组范围的配置设置,它必须在所有组成员上设置相同的值,在组复制运行时不允许修改,要使得修改值生效,需要完全重新引导组(使用系统变量group_replication_bootstrap_group= on来重新引导组)。

全局变量,动态变量,整型类型,默认值为1000000,取值范围:32位平台为1~4294967295,64位平台为1~9223372036854775807,MySQL 5.7.17版本引入。

官方文档地址:https://dev.mysql.com/doc/refman/8.0/en/group-replication-options.html

通过对文档了解到MGR会为每个实例节点分配一段连续的GTID值,所以怀疑是MGR发生了主从切换,从而导致GTID的事务ID不连续。

2.1、复现问题

为方便测试修改group_replication_gtid_assignment_block_size为1000,并重启MGR

GreatDB Cluster[(none)]> set persist group_replication_gtid_assignment_block_size=1000;
Query OK, 0 rows affected (0.00 sec)

GreatDB Cluster[(none)]> set global group_replication_bootstrap_group=on;
Query OK, 0 rows affected (0.00 sec)

GreatDB Cluster[(none)]> start group_replication;
Query OK, 0 rows affected (2.45 sec)

GreatDB Cluster[(none)]> set global group_replication_bootstrap_group=off;
Query OK, 0 rows affected (0.00 sec)

把82切换为主节点然后通过sysbench模拟业务

GreatDB Cluster[sysbench]> select @@gtid_executed;
+---------------------------------------------+
| @@gtid_executed                             |
+---------------------------------------------+
| 5cd1a64d-7358-11ec-b349-080027fa2d35:1-5445 |
+---------------------------------------------+
1 row in set (0.00 sec)

GreatDB Cluster[sysbench]> select * from performance_schema.replication_group_members;
+---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST  | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
+---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+
| group_replication_applier | cf43d5d7-7354-11ec-af9a-080027fa2d35 | 172.16.50.81 |        4444 | ONLINE       | PRIMARY     | 8.0.25         |
| group_replication_applier | cf520b3b-7354-11ec-b785-08002792d155 | 172.16.50.82 |        4444 | ONLINE       | SECONDARY   | 8.0.25         |
| group_replication_applier | cf85763c-7354-11ec-898d-0800276e4bea | 172.16.50.83 |        4444 | ONLINE       | SECONDARY   | 8.0.25         |
+---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+
3 rows in set (0.00 sec)

GreatDB Cluster[sysbench]> SELECT group_replication_set_as_primary('cf520b3b-7354-11ec-b785-08002792d155');
+--------------------------------------------------------------------------+
| group_replication_set_as_primary('cf520b3b-7354-11ec-b785-08002792d155') |
+--------------------------------------------------------------------------+
| Primary server switched to: cf520b3b-7354-11ec-b785-08002792d155         |
+--------------------------------------------------------------------------+
1 row in set (1.38 sec)

GreatDB Cluster[sysbench]> select * from performance_schema.replication_group_members;
+---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST  | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
+---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+
| group_replication_applier | cf43d5d7-7354-11ec-af9a-080027fa2d35 | 172.16.50.81 |        4444 | ONLINE       | SECONDARY   | 8.0.25         |
| group_replication_applier | cf520b3b-7354-11ec-b785-08002792d155 | 172.16.50.82 |        4444 | ONLINE       | PRIMARY     | 8.0.25         |
| group_replication_applier | cf85763c-7354-11ec-898d-0800276e4bea | 172.16.50.83 |        4444 | ONLINE       | SECONDARY   | 8.0.25         |
+---------------------------+--------------------------------------+--------------+-------------+--------------+-------------+----------------+
3 rows in set (0.00 sec)

模拟业务在新的主节点写业务

# sysbench ./oltp_read_write.lua --mysql-db=sysbench --mysql-host=172.16.50.82 --mysql-port=4444 --mysql-user=greatdb --mysql-password=greatdb --tables=3 --table_size=1000000 --report-interval=2 --threads=3 --db-driver=mysql --skip-trx=off --db-ps-mode=disable --create-secondary=off  --time=10 --mysql-ignore-errors=9001,9002,9000,1062,8530,8532 run

检查当前的gtid_executed

GreatDB Cluster[sysbench]> select @@gtid_executed;
+-------------------------------------------------------+
| @@gtid_executed                                       |
+-------------------------------------------------------+
| 5cd1a64d-7358-11ec-b349-080027fa2d35:1-5445:6053-6268 |
+-------------------------------------------------------+
1 row in set (0.00 sec)

2.2、空洞对实际使用的影响

模拟业务持续在82节点写业务

sysbench ./oltp_read_write.lua --mysql-db=sysbench --mysql-host=172.16.50.82 --mysql-port=4444 --mysql-user=greatdb --mysql-password=greatdb --tables=3 --table_size=1000000 --report-interval=2 --threads=3 --db-driver=mysql --skip-trx=off --db-ps-mode=disable --create-secondary=off  --time=100 --mysql-ignore-errors=9001,9002,9000,1062,8530,8532 run

检查当前的gtid_executed,一段时间后gtid_executed恢复连续

GreatDB Cluster[sysbench]> select @@gtid_executed;
+-------------------------------------------------------+
| @@gtid_executed                                       |
+-------------------------------------------------------+
| 5cd1a64d-7358-11ec-b349-080027fa2d35:1-5451:6053-6659 |
+-------------------------------------------------------+
1 row in set (0.00 sec)

GreatDB Cluster[sysbench]> select @@gtid_executed;
+---------------------------------------------+
| @@gtid_executed                             |
+---------------------------------------------+
| 5cd1a64d-7358-11ec-b349-080027fa2d35:1-6665 |
+---------------------------------------------+
1 row in set (0.01 sec)

解析binlog,分析gtid生成时间

SET @@SESSION.GTID_NEXT= '5cd1a64d-7358-11ec-b349-080027fa2d35:6268'/*!*/;
# at 173167217
#220112 15:12:29 server id 1  end_log_pos 173167287     Query   thread_id=19    exec_time=0     error_code=0

SET @@SESSION.GTID_NEXT= '5cd1a64d-7358-11ec-b349-080027fa2d35:6269'/*!*/;
# at 173169472
#220112 15:14:29 server id 1  end_log_pos 173169542     Query   thread_id=19    exec_time=1     error_code=0

SET @@SESSION.GTID_NEXT= '5cd1a64d-7358-11ec-b349-080027fa2d35:6659'/*!*/;
# at 174048922
#220112 15:14:50 server id 1  end_log_pos 174048992     Query   thread_id=19    exec_time=0     error_code=0

SET @@SESSION.GTID_NEXT= '5cd1a64d-7358-11ec-b349-080027fa2d35:6660'/*!*/;
# at 175419962
#220112 15:15:04 server id 1  end_log_pos 175420032     Query   thread_id=19    exec_time=0     error_code=0


SET @@SESSION.GTID_NEXT= '5cd1a64d-7358-11ec-b349-080027fa2d35:5445'/*!*/;
# at 172680137
#220112 15:06:15 server id 1  end_log_pos 172680207     Query   thread_id=19    



SET @@SESSION.GTID_NEXT= '5cd1a64d-7358-11ec-b349-080027fa2d35:5446'/*!*/;
# at 174051177
#220112 15:14:50 server id 1  end_log_pos 174051247     Query   thread_id=19

分析发现事务id先从6268增长到6659然后开始补空洞补完后继续从6659开始增长

3、总结

group_replication_gtid_assignment_block_size为每个成员保留的连续GTID数。每个成员从中进行消耗,并在其需要的时候获取更多的GTID数(类似于分布式事务中的全局序列,该系统变量设置的值表示每个成员每一次从全局序列中获取多大范围的连续数字范围来作为自身写事务的GTID号)。

举个例子,集群中有2个节点,group_replication_gtid_assignment_block_size为1000,那么为节点A分配的Gtid_set为group_name:1-1000,节点B分配的Gtid_set为group_name:1001-2000。则group_name:1-1000和group_name:1001-2000分别作为Gtid_set保存在member_gtids上。A节点的事务T1认证通过后,分配gtid为group_name:1,接着A节点事务T2分配group_name:2,然后B节点事务进入认证模块,认证通过后,为其分配group_name:1001,每分配一次gtid则gtids_assigned_in_blocks_counter增一。当发生主从切换时候,节点B会从1001开始记录gtid,所以会造成MGR的gtid_executed有时是不连续的多段,如aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-2:1001-1005

若分配次数gtids_assigned_in_blocks_counter已达到gtid_assignment_block_size,则需要compute_group_available_gtid_intervals()重新计算。基于member_uuid找到该成员可用的gtid区间,若还没为该成员分配gtid,则调用reserve_gtid_block()进行分配。需要注意的是,reserve_gtid_block()是最多分配而不是一定分配block_size大小的gtid序列,是否等于block_size依赖于group_available_gtid_intervals的第一个可用的连续gtid序列大小是否等于或大于block_size。

Enjoy GreatSQL :)


《深入浅出MGR》视频课程

戳此小程序即可直达B站

https://www.bilibili.com/medialist/play/1363850082?business=space_collection&business_id=343928&desc=0



文章推荐:


想看更多技术好文,点个“在看”吧!

以上是关于MGR 实践及常见问题分析的主要内容,如果未能解决你的问题,请参考以下文章

MGR MYSQL 集群崩溃恢复,及非正常手段修复

深度解读Paxos在MGR中的工程实践

MGR及GreatSQL资源汇总

events scheduler导致MGR节点退出详解及修复

events scheduler导致MGR节点退出详解及修复

数据分析实践:遇到的问题及感想