技术开发 频道

MySQL复制事件在主备之间来回传输检测

        【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引起的,把上述复制构架进行简化可以很容易的重现事故

  事故重现:

1、配置server1和server2为双主结构:
    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格式不一样,会出现不同的结果,以简化的结构为例:

1、server1:row,server2:statement
   server1和server2上都只会执行一次insert(和正常情况一样)
2、server1:statement,server2:row
   server1会执行两次insert,而server2只会执行1次
3、server1和server2日志模式相同
   事件循环传递并被不断执行

  原因也被找出:row模式日志无法重写成statement模式,而statement模式日志执行后可以写成row模式

0
相关文章