【IT168 技术】背景知识:
MySQL复制双主结构:主备之间可以互相复制,备库read_only被打开:
MySQL1(read/write)<–>MySQL2(read only)
双主结构的好处:当主库MySQL1 down掉之后,备库MySQL2关闭read_only后可以马上成为主库,而在MySQL1以read_only形式重新启动后,整个结构与原来一样,只是MySQL1和MySQL2互换了角色,因此MySQL2一开始就是一个standby(当然也能提供读的服务,分担MySQL1的部分查询压力)
线上问题:
复制结构:MySQL1 <–(dual master) –> MySQL2 –(rep)–> MySQL3
MySQL1和MySQL2是双主结构,MySQL3是纯备库(需要时也可以与MySQL2互为双主)当MySQL1宕机而做主备切换时,MySQL2成为新主,期望MySQL1重启后成为standby的新备(即MySQL1和MySQL2的角色互换),MySQL3则保持不变。在MySQL1硬件失效导致重启不成功后,将MySQL3变成MySQL2的stand-by主库时,原先复制到MySQL2的复制事件会在MySQL2/3之间来回传输,导致严重问题:若插入数据上定义了主键或者唯一索引,主备上会不断报错:duplicate key,否则,主备上同一条记录会被不断插入!
经过分析最终找到了问题的原因,是由于server_id引起的,把上述复制构架进行简化可以很容易的重现事故
事故重现:
server1(server_id=1) <–(dual master)–> server2(server_id=2)
2、在server1执行以下SQL命令:
mysql> create table test.t1(id int);
mysql> stop slave;
mysql> insert into t1 values(1);
mysql> set global server_id=3;
mysql> start slave;
3、在server1,server2上执行:
mysql> select count(*) from t1;
分析:
从简化的例子中可以发现,导致复制事件的主要原因是server1的server_id发生了改变,原理如下:
在双主复制中,主库库更新数据时,会将更新事件发送到备库,备库更新数据、写binlog后,还会把复制事件发送给主库,因此最初主库上的更新事件又传了回来,这时候就要对复制事件进行判断,否则这种更新将在主备之间无线循环,判断依据就是server_id,因此当主库发现复制事件的server_id和自己的server_id相同时,放弃执行,于是主备之间可以正常复制了,但前提是主备的server_id保持不变
因此,对于上述简化结构,server1上插入一条记录一条记录之前stop slave,保证了插入查找在server2上执行后不能传到server1,在这之间server1的server_id发生了改变,当它start slave后,server2的复制事件被接收,由于server1此时的server_id为3,与复制事件中server_id(=1)不同,于是执行更新并写binlog,之后又将复制事件传递给server2,server2的server_id为2,也与复制事件中server_id(=1)不同,于是复制事件会在server_1和server_2之间循环传递和执行,停止这种传递的方法是将server1的server_id改回1
现在,线上事故也就不难解释了,MySQL1发送给MySQL2的复制事件,由于其server_id与MySQL2和MySQL3都不一样,当MySQL2与MySQL3互为双主后,复制事件会在它们之间循环传递和执行,让MySQL3的server_id变为MySQL1原来的server_id即可解决此问题(其实,这时MySQL3充当的就是MySQL1最初的角色)
此类问题一旦发生,大部分情况让MySQL主备表现异常,而少数情况会非常隐蔽,而如果没能及时发现并解决问题,数据就悲剧了…
因此,在到问题的真正原因后,我们尝试监测此问题,要想彻底杜绝不太可能,MySQL3自动判断它将要充当MySQL1的角色?
解决方案:
线上部署的MySQL节点拓扑结构有个明显特征:绝多数的1主1备,以及个别的1主多备,针对这些拓扑结构,处理此问题就可以简化许多,在备库复制的IO线程接收到主库发送的事件做如下判断:
1 事件的server_id与本实例的server_id不相同
2 事件的srever_id与当前实例的所有备库的server_id均不同
3 当前实例的master的也是其slave
其中:条件1和2确定此复制事件不是由自身或者其slave发出,条件3保证该复制事件有可能循环传递
当满足上述则可条件时,判定复制事件是经回路传送过来的,运行效果:
当MySQL复制的IO线程检测到事件存在回路时,会打印如下警告信息:
Detect dual master circular topological structure …然而,对于多个(>=3)节点构成的环形结构(生产环境基本不会使用),例如 M1-> S2 -> S3 -> M1, 上述判断无法起作用
社区反馈:
Percona的Sterward接受此问题#940404 并将其标记为Medium,优先级中等的task, patch查看(percona 5.5.18)
其他发现:
在分析问题和重现现场时,我们曾还事故可能和日志模式有关,因此发现了一个问题,当出现主备之间事件循环传递时,因为主备之间binlog格式不一样,会出现不同的结果,以简化的结构为例:
server1和server2上都只会执行一次insert(和正常情况一样)
2、server1:statement,server2:row
server1会执行两次insert,而server2只会执行1次
3、server1和server2日志模式相同
事件循环传递并被不断执行
原因也被找出:row模式日志无法重写成statement模式,而statement模式日志执行后可以写成row模式