版权声明:本文为博主原创文章,转载请注明出处:https://twocups.cn/index.php/2021/07/02/44/
gtid主从不一致情况分类
- 从库gtid多于主库(一般是从库开启了写操作)
- 从库gtid少于主库(一般是发生了宕机)
- 从库上没有全部的主库已经删除的gtid
gtid主从不一致情况扫描脚本逻辑
我写了一个 gtid 主从不一致扫描脚本,运行在集群管控节点上,每天扫描一下集群内的 gtid 异常情况。企业实际环境中不是很推荐像我一样用 shell 脚本去写,因为太依赖机器环境了。最好使用 Go 之类的语言去写,而且 Go 本身也有很多封装好的库,写起来会更方便。并且,用 Go 写之后还可以和公司内部的一些巡检或监视平台配合。
我说一下脚本的扫描逻辑。首先,在集群管控节点上搜索集群中所有的 mysql 实例 id,以及主从库的 ip 地址和端口,然后对每个实例运行以下扫描逻辑。
1. 前置检查
- 检查gtid模式是否开启
- 检查数据库类型
首先,我们要检查gtid是否开启,如果没有开启就直接报错,并结束这个实例的扫描。
2. 采集gtid
- 采集从库gtid和uuid
- 停顿
- 采集主库gtid和uuid
采集从库 gtid 一定要在主库前面,从而保证主库的gtid比从库更新,否则可能会出现从库gtid跑在主库前面的奇怪情况。因为我们集群比较大,所以向主从库请求gtid会有一定程度的延迟,还是会有可能引发从库gtid比主库更新的情况,所以我还是在中间停顿了0.1s,保证了主库gtid一定比从库的更新。
3. 查找多的gtid
- 采集从库比主库多的gtid
- 第一次采集主库比从库多的gtid
- 停顿
- 第二次采集主库比从库多的gtid
- 根据两次采集的结果,计算出主库比从库多的gtid中异常的那部分
- 分析主库比从库多的gtid
- 分析从库比主库多的gtid
看到这个逻辑,一定会想到一个问题:为什么采集从库比主库多的 gtid 只需要采集一次,而采集主库比从库多的 gtid 要采集两次呢?
从库比主库多的 gtid 只需要采集一次是因为,从库的 gtid 本身就不应该比主库多,从库的 gtid 应该和主库是一样的,或者因为延迟,主库的 gtid 比主库稍微慢一些。如果从库的 gtid 比主库多,那么多出来的那部分一定是有问题的 gtid。
但是主库 gtid 比从库多是正常的,因为我们是先采集从库再采集主库,这就导致主库的 gtid 比从库的更新。在我们采集两者的间隔,主库也可能在源源不断地产生新的 gtid,所以主库的 gtid 会比从库更多。那我们可以先采集主库再采集从库吗?那更不行,否则你就会看到从库的 gtid 跑到主库前面去,这更离谱。
所以,我们需要采集两次主库比从库多出的 gtid,并且在两次采集中加一个时间间隔,目的是区分出所有的主库正在增长的 gtid。这里我们用到了 mysql 提供的一个关于 gtid 的函数。
select gtid_subtract('A', 'B')
这个函数的作用是求出 gtid 集合 A 与 gtid 集合 B 的差值,具体来说,是求出 A 集合中有、而 B 集合中没有的 gtid。换言之,这个函数可以在 A 集合中去掉 A 和 B 集合相同的 gtid。
好消息是,这个函数非常方便。如果没有这个函数,我们就需要去比较 gtid 的字符串了,非常麻烦。
坏消息是,关于 gtid 的函数,mysql 就提供了这么一个。我们能用的也只有它。
我们通过连续使用两次这个函数,就可以得到两次采集的主库比从库多的 gtid 结果中重合的那一部分,这就是主库 gtid 比从库多的 gtid 中异常的那部分。
这里其实存在一个问题,那就是两次采集重合的部分,不一定是异常的部分,还可能是正在正常增长的 gtid。所以我们采集两次的时间间隔可以长一些,我自己设置的是0.5s。如果两次采集的部分中仍然出现了正常增长的 gtid,那么说明主从库存在延迟大的问题。检测主从库延迟是否过大,也正是我们设置时间间隔的第二个原因。
4. 查找主库中已经删除但从库中未出现过的gtid
- 采集从库接收到但未执行的 gtid + 从库已执行的 gtid(两者是有重合的)
- 分离出从库收到但未执行的 gtid
- 采集从库已删除的 gtid
- 采集主库已删除的 gtid
- 从主库已删除的 gtid 中依次过滤掉从库中出现过的 gtid
我没找到单独采集从库接收到但未执行的 gtid 的方法,我只能从 mysql 指令“show slave status”里面的参数“Retrieved_Gtid_Set”里面得知该数据。但这个参数又不能单独采集,所以我只能连它和“Executed_Gtid_Set”一起采集了。但是它们俩的 gtid 一般来说都是有重合的,不能直接使用函数 gtid_subtract。
我又观察到它们集合中的 gtid 之间除了逗号还多有一个空格,并且“Retrieved_Gtid_Set”的值和“Executed_Gtid_Set”的值两段数据之间没有逗号只有空格。那我就可以通过处理字符串的方法,将从库接收到但未执行的 gtid 分离出来。