author:sufei
一帮寻、現(xiàn)象
在生產(chǎn)環(huán)境遇到如下現(xiàn)象:
即矛盾的結果:
- 主庫的MySQL processlist明明可以看到dump線程,說明有從庫在進行dump邏輯钝域;
- show slave hosts為空式廷,即無法從主庫查到從庫狀態(tài);
二子寓、原因分析
根據(jù)現(xiàn)象,合理地猜測笋除,存在用戶執(zhí)行了dump_gtid邏輯斜友,也就是復制binlog邏輯,但是該線程不在slave_list列表中垃它。我們知道當一個從庫連接到主庫執(zhí)行GTID復制命令時鲜屏,主要執(zhí)行以下步驟:
1、執(zhí)行COM_REGISTER_SLAVE命令国拇,即調(diào)用register_slave完成從庫注冊(也就是插入slave_list列表)洛史;
2、執(zhí)行COM_BINLOG_DUMP_GTID命令酱吝,即調(diào)用com_binlog_dump_gtid循環(huán)發(fā)送binlog給從庫也殖;
目前現(xiàn)象是,存在dump_gtid命令地執(zhí)行务热,但注冊列表中沒有相關從庫信息忆嗜,存在兩種可能:
- 從庫沒有執(zhí)行register指令,直接執(zhí)行dump執(zhí)行崎岂;這個情況可以排除捆毫,一般只有在自己模擬從庫的程序中才會如此忽略,官方從庫邏輯都是先進行register该镣,再進行dump冻璃;
- 從庫進行了register,但是在某些情況被unregister了损合,但是dump線程沒有被kill省艳。查看unregister情況有如下幾種:
1、從庫register注冊過程中通過server_id查找(即在register_slave函數(shù)中)嫁审,把相同地server_id的舊實例unregister移除跋炕;這種情況是避免相同從庫在主庫存在兩個復制線程;
mysql_mutex_lock(&LOCK_slave_list);
unregister_slave(thd, false, false/*need_lock_slave_list=false*/);
res= my_hash_insert(&slave_list, (uchar*) si);
mysql_mutex_unlock(&LOCK_slave_list);
// unregister_slave函數(shù)具體如下律适,其通過server_id進行從庫是否相同判斷
void unregister_slave(THD* thd, bool only_mine, bool need_lock_slave_list)
{
if (thd->server_id && my_hash_inited(&slave_list))
{
if (need_lock_slave_list)
mysql_mutex_lock(&LOCK_slave_list);
else
mysql_mutex_assert_owner(&LOCK_slave_list);
SLAVE_INFO* old_si;
if ((old_si = (SLAVE_INFO*)my_hash_search(&slave_list,
(uchar*)&thd->server_id, 4)) &&
(!only_mine || old_si->thd == thd))
my_hash_delete(&slave_list, (uchar*)old_si);
if (need_lock_slave_list)
mysql_mutex_unlock(&LOCK_slave_list);
}
}
2辐烂、dump_gtid退出發(fā)送binlog循環(huán)時(即在com_binlog_dump_gtid函數(shù)中)遏插,這種情況屬于復制線程正常推出情況;
mysql_binlog_send(thd, name, (my_off_t) pos, &slave_gtid_executed, flags);
unregister_slave(thd, true, true/*need_lock_slave_list=true*/);
3纠修、THD線程銷毀時(即在THD的析構函數(shù)中)胳嘲,如果該線程是dump線程,則需要進行unregister扣草,這種情況屬于異常推出了牛;
if (rli_slave)
rli_slave->cleanup_after_session();
/*
As slaves can be added in one mysql command like COM_REGISTER_SLAVE
but then need to be removed on error scenarios, we call this method
here.
*/
unregister_slave(this, true, true);
從分析可以看出:后兩種情況,最終THD結構都會被銷毀辰妙,而只有第一種情況鹰祸,舊slave雖然被unregister,但是其dump線程還存在密浑。哪何時銷毀呢蛙婴?為什么出現(xiàn)了沒有銷毀的情況呢?
問題1:對于舊slave尔破,何時銷毀街图?
我們可以看到,當新slave在進行dump指令時呆瞻,會調(diào)用kill_zombie_dump_threads(thd);即kill舊的slave線程台夺。
kill_zombie_dump_threads(thd); // kill舊的slave線程
query_logger.general_log_print(thd, thd->get_command(),
"Log: '%s' Pos: %llu GTIDs: '%s'",
name, pos, gtid_string);
my_free(gtid_string);
mysql_binlog_send(thd, name, (my_off_t) pos, &slave_gtid_executed, flags); // 開始發(fā)送binlog
unregister_slave(thd, true, true/*need_lock_slave_list=true*/);
問題2:現(xiàn)在的情況径玖,明明調(diào)用了kill_zombie_dump_threads痴脾,為什么沒有銷毀呢?
查看kill_zombie_dump_threads函數(shù)梳星,如下:
void kill_zombie_dump_threads(THD *thd)
{
String slave_uuid;
get_slave_uuid(thd, &slave_uuid);
if (slave_uuid.length() == 0 && thd->server_id == 0)
return;
Find_zombie_dump_thread find_zombie_dump_thread(slave_uuid);
THD *tmp= Global_THD_manager::get_instance()->
find_thd(&find_zombie_dump_thread);
if (tmp)
{
……
tmp->duplicate_slave_id= true;
tmp->awake(THD::KILL_QUERY);
mysql_mutex_unlock(&tmp->LOCK_thd_data);
}
}
不然看出:在kill dump線程的時候赞赖,是通過uuid進行舊slave查找的,這就存在問題了冤灾,也就是生產(chǎn)出現(xiàn)的情況前域,當兩個從庫server_id相同,所以造成了register覆蓋韵吨,但是uuid不同匿垄,造成舊的slave無法被kill。
三归粉、結論
當兩個從庫server_id相同椿疗,uuid不同時,會造成舊從庫無法被刪除糠悼。修復建議:1届榄、修改源碼,使得在兩次判斷相同從庫的邏輯一致倔喂;2铝条、生產(chǎn)上靖苇,避免server_id相同,uuid不同的實例班缰,同樣反之依然贤壁,即uuid相同,server_id不同埠忘,會造成從庫無緣無故被殺芯砸。
官方有相關bug報告已久,但并未修復给梅,不知道為何假丧?https://bugs.mysql.com/bug.php?id=69771