本文目錄:背景測(cè)試場(chǎng)景問題分析小結(jié)
背景
在一次變更中使用 MHA 進(jìn)行主從切換蛔翅,命令如下:
```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
```
然而卻遇到了報(bào)錯(cuò),如下:
[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.
看報(bào)錯(cuò)是認(rèn)為指定的新主是一個(gè)bad new master虏劲。
遇到這個(gè)報(bào)錯(cuò)內(nèi)心是懵的郑口,明明切換前檢查集群狀態(tài)有梆、masterha_check_repl都是正常的骆撇。嗯……還是對(duì) MHA 的原理了解不夠深入。
當(dāng)時(shí)也沒時(shí)間去研究為什么報(bào)錯(cuò)了羽德,于是就手工切換了几莽,接下來就讓我們一起去探索為什么會(huì)出現(xiàn)這個(gè)報(bào)錯(cuò)吧!
說明一下宅静,線上主從集群的環(huán)境是這樣的:
角色MySQL版本
MMySQL 5.6.40
S1MySQL 5.6.40
S2MySQL 5.7.29
S3MySQL 5.7.29
PS:為什么主從版本會(huì)不一致呢章蚣?是因?yàn)檎谧錾?jí),本次切換就是為了將 S2 切換為主姨夹,然后將低版本的兩個(gè)實(shí)例升級(jí)上去纤垂。
測(cè)試場(chǎng)景
線上通過手工切換繞過了 MHA 的報(bào)錯(cuò),后面要進(jìn)行分析具體原因磷账。因?yàn)楝F(xiàn)場(chǎng)環(huán)境新主的版本和老主庫版本是不一樣的峭沦,猜想是否 MHA 不支持跨版本切換,之前也沒有留意這個(gè)問題逃糟。于是在測(cè)試環(huán)境中進(jìn)行了一波測(cè)試吼鱼,下面列出測(cè)試場(chǎng)景和測(cè)試結(jié)論,有興趣的可以自己測(cè)試一下:
測(cè)試場(chǎng)景原master版本新master版本其他slaves 版本切換結(jié)果
場(chǎng)景15.6.405.7.29無切換成功
場(chǎng)景25.7.295.6.40無切換成功
場(chǎng)景35.6.405.7.295.6.38切換失敗
場(chǎng)景45.6.385.7.295.6.40切換失敗
場(chǎng)景55.6.385.7.295.7.29切換成功
現(xiàn)象是這么個(gè)現(xiàn)象履磨,是不是很好奇蛉抓,為什么只有一個(gè)從庫的時(shí)候,跨版本可以切換成功剃诅,當(dāng)還有其他從庫的時(shí)候某些情況可以切換成功巷送,某些情況又切換失敗,往下看吧矛辕!
問題分析
先去google一下笑跛,搜索關(guān)鍵詞:mha .. is bad as a new master,
然后搜出來的并沒有我想要的結(jié)果聊品,有些參考價(jià)值的文章如下:
https://blog.51cto.com/u_860143/2431044 【和我的場(chǎng)景相似飞蹂,但什么解釋也沒說】
https://www.modb.pro/db/50655【不太明確,當(dāng)時(shí)沒理解】
窮途末路翻屈,只能去源碼中翻翻了陈哑,畢竟 MHA 一款開源的工具【不逼自己一把就不知道自己英文還是不錯(cuò)的】 找到 MHA 選主的相關(guān)代碼,首先定義了幾個(gè)數(shù)組:
slaves 數(shù)組:選取 alive 的 slaves
latest 數(shù)組:從 alive slave 中選取復(fù)制位點(diǎn)最新的 slaves
pref 數(shù)組:配置文件中配置了 candidate_master 的 slaves
bad 數(shù)組:后面解釋
接著在進(jìn)行選主的時(shí)候按照以下的順序進(jìn)行選舉:
選舉優(yōu)先級(jí)最高的 slave 作為新主(通常是手工切換指定的 new master)伸眶,如果該 slave 不能作為新主惊窖,則報(bào)錯(cuò)退出,否則如果是故障切換厘贼,則進(jìn)行下面的步驟
選擇復(fù)制位點(diǎn)最新并且在 pref 數(shù)組里的 slave 作為新主界酒,如果復(fù)制位點(diǎn)最新的 slave 不在 pref 數(shù)組中,則繼續(xù)下面步驟
從 pref 中選擇一個(gè) slave 作為新主嘴秸,如果沒有選出則繼續(xù)
選擇復(fù)制位點(diǎn)最新的 slave 作為新主毁欣,如果沒有選出則繼續(xù)
從所有的 slave 中進(jìn)行選擇
經(jīng)過以上步驟仍然選擇不出主則選舉失敗
注意:前面的6個(gè)選舉步驟庇谆,都需要保證新主不在 bad 數(shù)組中
# 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;}
因?yàn)閳?bào)錯(cuò)是說新主是 bad ,那我們重點(diǎn)看下新主為什么會(huì)被判定為 bad 凭疮,如何判定的饭耳。獲取 bad 列表的函數(shù)是get_bad_candidate_masters,如下哭尝,可以看出具有以下五種情況的 slave 會(huì)被判定為 bad :
dead servers
{no_master} >= 1【在配置文件中設(shè)置了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;}
對(duì)于1-3,5很好理解材鹦,而且線上后來通過監(jiān)控進(jìn)行了排查逝淹,并不存在這些問題,于是重點(diǎn)看下4是如何來進(jìn)行定義的桶唐。
找到相關(guān)的函數(shù):
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.");}
可以看到,這里首先會(huì)從 alive_servers 中獲取最小的版本尤泽,也就是min_major_version:
如果實(shí)例是 dead 或非從庫欣簇,則不比較該實(shí)例,否則進(jìn)行比較坯约,關(guān)鍵代碼next if (?>dead||
接下來熊咽,根據(jù)傳入的 server 的parsed_major_version【MySQL 的主版本,例如闹丐,5.6横殴,5.7】和min_major_version進(jìn)行對(duì)比:
如果parsed_major_version==min_major_version,則oldest_major_version=1卿拴;否則oldest_major_version=0
綜上可以看出衫仑,新主的版本號(hào),需要是所有從庫中版本最低的才能作為新的主庫堕花,否則將不能作為新的主庫文狱。
到這里,問題就水落石出了缘挽,回到我們前面測(cè)試的場(chǎng)景中瞄崇,就弄明白了:
場(chǎng)景1和場(chǎng)景2只有一個(gè)從庫的時(shí)候,跨版本切換可以切換成功壕曼,是因?yàn)檫@個(gè)從庫的主版本就是 min_major_version
場(chǎng)景3和場(chǎng)景4中切換失敗的原因是杠袱,新主的主版本為5.7,而所有從庫中最小的主版本號(hào)為5.6窝稿,因此不能切換
但是,MHA 為什么會(huì)這樣設(shè)計(jì)呢凿掂?
MySQL 源端(master)低版本到目標(biāo)端(slave)高版本數(shù)據(jù)復(fù)制是沒有問題伴榔,源端(master)高版本到目標(biāo)端(slave)數(shù)據(jù)復(fù)制可能會(huì)出現(xiàn)問題纹蝴。即:5.7可以作為8.0版本的從庫,5.6可以作為5.7的從庫踪少;但是8.0作為5.7或者5.7作為5.6的從庫就會(huì)有問題塘安。這個(gè)在官方有介紹:https://dev.mysql.com/doc/refman/5.7/en/replication-compatibility.html
不過 MHA 在比較最小版本的時(shí)候沒有比較原主庫的版本,這在切換的時(shí)候還是可能會(huì)出現(xiàn)低版本向高版本復(fù)制的情況援奢,比如測(cè)試場(chǎng)景1兼犯,不知道是基于什么考慮,歡迎大家留言討論集漾。
小結(jié)
MHA 選主邏輯:
選舉優(yōu)先級(jí)最高的 slave 作為新主(通常是手工切換指定的 new master)切黔,如果該 slave 不能作為新主,則報(bào)錯(cuò)退出具篇,否則如果是故障切換纬霞,則進(jìn)行下面的步驟
選擇復(fù)制位點(diǎn)最新并且在設(shè)置了 candidate_master 的 slave 作為新主,如果復(fù)制位點(diǎn)最新的 slave 沒有設(shè)置 candidate_master 驱显,則繼續(xù)下面步驟
從設(shè)置了 candidate_master 中選擇一個(gè) slave 作為新主诗芜,如果沒有選出則繼續(xù)
選擇復(fù)制位點(diǎn)最新的 slave 作為新主,如果沒有選出則繼續(xù)
從所有的 slave 中進(jìn)行選擇
經(jīng)過以上步驟仍然選擇不出主則選舉失敗
注意:前面的6個(gè)選舉步驟埃疫,都需要保證新主不在bad數(shù)組中
bad 數(shù)組定義如下:
dead servers
{no_master} >= 1【在配置文件中設(shè)置了no_master】
log_bin is disabled【未開啟binlog】
{oldest_major_version} eq '0'【MySQL major 版本不是最舊的】
too much replication delay【延遲大伏恐,與 master 的 binlog position 差距大于 100000000】
其中4這個(gè)是比較容易忽視的一點(diǎn),需要注意栓霜!
轉(zhuǎn)載于https://cloud.tencent.com/developer/article/1875033