Posts tagged ‘server_id’

mysql server_id错误配置讨论

在ITPUB上看到一个面试题,感觉很有意思,不知道大家有没有遇到过这样的问题:

NewImage

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

两种情况,

第一种情况:两个slave有同样的server-id会有什么问题?

第二种情况:如果是级联复制,再级联复制的路径上有相同的server-id会有什么问题。

 

 

其实很简单,只要你搭建复制,做一些简单的操作,你就能发现问题。

在第一个场景下,你会发现两个slave在不断的重连master,日志里面也会有错误信息,说slave被断掉,尝试重连,并且也连上去了。然后又被断掉,又重连…循环往复。

第二个场景,最后一个slave不能得到master的变更,在master上做的任何操作都不会应用到最后一个slave上。

 

 

MySQL复制的原理这边就不详细描述。简单的说,就是如果开启了log-bin记录二进制日志,master会在自己的binlog中记录下变更发生的时间,query(如果是Row的话会转成具体的行变更),server-id等信息,然后当slave请求(请求的信息中包括连接master的ip,port等连接信息;以及需要从master的那个二进制文件的哪个位置开始,获得之后的所有master变更)发送到master之后,master启动一个binlog dump的线程用于发送变更信息给该slave。slave通过IO thread接受消息,记录到自己的relay log中,(如果发现server-id等于自己的,它就认为是在本地产生的,直接丢弃),然后SQL thread读取relay log应用到本地MySQL。我们理解了这个,其实上面的两个问题就能够迎刃而解了。

第二个问题更加直观,master和第三个slave的server-id是一样的,所以第三个slave发现master产生的binlog,误以为是它自己产生的,就会丢弃掉。这样的话,master产生的binlog在第三个slave就无法应用。

第一个问题比较复杂一点,牵涉到master怎么处理server-id相同的slave请求。这里冲突的焦点就是,master怎么区分各个slave的不同之处。在MySQL中,就是用server id来区分的。比如:左边的slave首先连上master,master会分配binlog dump线程并于该slave通讯,发送变更信息;这时,右边的slave连上来,server-id也是2,master以server-id来区别slave,所以它认为是同一个slave连过来,请求另外一个binlog和对应position处开始的数据,于是按照正常逻辑它清理了左边slave的binlog dump线程,并给右边的分配binlog dump线程。左边的slave发现复制断掉以后会自动重连,所以右边的又悲剧了…循环往复,两个slave一直这样相互冲突。源码参看:sql/sql_parse.cc dispatch_command()的COM_BINLOG_DUMP段,截取部分源码如下:

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;

 

原因分析完了,我们来说说具体的解决方案:那就是要尽量避免上面两种情况:master的各个slave必须有不同的id;级联复制各节点不能有相同的server-id。

不妨把条件升级一下:所有的MySQL server_id都不允许一样?

server-id是用4个字节的unsigned int来保存的,值的范围:0 .. 4294967295。有将近43亿的server-id可以用。足够我们使用了。

要保证唯一的话,手工分配是完全不靠谱的,自动分配的话,我们提供两种方法以供参考:

1、使用一个集中式的自增的公共服务。可以用oracle的sequece,mysql的自增值等。优点是比较简单,缺点就是需要一个额外的服务

2、使用IP+PORT来算出一个server-id,一个IP和PORT可以唯一定位一个MySQL实例,所以各个MySQL server-id不会有冲突。IP和PORT怎么跟server-id对应列。由于IP本身对应的就是一个4个字节,(比如:INET_ATON(‘209.207.224.40’)=209×2563 + 207×2562 + 224×256 + 40=3520061480),加上端口两个字节,所以做不到完全的一一对应的关系。但是IP地址中有很多保留的的地址(比如:127.0.0.0到127.255.255.255是保留地址),另外由于一个IP对应的实例毕竟有限(你不可能在一个IP上启动65535个MySQL实例吧?),所以针对你自己的业务场景做一套适应的IP,PORT对应server-id的算式也是一个解决办法。比如:如果你高位两个字节都相同,就可以把端口的两个字节和IP后两个字节拼接成一个4字节的无符号整数作为server-id。

主备备的两个备机转为双master时出现的诡异slave lag问题

有三台MySQL服务器,a,b和c,复制关系为 a -> b -> c。a,b,c的server_id分别为1,2,3
因为需要切换为 a b <-> c,也就是说,a单独出来,b和c作为双master结构时。
这种切换会经常出现在需要搭建备机把数据备份出来,然后把a独立出来的case中。

昨天,我就做了这样的切换,结果发现出现莫名奇妙的slave lag。
Seconds_Behind_Master一下子为0,一下子变成几千秒。
使用mysqlbinlog查看,binlog日志里面也有很多时间在几小时以前的event数据。
为了验证复制是否正常,我特别测试了一下,在b建一个表,并插入时间数据,到c上一看,表已经复制过来了,时间数据也是正确。
询问了一下同事,他说应该是MySQL的bug,在这种切换的情况下很容易触发这个bug,可以采用stop slave;change master; start slave;的方法来修复。但是实际的数据其实完全没有影响,复制还是正常的。

于是我按照这个办法:
stop slave io_thread;
stop slave;
show slave status\G
(这里先停io_thread是为了SQL thread和IO thread都执行到了同一个位置,change master 的时候没有风险)
stop slave;change master to … ; start slave;
(change master到show slave status的Master_Log_File:和Exec_Master_Log_Pos:位置,也就是说,其实根本没有改变复制的位置)

结果slave lag依然故我。这个问题就比较郁闷了。时间已经过了午夜,脑袋也转不动了,想过不管它了,反正复制没有问题。但是问题没有解决总觉得什么东西卡在喉咙一样。各种资料,各种变量都参考了一遍,最后,基本不太意识的输入:
show master logs;
show binlog events in ‘mysql-bin.000680’ from 34385301;
想看看最新产生的event,结果就发现不对的地方了。
这个最新产生的event有很多,并且server_id是1,1是a的server_id啊,应用访问的是b啊,怎么会在b上面产生a的server_id列,MySQL哪里出问题了?

仔细一想,明白了,事情是这样的:
a -> b -> c,a的event1(server_id为1)复制到b,也会复制到c,这个是正常的。
然后搭建c -> b的复制关系时,b需要断开a的连接,切换主库到c,在 change master 的位置在event1出现之前,那么event1肯定会被重新复制到b去,event1的server_id是1,那么b判断,这个event1不是我提交的,需要在本地执行,并且把它记录到了自己的binlog中;
由于b和c是双master结构,event1又复制到了c,c同样判断它不是我提交的,那么我需要在本地执行,并且记录到本地binlog中。
这样event1就在b和c之间循环往复,时间保持不变,MySQL的slave lag也就一下子是0,一下子是几千秒了。

这里,还需要说明一点,在环型复制里面,event之所以能够在环内只循环一次,而不是重复做,是因为提交的那个节点会发现这个event的server_id是自己的server_id,也就是说是自己提交的。那么,它就不会把这个event再应用一次,自然也不会记录到binlog。这个循环就结束了。除非你闲着没事做,设置了replicate-same-server-id参数。

那么解决问题怎么办列,很简单,把没有应用访问的c的server_id设置成a的server_id:
set global server_id=1;
看看时间差不多了,server_id为1的event都被干掉以后:
set global server_id=3;
然后再设置回来。
还好,MySQL 5.0和5.1的server_id都是动态的。

may your success.