今天分析一个诡异问题,一个模拟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,代码如下:
- caseCOM_REGISTER_SLAVE:
- {
- if(!register_slave(thd,(uchar*)packet,packet_length))
- my_ok(thd);
- break;
- }
在注册Slave线程的时候会发生什么呢?我们略去无用的代码直接看重点,repl_failsafe.cc代码如下:
- intregister_slave(THD*thd,uchar*packet,uintpacket_length)
- {
- intres;
- 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//phpfensi.com
- pthread_mutex_unlock(&LOCK_slave_list);
- returnres;
- .....
- }
这是什么意思呢?这就是重连机制,slave_list是一个Hash表,server_id是Key,每一个线程注册上来,需要删掉同样server_id的Slave线程,再把新的Slave线程加到slave_list表中.
线程注册上来后,请求Binlog,发送COM_BINLOG_DUMP请求,Master会发送binlog给Slave,代码如下:
- caseCOM_BINLOG_DUMP:
- {
- ulongpos;
- ushortflags;
- uint32slave_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:Thefollowinghastobechangedtoan8byteinteger*/
- pos=uint4korr(packet);
- flags=uint2korr(packet+4);
- thd->server_id=0;/*avoidsuicide*/
- 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线程,因为执行到这一步肯定是binlogdump线程被kill了
- /*fakeCOM_QUIT--ifwegethere,thethreadneedstoterminate*/
- error=TRUE;
- break;
- }
mysql_binlog_send函数在sql_repl.cc,里面是轮询Master binlog,发送给Slave,再来简单看看unregister_slave做了什么,repl_failsafe.cc,代码如下:
- voidunregister_slave(THD*thd,boolonly_mine,boolneed_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断开了,重新请求上来,当然需要踢掉原来的线程,这就是线程重连机制.