MHA 切换的一个“坑”

本文目录:背景测试场景问题分析小结

背景

在一次变更中使用 MHA 进行主从切换,命令如下:


```shell

masterha_master_switch--master_state=alive--conf=/etc/mha/mha3306.cnf--new_master_host=xx.xx.xx.xx--new_master_port=3306--interactive=0--orig_master_is_new_slave

```

然而却遇到了报错,如下:

[error][/usr/share/perl5/vendor_perl/MHA/ServerManager.pm,ln1213]XX.XX.XX.XXis badasanewmaster![error][/usr/share/perl5/vendor_perl/MHA/MasterRotate.pm,ln232]Failed togetnewmaster![error][/usr/share/perl5/vendor_perl/MHA/ManagerUtil.pm,ln177]GotERROR:at/bin/masterha_master_switch line53.

看报错是认为指定的新主是一个bad new master。

遇到这个报错内心是懵的,明明切换前检查集群状态、masterha_check_repl都是正常的。嗯……还是对 MHA 的原理了解不够深入。

当时也没时间去研究为什么报错了,于是就手工切换了,接下来就让我们一起去探索为什么会出现这个报错吧!

说明一下,线上主从集群的环境是这样的:

角色MySQL版本

MMySQL 5.6.40

S1MySQL 5.6.40

S2MySQL 5.7.29

S3MySQL 5.7.29

PS:为什么主从版本会不一致呢?是因为正在做升级,本次切换就是为了将 S2 切换为主,然后将低版本的两个实例升级上去。

测试场景

线上通过手工切换绕过了 MHA 的报错,后面要进行分析具体原因。因为现场环境新主的版本和老主库版本是不一样的,猜想是否 MHA 不支持跨版本切换,之前也没有留意这个问题。于是在测试环境中进行了一波测试,下面列出测试场景和测试结论,有兴趣的可以自己测试一下:

测试场景原master版本新master版本其他slaves 版本切换结果

场景15.6.405.7.29无切换成功

场景25.7.295.6.40无切换成功

场景35.6.405.7.295.6.38切换失败

场景45.6.385.7.295.6.40切换失败

场景55.6.385.7.295.7.29切换成功

现象是这么个现象,是不是很好奇,为什么只有一个从库的时候,跨版本可以切换成功,当还有其他从库的时候某些情况可以切换成功,某些情况又切换失败,往下看吧!

问题分析

先去google一下,搜索关键词:mha .. is bad as a new master,

然后搜出来的并没有我想要的结果,有些参考价值的文章如下:

https://blog.51cto.com/u_860143/2431044 【和我的场景相似,但什么解释也没说】

https://www.modb.pro/db/50655【不太明确,当时没理解】

穷途末路,只能去源码中翻翻了,毕竟 MHA 一款开源的工具【不逼自己一把就不知道自己英文还是不错的】 找到 MHA 选主的相关代码,首先定义了几个数组:

slaves 数组:选取 alive 的 slaves

latest 数组:从 alive slave 中选取复制位点最新的 slaves

pref 数组:配置文件中配置了 candidate_master 的 slaves

bad 数组:后面解释

接着在进行选主的时候按照以下的顺序进行选举:

选举优先级最高的 slave 作为新主(通常是手工切换指定的 new master),如果该 slave 不能作为新主,则报错退出,否则如果是故障切换,则进行下面的步骤

选择复制位点最新并且在 pref 数组里的 slave 作为新主,如果复制位点最新的 slave 不在 pref 数组中,则继续下面步骤

从 pref 中选择一个 slave 作为新主,如果没有选出则继续

选择复制位点最新的 slave 作为新主,如果没有选出则继续

从所有的 slave 中进行选择

经过以上步骤仍然选择不出主则选举失败

注意:前面的6个选举步骤,都需要保证新主不在 bad 数组中


# Picking upnewmaster# If preferred node is specified,oneofactive preferred nodes will benewmaster.# If the latest server behinds toomuch(i.e.stopping sql threadforonline backups),we should not use itasanewmaster,but we should fetch relay log there.Even though preferred master is configured,it does not become a masterifit's far behind.sub select_new_master{my $self=shift;my $prio_new_master_host=shift;my $prio_new_master_port=shift;my $check_replication_delay=shift;$check_replication_delay=1if(!defined($check_replication_delay));my $log=$self->{logger};my @latest=$self->get_latest_slaves();my @slaves=$self->get_alive_slaves();my @pref=$self->get_candidate_masters();my @bad=$self->get_bad_candidate_masters($latest[0],$check_replication_delay);if($prio_new_master_host&&$prio_new_master_port){my $new_master=$self->get_alive_server_by_hostport($prio_new_master_host,$prio_new_master_port);if($new_master){my $a=$self->get_server_from_by_id(\@bad,$new_master->{id});unless($a){$log->info("$prio_new_master_host can be new master.");return$new_master;}else{$log->error("$prio_new_master_host is bad as a new master!");return;}}else{$log->error("$prio_new_master_host is not alive!");return;}}$log->info("Searching new master from slaves..");$log->info(" Candidate masters from the configuration file:");$self->print_servers(\@pref);$log->info(" Non-candidate masters:");$self->print_servers(\@bad);return$latest[0]if($#pref<0&&$#bad<0&&$latest[0]->{latest_priority});if($latest[0]->{latest_priority}){$log->info(" Searching from candidate_master slaves which have received the latest relay log events..")if($#pref>=0);foreach my$h(@latest){foreach my$p(@pref){if($h->{id}eq $p->{id}){return$hif(!$self->get_server_from_by_id(\@bad,$p->{id}));}}}$log->info("  Not found.")if($#pref>=0);}#newmasteris not latest  $log->info(" Searching from all candidate_master slaves..")if($#pref>=0);foreach my$s(@slaves){foreach my$p(@pref){if($s->{id}eq $p->{id}){my $a=$self->get_server_from_by_id(\@bad,$p->{id});return$sunless($a);}}}$log->info("  Not found.")if($#pref>=0);if($latest[0]->{latest_priority}){$log->info(" Searching from all slaves which have received the latest relay log events..");foreach my$h(@latest){my $a=$self->get_server_from_by_id(\@bad,$h->{id});return$hunless($a);}$log->info("  Not found.");}# noneoflatest servers can not be a master  $log->info(" Searching from all slaves..");foreach my$s(@slaves){my $a=$self->get_server_from_by_id(\@bad,$s->{id});return$sunless($a);}$log->info("  Not found.");return;}

因为报错是说新主是 bad ,那我们重点看下新主为什么会被判定为 bad ,如何判定的。获取 bad 列表的函数是get_bad_candidate_masters,如下,可以看出具有以下五种情况的 slave 会被判定为 bad :

dead servers

{no_master} >= 1【在配置文件中设置了no_master】

log_bin is disabled【未开启binlog】

{oldest_major_version} eq '0'【MySQL major 版本不是最旧的】

too much replication delay【延迟大,与 master 的 binlog position 差距大于 100000000】

# The following servers can not be master:#-dead servers#-Set no_masterinconffiles(i.e.DRservers)#-log_bin is disabled#-Major version is not the oldest#-too much replication delaysubget_bad_candidate_masters($$$){my $self=shift;my $latest_slave=shift;my $check_replication_delay=shift;my $log=$self->{logger};my @servers=$self->get_alive_slaves();my @ret_servers=();foreach(@servers){if($_->{no_master}>=1||$_->{log_bin}eq'0'||$_->{oldest_major_version}eq'0'||($latest_slave&&($check_replication_delay&&$self->check_slave_delay($_,$latest_slave)>=1))){push(@ret_servers,$_);}}return@ret_servers;}

对于1-3,5很好理解,而且线上后来通过监控进行了排查,并不存在这些问题,于是重点看下4是如何来进行定义的。

找到相关的函数:

subcompare_slave_version($){my $self=shift;my @servers=$self->get_alive_servers();my $log=$self->{logger};$log->debug(" Comparing MySQL versions..");my $min_major_version;foreach(@servers){my $dbhelper=$_->{dbhelper};--如果dead或不为从库,则跳过判断    nextif($_->{dead}||$_->{not_slave});my $parsed_major_version=MHA::NodeUtil::parse_mysql_major_version($_->{mysql_version});if(!$min_major_version||$parsed_major_version<$min_major_version){$min_major_version=$parsed_major_version;}}foreach(@servers){my $dbhelper=$_->{dbhelper};nextif($_->{dead}||$_->{not_slave});my $parsed_major_version=MHA::NodeUtil::parse_mysql_major_version($_->{mysql_version});if($min_major_version==$parsed_major_version){$_->{oldest_major_version}=1;}else{$_->{oldest_major_version}=0;}}$log->debug("  Comparing MySQL versions done.");}

可以看到,这里首先会从 alive_servers 中获取最小的版本,也就是min_major_version:

如果实例是 dead 或非从库,则不比较该实例,否则进行比较,关键代码next if (−>dead||

接下来,根据传入的 server 的parsed_major_version【MySQL 的主版本,例如,5.6,5.7】和min_major_version进行对比:

如果parsed_major_version==min_major_version,则oldest_major_version=1;否则oldest_major_version=0

综上可以看出,新主的版本号,需要是所有从库中版本最低的才能作为新的主库,否则将不能作为新的主库。

到这里,问题就水落石出了,回到我们前面测试的场景中,就弄明白了:

场景1和场景2只有一个从库的时候,跨版本切换可以切换成功,是因为这个从库的主版本就是 min_major_version

场景3和场景4中切换失败的原因是,新主的主版本为5.7,而所有从库中最小的主版本号为5.6,因此不能切换

但是,MHA 为什么会这样设计呢?

MySQL 源端(master)低版本到目标端(slave)高版本数据复制是没有问题,源端(master)高版本到目标端(slave)数据复制可能会出现问题。即:5.7可以作为8.0版本的从库,5.6可以作为5.7的从库;但是8.0作为5.7或者5.7作为5.6的从库就会有问题。这个在官方有介绍:https://dev.mysql.com/doc/refman/5.7/en/replication-compatibility.html

不过 MHA 在比较最小版本的时候没有比较原主库的版本,这在切换的时候还是可能会出现低版本向高版本复制的情况,比如测试场景1,不知道是基于什么考虑,欢迎大家留言讨论。

小结

MHA 选主逻辑:

选举优先级最高的 slave 作为新主(通常是手工切换指定的 new master),如果该 slave 不能作为新主,则报错退出,否则如果是故障切换,则进行下面的步骤

选择复制位点最新并且在设置了 candidate_master 的 slave 作为新主,如果复制位点最新的 slave 没有设置 candidate_master ,则继续下面步骤

从设置了 candidate_master 中选择一个 slave 作为新主,如果没有选出则继续

选择复制位点最新的 slave 作为新主,如果没有选出则继续

从所有的 slave 中进行选择

经过以上步骤仍然选择不出主则选举失败

注意:前面的6个选举步骤,都需要保证新主不在bad数组中

bad 数组定义如下:

dead servers

{no_master} >= 1【在配置文件中设置了no_master】

log_bin is disabled【未开启binlog】

{oldest_major_version} eq '0'【MySQL major 版本不是最旧的】

too much replication delay【延迟大,与 master 的 binlog position 差距大于 100000000】

其中4这个是比较容易忽视的一点,需要注意!

转载于https://cloud.tencent.com/developer/article/1875033

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容