本文内容遵从cc版权协议, 可以随意转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明网址: http://www.penglixun.com/tech/database/mysql_multi_slave_same_serverid.html 今天分析一个诡异问题,一个模拟slave线程的程序,不断的被master ser
本文内容遵从cc版权协议, 可以随意转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明网址: http://www.penglixun.com/tech/database/mysql_multi_slave_same_serverid.html
今天分析一个诡异问题,一个模拟slave线程的程序,不断的被master server给kill掉,最终发现是因为有两个slave使用同样一个server id去连接master server,为什么两个slave用同一个server id会被master server给kill呢?分析了源码,这源于mysql replication的重连机制。
我们首先看看一个slave注册到master会发生什么,首先slave需要向master发送一个com_register_slave类型的请求(sql_parse.cc)命令请求,这里master会使用register_slave函数注册一个slave到slave_list。
case com_register_slave: { if (!register_slave(thd, (uchar*)packet, packet_length)) my_ok(thd); break; }
在注册slave线程的时候会发生什么呢?我们略去无用的代码直接看重点:(repl_failsafe.cc)
int register_slave(thd* thd, uchar* packet, uint packet_length){ int res; slave_info *si; uchar *p= packet, *p_end= packet + packet_length;.... //省略 if (!(si->master_id= uint4korr(p))) si->master_id= server_id; si->thd= thd; pthread_mutex_lock(&lock_slave_list); unregister_slave(thd,0,0); //关键在这里,先取消注册server_id相同的slave线程 res= my_hash_insert(&slave_list, (uchar*) si); //把新的slave线程注册到slave_list pthread_mutex_unlock(&lock_slave_list); return res;.....}
这是什么意思呢?这就是重连机制,slave_list是一个hash表,server_id是key,每一个线程注册上来,需要删掉同样server_id的slave线程,再把新的slave线程加到slave_list表中。
线程注册上来后,请求binlog,发送com_binlog_dump请求,master会发送binlog给slave,代码如下:
case com_binlog_dump: { ulong pos; ushort flags; uint32 slave_server_id; status_var_increment(thd->status_var.com_other); thd->enable_slow_log= opt_log_slow_admin_statements; if (check_global_access(thd, repl_slave_acl)) break; /* todo: the following has to be changed to an 8 byte integer */ pos = uint4korr(packet); flags = uint2korr(packet + 4); thd->server_id=0; /* avoid suicide */ if ((slave_server_id= uint4korr(packet+6))) // mysqlbinlog.server_id==0 kill_zombie_dump_threads(slave_server_id); thd->server_id = slave_server_id; general_log_print(thd, command, log: '%s' pos: %ld, packet+10, (long) pos); mysql_binlog_send(thd, thd->strdup(packet + 10), (my_off_t) pos, flags); //不断的发送日志给slave端 unregister_slave(thd,1,1); //发送完成后清理slave线程,因为执行到这一步肯定是binlog dump线程被kill了 /* fake com_quit -- if we get here, the thread needs to terminate */ error = true; break; }
mysql_binlog_send函数在sql_repl.cc,里面是轮询master binlog,发送给slave。
再来简单看看unregister_slave做了什么(repl_failsafe.cc):
void unregister_slave(thd* thd, bool only_mine, bool need_mutex){ if (thd->server_id) { if (need_mutex) pthread_mutex_lock(&lock_slave_list); slave_info* old_si; if ((old_si = (slave_info*)hash_search(&slave_list, (uchar*)&thd->server_id, 4)) && (!only_mine || old_si->thd == thd)) //拿到slave值 hash_delete(&slave_list, (uchar*)old_si); //从slave_list中拿掉 if (need_mutex) pthread_mutex_unlock(&lock_slave_list); }}
这就可以解释同样的server_id为什么会被kill,因为一旦注册上去,就会现删除相同server_id的slave线程,然后把当前的slave加入,这是因为有时slave断开了,重新请求上来,当然需要踢掉原来的线程,这就是线程重连机制。
切记,一个mysql集群中,绝不可以出现相同server_id的实例,否则各种诡异的问题可是接踵而来。
