MySQL中server_id一致带来的问题

沃趣科技  沈刚

简介

我们都知道在MySQL搭建复制环境的时候,需要设置每个server的server_id不一致,如果主库与从库的server_id一致,那么复制会失败。但是最近在解决一个客户的问题的时候,遇到一个有意思的现象,客户环境有三台数据库服务器,一主两从,客户的两台从库设置了相同server_id,在排查问题的过程中,查看MySQL错误日志,发现有很多奇怪的信息。
我们模拟了客户的环境,并进行测试、分析,最终在代码中找到了我们想要的答案。下面就是我们测试、分析、总结的步骤以及内容。

测试步骤

环境介绍

  • 主库
    IP:192.168.1.130
    server_id:3656
  • 从库A
    IP:192.168.1.36
    server_id:56
  • 从库B
    IP:192.168.1.57
    server_id:56

三台主机除server_id之外,其余配置如下:


  1. server_id = 123
  2.     [client]
  3.     socket = /home/mysql/data/mysqldata5.5/sock/mysql.sock
  4.     [mysqld]
  5.     #server_id = 3655
  6.     server_id = 123
  7.     port = 3306
  8.     skip_name_resolve = 1
  9.     binlog_format = ROW
  10.     #binlog_format = STATEMENT
  11.     basedir = /home/mysql/program/mysql5.5.36
  12.     datadir = /home/mysql/data/mysqldata5.5/mydata
  13.     socket = /home/mysql/data/mysqldata5.5/sock/mysql.sock
  14.     pid-file = /home/mysql/data/mysqldata5.5/sock/mysql.pid
  15.     tmpdir = /home/mysql/data/mysqldata5.5/tmpdir
  16.     log-error = /home/mysql/data/mysqldata5.5/log/error.log
  17.     slow_query_log
  18.     slow_query_log_file = /home/mysql/data/mysqldata5.5/slowlog/slow-query.log
  19.     log-bin = /home/mysql/data/mysqldata5.5/binlog/mysql-bin
  20.     relay-log = /home/mysql/data/mysqldata5.5/relaylog/mysql-relay-bin
  21.     innodb_data_home_dir = /home/mysql/data/mysqldata5.5/innodb_ts
  22.     innodb_log_group_home_dir = /home/mysql/data/mysqldata5.5/innodb_log
  23.     #innodb_undo_directory = /home/mysql/data/mysqldata5.5/undo/
  24.     sync_binlog=1
  25.     innodb_file_per_table=1
  26.     #skip_grant_tables
  27.     expire_logs_days = 1
  28.     log_slave_updates = ON
  29.     #replicate-same-server-id=1
  30.     skip_slave_start
  31.     #innodb_undo_tablespaces=1

5.5.36版本现象

初始搭建环境之后,查看各主机状态。搭建环境的步骤就省略。

主库(192.168.1.130)

主库通过show processlist语句查看,只有一个dump线程,但是通过多次刷新,可以看到连接的是不同的服务器。可以看到每次通过show processlist语句显示的dump线程的Host字段中,IP:PORT的值是不断在更新的,说明dump线程在不断的重连,才会出现占用不同的端口的现象。

55b1c382-4c8b-40c9-a61d-3c7332a49a3a.png

从库A(192.168.1.36)

通过show slave status\G命令查看复制状态,多次执行可以看到Slave_IO_Running字段显示的内容,出现YES或者Connnecting两种状态。可以看到I/O线程在不断的进行重连。
并且通过tail -f命令查看error log,可以看到I/O线程一直在尝试重新连接。


029b534b-7876-4daa-a584-7a87ca7a2a5b.png
可以看到在错误日志中打印的信息是,I/O线程连接

2daf6977-7053-47ec-8f47-197089798250.png

从库B(192.168.1.57)

从库B现象与从库A一致。



e0f7a94e-d812-4acc-923d-37c13ec6a24d.png

e7049dbd-6e24-406d-a80f-de814f7bb4d7.png

5.6.36版本现象

搭建环境步骤省略。

主库(192.168.1.130)

show processlist查看有两个dump线程,并且多次刷新,发现Host字段中的IP:PORT并没有修改,说明dump线程一直保持连接。

46b031c3-6635-4042-83a2-f4163422e01f.png

从库A(192.168.1.36)

tail -f /home/mysql/data/mysqldata5.6/log/error.log查看错误日志,没有不断断开连接

从库B(192.168.1.57)

tail -f /home/mysql/data/mysqldata5.6/log/error.log查看错误日志,没有不断断开连接

57be96d6-3ff3-48f0-b178-78d6c3aa0fe6.png

原因分析

http://www.penglixun.com/tech/database/mysql_multi_slave_same_serverid.html这是彭立勋写的关于多个slave使用相同server_id时冲突的原因的一篇文章。按照彭大大的分析,我理解的是,slave的I/O线程连接上主库的时候,主库上会调用register_slave()这个函数,在这个函数中又调用了unregister_slave()函数,会将之前使用相同server_id的线程给注销掉。从而导致从库的I/O线程不断断开重连。
但是仔细看了一下unregister_slave()函数的代码,并没有发现MySQL是根据server_id来注销dump线程的。并且进一步比较了一下5.5.36和5.6.36版本的代码,并没有发现不同。而从库设置server_id一致导致I/O线程不断重连的现象只在5.5版本中看到,在5.6版本中并没有这个现象,所以导致5.5现象的原因不是在unregister_slave()函数中。
进一步看了一下彭大大的文章,发现有人在下面评论,说主要是kill_zombie_slave_threads()函数导致的。于是看了一下kill_zombie_slave_threads()函数的逻辑,发现MySQL应该就是在这一步根据server_id将线程kill了。

  • 5.5.36版本
    首先来看下5.5.36版本的kill_zombie_dump_threads()函数的代码。看到这个函数传入的参数是一个uint32类型的slave_server_id,在函数中做的事情是,遍历MySQL中的所有线程,如果遍历到一个线程是dump线程并且线程的server_id是等于传入的参数值话,则跳出遍历循环,并对kill掉这个线程。


  1. void kill_zombie_dump_threads(uint32 slave_server_id)
  2.     {
  3.       mysql_mutex_lock(&LOCK_thread_count);
  4.       I_List_iterator<THD> it(threads);
  5.       THD *tmp;
  6.       while ((tmp=it++))
  7.       {
  8.         if (tmp->command == COM_BINLOG_DUMP &&
  9.           tmp->server_id == slave_server_id)
  10.         {
  11.          mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete
  12.          break;
  13.         }
  14.       }
  15.       mysql_mutex_unlock(&LOCK_thread_count);
  16.       if (tmp)
  17.       {
  18.         /*
  19.          Here we do not call kill_one_thread() as
  20.          it will be slow because it will iterate through the list
  21.          again. We just to do kill the thread ourselves.
  22.         */
  23.         tmp->awake(THD::KILL_QUERY);
  24.         mysql_mutex_unlock(&tmp->LOCK_thd_data);
  25.       }
  26.     }

5.6.35版本
再来看一下5.6.36版本的kill_zombie_dump_threads()函数的代码实现,与5.5.36大不相同。首先传入的参数是一THD类型的指针,在函数中实现的逻辑同样是遍历MySQL中的所有线程,如果找到dump线程,首先看一下这个线程有没有uuid字段(因为uuid是在5.6之后的版本才有的,这边是为了兼容5.5),如果有uuid则用uuid进行比较,如果没有uuid,则用server_id进行比较。


  1. void kill_zombie_dump_threads(THD *thd)
  2.     {
  3.       String slave_uuid;
  4.       get_slave_uuid(thd, &slave_uuid);
  5.       if (slave_uuid.length() == 0 && thd->server_id == 0)
  6.         return;
  7.       mysql_mutex_lock(&LOCK_thread_count);
  8.       THD *tmp= NULL;
  9.       Thread_iterator it= global_thread_list_begin();
  10.       Thread_iterator end= global_thread_list_end();
  11.       bool is_zombie_thread= false;
  12.       for (; it != end; ++it)
  13.       {
  14.         if ((*it) != thd && ((*it)->get_command() == COM_BINLOG_DUMP || (*it)->get_command() == COM_BINLOG_DUMP_GTID))
  15.         {
  16.          String tmp_uuid;
  17.          get_slave_uuid((*it), &tmp_uuid);
  18.          if (slave_uuid.length())
  19.          {
  20.            is_zombie_thread= (tmp_uuid.length() && !strncmp(slave_uuid.c_ptr(),
  21.                                              tmp_uuid.c_ptr(), UUID_LENGTH));
  22.          else
  23.          {
  24.            /*
  25.            ? Check if it is a 5.5 slave's dump thread i.e., server_id should be
  26.            ? same && dump thread should not contain 'UUID
函数调用
知道了kill_zombie_dump_threads()线程实现的逻辑,那MySQL是在什么地方会调用这个函数的呢。看了一下函数是在case COM_BINLOG_DUMP中被调用的。
在5.5.36版本中是在



  1. case COM_BINLOG_DUMP:
  2.      {
  3.      ulong pos;
  4.      ushort flags;
  5.      uint32 slave_server_id;
  6.      status_var_increment(thd->status_var.com_other);
  7.      thd->enable_slow_log= opt_log_slow_admin_statements;
  8.      if (check_global_access(thd, REPL_SLAVE_ACL))
  9.     break;
  10.      /* TODO: The following has to be changed to an 8 byte integer */
  11.      pos = uint4korr(packet);
  12.      flags = uint2korr(packet + 4);
  13.      thd->server_id=0; /* avoid suicide */
  14.      if ((slave_server_id= uint4korr(packet+6))) // mysqlbinlog.server_id==0
  15.      kill_zombie_dump_threads(slave_server_id);
  16.      thd->server_id = slave_server_id;
  17.      general_log_print(thd, command, "Log: '%s' Pos: %ld", packet+10, (long) pos);
  18.      mysql_binlog_send(thd, thd->strdup(packet + 10), (my_off_t) pos, flags);
  19.     unregister_slave(thd,1,1);
  20.     /* fake COM_QUIT -- if we get here, the thread needs to terminate */
  21.      error = TRUE;
  22.     break;
  23.     }

在5.6.36版本中也是在case COM_BINLOG_DUMP中,只不过是将之前的逻辑封装在了com_binlog_dump()函数中了,kill_zombie_dump_threads()也是在com_binlog_dump()函数中调用的。


  1. case COM_BINLOG_DUMP:
  2.     error= com_binlog_dump(thd, packet, packet_length);
  3.     break

case COM_BINLOG_DUMP中所进行的操作就是将dump线程通知I/O线程拉取新的binlog。

总结

整理下来的话,基本上可以确定主要是因为kill_zombie_dump_threads()函数导致在5.6之前的版本中,如果是一主多从的架构中,如果在从库之间的server_id如果设置为一样,会出现从开I/O线程不断断开重连的现象。因为在5.6之前的版本中,还没有UUID的概念,MySQL使用server_id来区分是否是同一台机器,而在5.6之后的版本是使用的UUID来区分。
总结一句,就是数据库之间的server_id不要设置成一样,不然可能会有一些不可预知的错误。


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/28218939/viewspace-2143620/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/28218939/viewspace-2143620/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值