SlideShare ist ein Scribd-Unternehmen logo
1 von 56
主备备的两个备机转为双 Master 出现诡异的 slave lag 问题



有三台 MySQL 服务器,a,b 和 c,复制关系为 a -> b -> c。a,b,c 的 server_i

d 分别为 1,2,3

因为需要切换为 a b <-> c,也就是说,a 单独出来,b 和 c 作为双 master 结

构时。

这种切换会经常出现在需要搭建备机把数据备份出来,然后把 a 独立出来的 cas

e 中。

昨天,我就做了这样的切换,结果发现出现莫名奇妙的 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 statusG

(这里先停 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_Lo

g_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,在 chan

ge 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。这个循环就结束了。除非你闲着没事做,设置了 rep

licate-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.


research report for MySQL multi-master tool


把老的多主 master 向同一个 slave 复制的文档找出来了。这个是研究性质的,

不会牵涉到太多技术细节,所以不担心会有泄漏以前公司技术的嫌疑。之前公司

的这个软件已经完成了,包括事件解析,应用以及冲突处理。我自己想做一个开
源的,multi_master 的版本,但是架构基本上会非常不同。希望有时间能够真正

的做出来阿,这个功能相信对我们还是非常有用的。

Multi-Master

测试报告



目 录


目 录2

1 报告信息 3

2 概述 4

2.1 目的 4

2.2 相关信息 4

2.3 MySQL replication 数据复制格式 5

3 测试过程 9

3.1 测试进度安排 9

3.2 测试过程描述 9

4 测试结论 12

4.1 测试最终结论 12

5 附录 13

5.1 附录 1 MySQL 5.1.20 Beta 包含的事件类型 13

5.2 附录 2 MySQL 5.1.20 Beta 各事件的附加事件头长度 14
5.3 附录 3 MySQL 5.1.20 Beta 中各列在内部存储时可能的各种数据类型 16

5.4 附录 4 MySQL 5.1.20 Beta 中各 ROW_EVENT 的 m_flags 包含的标志位

17



概述


目的


测 试是否可以模拟 MySQL replication 的功能构建一个小工具。该工具模拟 re

plication 端的两个线程:I/O 线程和 SQL 线程。I/O 线程用于从 master 端 (主

服务器)注册自己和申请获得 master 的 binlog 信息,把获得的 binlog 信息进行

解析并保存在本地 relay-log 中。SQL 线程负责读 取解析 relay-log 并在本地 My

SQL 服务器上执行这些语句。从而达到从 master 端复制数据并在 slave 端(从

服务器)执行,以及时同步 slave 的目的。用这个工具,我们可以避免 MySQL

只能从单个 master 中同步数据的限制,实现从多个 master 中复制数据,同步 s

lave 的功 能。


相关信息


项目来源


在我们 GSB 的数据库复制同步中,杭州 的 GSB 数据库需要从不同的 master

端获得数据,并同步到自己的数据库中。而 MySQL 本身不提供 multi-master 的

功能,只能从单个 master 中复制和同步数据,并且在将来一段时间也不准备增
加这个功能。所以我们打算先对 MySQL 的源代码进行理解和分析,并尝试着模

拟 MySQL replication 的功能,研究 multi-master 工具的可行性。


测试环境


在 这次测试中我们主要用到了两台机器,192.168.1.112 和 192.168.1.113,它

们都是虚拟机,安转的操作系统为 Linux 2.6.16。其中 112 机器中安装的 MyS

QL server 版本为:5.0.27-standard-log,作为 master。113 机器安装的 MySQ

L server 版本为:5.0.24-max-log,作为 slave。由于采用了增加 server_id 列标

识提交语句的 site 的策略,我们将测试环境 中的两个 MySQL 版本升级为 5.1.

20 Beta。


其他相关信息


MySQL 的 数据复制功能主要涉及到三个线程(master 端一个,slave 端两个)。

当用户在 slave 端提交 start slave;slave 端将首先创建 I/O 线程,I/O 线程会连

接到 master 并请求 master 将其 binlog 中的语句发送回来。Master 接收 到请求

后将创建一个线程(Binlog Dump)用于发送 binlog 信息给 slave,用户可以通

过 Show processlist 在 master 端看到此线程。I/O 线程读取主服务器 Binlog D

ump 线程发送的内容并将该数据拷贝到从服务器数据目录中的本地文件中,即

中继日志(relay-log)。第 3 个线程是 SQL 线程,是 slave 端创建的用于读取

中继日志并执行日志中语句的线程。

如果有多个从服务器,主服务器将为每个当前连接的从服务器创建一个(Binlog

Dump)线程;每个从服务器都有自己的 I/O 和 SQL 线程。
MySQL 复制基于服务器在二进制日志中对所有数据库的更改(更新、删除等等)。

因此,要进行复制,必须在主服务器上启用二进制日志。

从 MySQL 5.1 开始,MySQL replication 可以支持两种复制类型,第一种是原

有的 statement based replication,也就是将在 master 端提交的查询语句及相

关环境一起记录到 binlog 中,slave 模拟该环境并提交语句执行。第二种是 row

based replication,即将 master 端提交的查询语句改变的所有行数据记录到 b

inlog 中,slave 端获得这些数据直接提交给 MySQL server,修改对应数据文件。

这两种复制类型可以单独运行或者混合起来。MySQL 默认的复制类型就是混合

类型的复制,它根据查询类型,表的类型等相 关因素确定该表的复制类型。

在每一个数据库的表中,我们增加了一列 server_id 用于模拟线程变量 thd->ser

ver_id。但是由于 server_id 存在于表中并且将随表数据一同复制,在复制中不

能随意改变,所以这个方法存在一定的限 制,后面我们将详细描述。由于我们

使用 server_id 列判断表中某一行(row)数据操作的最先发起 site,所以我们必

须保证复制的列中包括这一 列,这样我们必须限制 MySQL master 和 slave 端

replication 的类型为 row based,即 row-based replication。注意:row-based

replication 是 MySQL 5.1 版本才被引入的。下面我们所涉及的复制除了特殊指

明外都是指 row based replication。

在 MySQL 5.1.20 Beta 版本中,在 master 端如果一条语句改变了一个表的多

条数据,master 将首先在 binlog 中记录下一个 Tablemap 事件用于将表的相

关信息以及表的 id 信息对应记录下来。而针对每一行需要改变的数据,master

端单独记录一个 writerow(updaterow,deleterow)事件,而且每一行数据都

是二进制的 MySQL 内部格式的数据。其中 writerow 对应的查 询语句是:inser
t 和 replace;updaterow 对应的是 update 语句;deleterow 对应的是 delete 语

句。而 writerow 事件中除了环境数据以外还包含了对应的行数据为 record[0]。

record[0]中包含有将要插入 MySQL 表中的一行数据。 Update 语句包含两个行

数据,record[0]表示将要更新存储到表中的行数据,而 record[1]中包含了 upda

te 将要替换的已存在于表中 的行数据。Delete 的 record[0]中包含的是将要删除

的行数据。由于 binlog 中的数据都是二进制的 MySQL 格式的数据,而我们也没

有找到 直接将这些数据插入 MySQL server 的接口,所以在对这些数据操作之

前,我们首先必须将这些数据转换回可读数据。然后就可以直接向 MySQL ser

ver 提交对应的查询语句。

每种不同的类型在 MySQL 中保存的格式都不一样,MySQL 5.1.20 Beta 的数

据类型见附录 3。


MySQL replication 数据复制格式


这 里我们基于 MySQL 5.1.20 Beta 描述 MySQL 两个 slave 端的 thread 发送

和接收数据的格式。某些字段所占的字节数跟 MySQL 的版本有关,这里我们所

描述的为 binlog 版本为 4,MySQL server 版本为 5.1.20 Beta 下的数据格式。


MySQL I/O thread 数据格式


向主服务器注册自己


向 主服务器注册自己并不是一个必须的操作,如果没有注册同样可以向主服务

器请求数据。如果需要向主服务器注册,那么可以调用 mysql.h 中的 simple_c

ommand(mysql, command, arg, length, skip_check)函数,在 arg 参数中依序
填入下述的各个字段,并指定其中的参数 command 为 COM_REGISTER_SLA

VE 以注册自 己。


名称               字节数          含义


server_id        4            本 MySQL instance 的 server_id 值


strlen(report_hos 1 or 2      标识接下来的 report_host 的长度,如果长

t)                            度<251 占 1 个字节,否则占两个字节


report_host      Strlen(report_hos 向主服务器注册的 MySQL instance 标识

                 t)


strlen(report_use 1 or 2      标识接下来的 report_user 的长度,如果长

r)                            度<251 占 1 个字节,否则占 2 个字节


report_user      Strlen(report_use 向主服务器注册的用户名

                 r)


strlen(report_pas 1 or 2      标识接下来的 report_password 的长度,如

sword)                        果长度<251 占 1 个字节,否则占 2 个字节


report_password Strlen(report_pas 向主服务器注册的密码

                 sword)


report_port      2            向主服务器注册的端口


rpl_recovery_ran 4            复制的恢复等级

k


master_id        4            填入 0,主服务器将自行填入 master_id 值
register_master

图 1、主服务器注册示意图


向主服务器请求数据


从 服务器向主服务器发送了请求数据的命令以后主服务器将根据要求将对应 bi

nlog 文件的指定位置开始的事件记录发送给从服务器。向主服务器请求数据,

可以 调用 mysql.h 中的 simple_command(mysql, command, arg, length, ski

p_check)函数,在 arg 参数中依序填入下述的各个字段,并指定其中的参数 co

mmand 为 COM_BINLOG_DUMP。


名称                字节数               含义


master_log_pos    4                 请求主服务器发送的事件记录在 bin

                                    log 文件中的偏移量


binlog_flags      2                 暂时填 0,做扩展用


server_id         4                 本 MySQL instance 的 server_id 值


logname           Strlen(logname)   请求主服务器发送的 binlog 文件的

                                    文件名


如果没有指定 MySQL 使用 methods,那么我们应该调用函数 sql_common.h 头

文件中的 cli_advanced_command()代替 simple_command()。
向 主服务器请求了数据以后,从服务器就可以通过 cli_safe_read(mysql);获得

主服务器发送过来的数据,每次获得一个事件记录的数据。 cli_safe_read 的返

回值标示了从主服务器发送过来的数据的数据字节数。而发送过来的数据保存在

 mysql->net->read_pos 数组中。I/O thread 模块可以利用 MySQL 的 io_cache

将对应事件记录存储到 relay-log 文件中。


MySQL binlog 文件初始化


由于 MySQL binlog 的特殊性,以及为了 mysqlbinlog 工具能够识别我们 relay-l

og 文件,在新建一个 relay-log 文件时必须写入一定的初始化数据。这些初始化

数据依序包括如下字段:


名称               字节数              含义


BINLOG_MAGIC BIN_LOG_HEADER_SI Binlog 文件的标识值

(即”xfex62x69x ZE(4)

6e”)


MySQL SQL thread 数据格式


只 要循环的调用 cli_safe_read 函数,从服务器可以不断得到从主服务器发送过

来的事件记录。接下来我们介绍一下相关的一些事件记录格式。在提交了 COM

_BINLOG_DUMP 命令后,主服务器首先给从服务器发送的两个事件依序分别为

ROTATE_EVENT 和 FORMAT_DESCRIPTION_EVENT 事件。ROTATE_EVE

NT 事件用来标示接下来主服务器将从哪一个 binlog 文件的哪个位置开始 发送

事件记录。而 FORMAT_DESCRIPTION_EVENT 事件用来记录本 MySQL inst
ance 的 server_id 值,binlog 版本号,MySQL server 的版本,本 relay-log 创建

的时间以及各个不同事件的事件头所占的字节数等信息。我们关心的其他的事件

记录的格式包括 WRITE_ROWS_EVENT,UPDATE_ROWS_EVENT,DELET

E_ROWS_EVENT 等。


事件头字段描述


各个事件都包括一个事件头,事件头的字段格式如下:


名称          字 节 含义

            数


When        4   事件的创建时间。


Type        1   事件的类型(见附录 1)。


server_id   4   事件发生时所在 MySQL 的 server_id 值。


data_writte 4   该事件一共占用的字节数,包括事件头的字节数。

n


log_pos     4   下一事件在 binlog 文件中将要开始的位置,即本事件的结束位

                置


Flags       2   事件的其他标志位。



ROTATE_EVENT 事件字段描述


由于各个事件的事件头基本一致,这里我们就不重复介绍事件头的各字段了,后

面的各个事件我们也都将忽略对事件头字段的描述。
ROTATE_EVENT 事件的附加事件头字段主要包括:


名称         字 节 含义

           数


pos        8       主服务器将要发送的事件记录在 binlog 文件中的偏移量。一般

                   为从服务器提交的 COM_BINLOG_DUMP 请求中的偏移量值。


ROTATE_EVENT 事件的其他信息字段主要包括:


名称        字节数           含义


new_log_i strlen(new_log_ 主服务器将要发送的事件记录的 binlog 文件名。一

dent      ident)        般为从服务器提交的 COM_BINLOG_DUMP 请求中

                        的 binlog 文件名。



FORMAT_DESCRIPTION_EVENT 事件字段描述


FORMAT_DESCRIPTION_EVENT 事件的附加事件头的字段如下:


名称             字节数            含义


binlog_versi 2                Binlog 文件的版本号,这里一般为最新的版

on                            本号 4


server_versi ST_SERVER_VER_L MySQL 的版本号。例如: 5.1.20-beta-log”
                                           ”

on             EN(50)


Created        4              事件创建时间,这里一般和事件头中的 wh

                              en 一致
event_head 1                  一般事件的事件头长度,一般设置为:LOG

er_len                        _EVENT_HEADER_LEN(19)


post_header ENUM_END_EVENT- 不同事件类型的附加事件头的长度,见附录

_len         1(26)            2。



TABLE_MAP_EVENT 事件字段描述


TABLE_MAP_EVENT 事件的附加事件头的字段如下:


名称           字节数                    含义


m_table_id   6(5.1.4 前的版本中为 4)      表的 id 标识符


m_flags      2                      表的各种标志位,见附录 4


TABLE_MAP_EVENT 事件的其他信息字段主要包括:


名称        字节数        含义


m_dbl 1              数据库名的长度

en


m_dbn m_dblen+1      数据库名,以’0’结尾

am


m_tblle 1            表名的长度

n


m_tbln m_tbllen+1    表名,以’0’结尾

am
m_colc net_field_len 表的字段个数,所占字节数根据第一个字节的大小由 net_f

nt        gth()         ield_length 函数确定


m_colt m_colcnt         表的各个字段的字段类型,参见附录 3。

ype



WRITE_ROWS_EVENT 事件字段描述


WRITE_ROWS_EVENT 事件的附加事件头的字段如下:


名称                字节数                      含义


m_table_id        6(5.1.4 前的版本中为 4)        表的 id 标识符


m_flags           2                        表的各种标志位,见附录 4


WRITE_ROWS_EVENT 事件的其他信息字段主要包括:


名称           字节数             含义


m_width      net_field_length() 表的各列的位图长度,所占字节数根据第一个字节

                             的大小由 net_field_length 函数确定


m_cols.bit (m_width + 7) / 表的各列的位图,每一位表示 m_rows_buf 是否包

map           8              含表中一列的值,如果没有置位表示该列的值没有

                             包含在 m_rows_buf 中


m_rows_b 剩余字节数(len- 将要插入到表中的一行数据值。

uf           已占字节数)
UPDATE_ROWS_EVENT 事件字段描述


UPDATE_ROWS_EVENT 事件的附加事件头的字段如下:


名称           字节数                       含义


m_table_id   6(5.1.4 前的版本中为 4)         表的 id 标识符


m_flags      2                         表的各种标志位,见附录 4


UPDATE_ROWS_EVENT 事件的其他信息字段主要包括:


名称           字节数                  含义


m_width      net_field_length()   表的各列的位图长度,所占字节数根据第一

                                  个字节的大小由 net_field_length 函数确定


m_cols.bitm (m_width + 7) / 8     表中被匹配行数据的各列的位图,每一位表

ap                                示 m_rows_buf 是否包含表中该列的值。


m_cols_ai.bi (m_width + 7) / 8    表中将要更新的行数据的各列的位图,每一

tmap                              位表示 m_rows_buf 是否包含表中一列的

                                  值。


m_rows_buf 剩余字节数(len-已占字 表中被匹配的那一行数据的值以及将要更

             节数)                  新的一行数据值。



DELETE_ROWS_EVENT 事件字段描述


DELETE_ROWS_EVENT 事件的附加事件头的字段如下:
名称                 字节数                         含义


m_table_id         6(5.1.4 前的版本中为 4)           表的 id 标识符


m_flags            2                           表的各种标志位


DELETE _ROWS_EVENT 事件的其他信息字段主要包括:


名称           字节数             含义


m_width      net_field_length() 表的各列的位图长度,所占字节数根据第一个字节

                             的大小由 net_field_length 函数确定


m_cols.bit (m_width + 7) / 表的各列的位图,每一位表示 m_rows_buf 是否包

map           8              含表中一列的值。


m_rows_b 剩余字节数(len- 表中将要删除的一行数据值。

uf           已占字节数)



XID_EVENT 事件字段描述


XID_EVENT 一般出现在一个事务操作(transaction)之后或者其他语句提交之后。

它的主要作用是提交事务操作和把事件刷新至 binlog 文件中。

XID_EVENT 事件的信息字段包括:


名称           字节数             含义


xid          sizeof(xid) 8   commit 标识符
测试过程


测试进度安排


2007-07-26 —— 2007-08-03

理解和分析 MySQL slave 端 I/O 线程以及 SQL 线程的实现细节。

2007-08-04 —— 2007-08-08

模拟实现 MySQL replication 的 I/O 线程,实现向 master 请求 binlog,并记录

读到的信息到一个本地日志文件 relay-log 中的功能。使用 mysqlbinlog 应该能

查看该日志。

2007-08-09 —— 2007-08-15

模拟实现 MySQL replication 的 SQL 线程,实现读取并解析 relay-log 文件,设

置 slave 端的执行环境以提交查询。

2007-09-03 —— 2007-09-07

熟悉并了解 MySQL 5.1.20 Beta 中 row based replication 配置以及实现。

2007-09-10 —— 2007-09-21

模拟实现 MySQL 5.1.20 Beta 中 insert,update,delete 等语句的在 slave 段

的解析并生成对应的语句。

2007-09-24 —— 2007-09-30

阅读和了解 replication 冲突解决方案,分析 update(delete)更新 0 row 冲突

和 update(delete)复制执行空语句之间的区别。
测试过程描述


通过对 MySQL 源代码的阅读,我们了解并熟悉了 MySQL replication 的基本原

理和实现细节。

对 MySQL I/O 线程的模拟相对比较简单,这个线程的向 master 注册自己及请

求发送 binlog 信息都有相应的接口提供出来,并且对于从 master 端接收的信

息也只做了比较简单的分析就直接将该信息存入 relay-log 日志文件中。所以我

们模拟 I/O 线程比较顺利。

对于 MySQL 的 SQL 线程的模拟实现中,读取和解析 relay-log 日志文件虽然比

较繁琐,但是基本实现的困难不大。主要的困难出现在如何设置 slave 端的执行

环境。

这 里设置 slave 端的执行环境包括从 master 端复制过来的一些环境信息,比如:

server_id,charset,timezone, auto_increment_increment,auto_increment

_offset 等等。其中最重要的是 server_id,它表示 master 发送过来的 binlog 信

息中,将要执行的这一条语句最开始是在哪一个 MySQL server 中提交执行的。

如果配置了环形的 replication 链,并且复制过来的 server_id 与本机的 server_i

d 相等的话,那么说明 这条语句最开始就是在本机中执行的,它对应的语句应

该被忽略。

如果从 master 服务器复制过来的 server_id 与本机的 server_id 不同,那么就应

该在本机执行这条语句。这里我们特别要注意的是:在记录本地 binlog 日志文

件时,server_id 应该保证为复制 过来的 server_id,而不是我们普通提交查询
时记录的本地的 server_id。不然在配置双向复制和环形复制链时将造成数据复

制的死循环,从而造 成数据紊乱。

我们仔细察看了 SQL 线程的源代码,发现它在读取 relay-log 记录时会把复制过

来的 server_id 信息保存在对应 的 SQL 线程变量 thd->server_id 中,从而在写

本机 binlog 日志文件时记录的 server_id 将会是从 master 端复制过来的 server

_id 信息。如果我们要模拟 SQL 线程,我们需要在提交查询前修改连接会话的相

应变量。但由于 server_id 在 MySQL 中是一个全局变 量,而不是一个会话期变

量,所以我们不能在连接中修改这个变量(不然,从修改了 server_id 后到恢复

本机的 server_id 值之间的这一段时间 里,在 binlog 日志文件中记录的用户提

交的语句对应的 server_id 值将保持为修改后的值,而不是本机 server_id 值)。

在写 binlog 日志时,写入的 server_id 值依赖于连接线程中的 server_id 值,而

MySQL server 也没有提供任何修改连接线程中对应的 server_id 变量值的接口。

这样,我们无法模拟 SQL 线程来执行复制过来的语句。

为 了能够标识数据最新的插入和更新 site,我们在每一个数据库表上都增加了

一列 server_id。在对表执行 insert 操作时,server_id 将 自动赋值为该 site 的 s

erver_id 值。也就是说,在增加 server_id 列时设置它的 default 值为该 MySQL

的 server_id 值。 而为了保证在本机执行的 update 操作对 server_id 有同样的

影响,我们可以借助 MySQL 的 trigger 功能,使得在本机上执行的 update 操作

修改行数据值中 server_id 的值为本机的 server_id 值。另外,我们还要注意的

是用户在提交查询时不应该自己操作 server_id 值,而应该通过我们设置的 My

SQL 的已有机制进行操作,以防出现数据复制的死循环。下面我们分别描述 in

sert,update,delete 操作的提交和复制过程。
1. Insert 的提交和复制:


由 于在 MySQL 的 row based replication 中 insert 和 replace 语句在 binlog 中

都被记录为 write row 事件,所以我们把 replace 语句的提交和复制合并到 inse

rt 的提交和复制中。Insert 的提交和复制相对来说比其他的的操作简单。我们只

要设置 server_id 列的 default 值为对应 MySQL server 的 server_id 值,当用

户提交 insert 查询时,server_id 将自动赋值。 MySQL 将把 insert 插入的每一
                               而

行数据自动 地对应一个 write row 事件并记录在 binlog 中。其他的 slave 可以

通过我们的工具将 binlog 文件复制到本地,然后解析 write row 以生成对应的 r

eplace 语句。通过 server_id 列的值我们可以很容易的知道最开始提交 insert 语

句的 MySQL 的 server_id 值,从而在该 write row 事件复制到该 MySQL insta

nce 时停止复制,而避免数据复制的死循环。


1. Update 的提交和复制:


Update 的提交和复制比较复杂。在一个 MySQL 上提交的 update 语句将被对

应为一个 update row 事件记录在 binlog 文件中,slave 端复制并解析 update r

ow 事件生成对应的 update 语句。这里我们举一个数据复制的例子:如果某一条

数据首先在 MySQL instance 1(M1)上插入,那么该数据的 server_id 列值为

1,该数据复制到 MySQL instance 2(M2)数据将保持不变。但是,如果此时

在 M2 上提交的 update 语句更新了该行数据,那么 server_id 值仍然保持为 1。

如果该 update row 事件重新复制回 M1,那么我们的复制工具发现该行数据的 s

erver_id 值与本 MySQL instance 的 server_id 值一样,认为该 update 语句最开
始就是在本机提交执行的,它将忽略该 update 语句。针对这个问题,我们有两

种解决方案:


1.

     1.

          1. 由于在 MySQL server 中,update 要更新的那一行数据匹配不成

            功那么对于 MySQL 数据库的将不会有任何修改,并且这一条 upd

            ate 语句也将不会记录到 binlog 文件中。我们可以利用这一点允许

            multi-master 工具在解析 update row 事件时忽略对 server_id 的检

            查,允许从其他 MySQL instance 复制的在本 MySQL instance

            提交的 update 语句继续执行,实际上该语句将由于匹配不到对应

            的数据而执行空操作。这里执行的空操作实际上和我们后面要讨论

            的一种冲 突类型(slave 端提交的查询不能更新数据冲突)是一样

            的。


这里我们仍然用上面的例子进行说明:

首 先在 M1 上提交的 insert 语句被复制到 M2 并插入对应的数据,而当用户更

新这一行数据后,update row 事件复制回 M1 时,由于 multi-master 工具不再

检查 server_id 值,那么同样的 update 语句将在 M1 执行。M2 重新复制得到 u

pdate row 事件并生成对应 update 语句,但是由于该 update 语句是最先在 M2

提交成功的,那么正常情况下该 update 语句不能匹配到要修改的那一行 数据,

从而执行一条空语句,不会出现数据复制的死循环。当然,如果在复制回 M2 数
据前有其他的语句 insert 或者 update 生成了若干行能够被匹配的 数据的话,这

种方案是行不通的。


1.

     1.

          1. 第二种解决方案相对复杂一些。它保证了 update 语句更新行数据

           的 server_id 值为本 MySQL server 的 server_id 值。我们利用了

           MySQL 提供的 trigger, update 语句提交执行之前改变行数据 N
                            在

           EW 的 server_id 值为本 MySQL server 的 server_id 值。(在 My

           SQL 的 trigger 中,OLD 行数据表示数据库中已有的将要被 updat

           e 匹配的数据,而 NEW 行数据 表示 update 语句将要更新为其值

           的行数据)。但是,由于我们的 multi-master 工具将生成的语句直

           接提交到本 MySQL instance 中执行,那么如果我们的 trigger 不

           能识别工具提交的语句和用户提交的语句,而把 NEW 行数据的 s

           erver_id 值全部改成本 instance 的值,复制的死循环一定会出现。

           例如:在上面的例子中,M1 的 server_id 为 1,在 M2 上 update

           的数据复制到 M1 上提交执 行,如果 trigger 不能识别出它是从 m

           ulti-master 工具生成的语句,而是把它的 NEW 行数据修改为 1,

           那么记录在 M1 的 binlog 中的数 据将不能标识最开始提交语句的

           MySQL instance 位置,从而不能中断数据复制的循环。至少有两

           种方法可以区分普通的用户提交语句和 multi-master 工具提交的

           语句。 专门指定 一个用户用于 multi-master 工具提交查询语句,
              1,

           以区别于其他用户提交的语句。在 trigger 中我们可以用 user()
函数获得用户名来确定查 询语句提交的来源。2,通过 multi-mas

               ter 修改相应语句而且使得它提交的所有语句与普通用户提交的语

               句不同。下面我们详细阐述第二种策略。


我 们发现用户提交的所有 update 语句都有一个共同的特点:OLD 行数据和 N

EW 行数据的 server_id 值相等。如果我们在用工具生成 OLD 行数据和 NEW 行

数据的 server_id 值相等的 update 语句时,将 NEW 行数据的 server_id 值改变

(例如改为 0)以区别于用户提交的语句。这样 trigger 就能根据是否用户提交

的语句而进行相应的操作了。一般来说,我们的 trigger 可以写成如下的形式:

delimiter //

create trigger update_set_srvid_test_test001 before update on test001 for

each row

BEGIN

IF NEW.server_id=OLD.server_id THEN

SET NEW.server_id=1;

ELSEIF NEW.server_id=0 THEN

SET NEW.server_id=OLD.server_id;

END IF;

END;//

delimiter ;


 1. Delete 的提交和复制:
Delete 的提交和复制和 update 的基本类似。在一个 MySQL 上提交的 delete 语

句将被对应为一个 delete row 事件记录在 binlog 文件中,slave 端复制文件并解

析 delete row 事件生成对应的 delete 语句提交执行。为了解决本 MySQL 上 in

sert 的数据在别的 MySQL instance 上 delete 的问题,我们的第一个解决方案

和 update 一样,也是忽略对 server_id 值的检查。同样,它存在着两个缺点:1.

它与冲突解决方案中的一种类型是一样的,我们不能区别这样的一个空操作是

否是一个冲突。2. 如果在 delete 语句复制回本 instance 前,有其他的语句产生

了该 delete 语句能够匹配的行数据,那么这一行数据将被错误的删除掉。同时,

因 为 delete 不包括 NEW 行数据,update 的第二个解决方案对 delete 语句不适

用。



测试结论


测试最终结论


通过上面的测试和分析,我们发现可以通过增加 server_id 列和增加 trigger 等手

段模拟 MySQL 的 replication 功能,从而实现 MySQL 的多主复制工具 multi-ma

ster。



附录


附录 1 MySQL 5.1.20 Beta 包含的事件类型


下面列举了各种 MySQL 的事件类型(代码拷贝自 MySQL 5.1.20 的源代码):

enum Log_event_type
{

/*

Every time you update this enum (when you add a type), you have to

fix Format_description_log_event::Format_description_log_event().

*/

UNKNOWN_EVENT= 0,

START_EVENT_V3= 1,

QUERY_EVENT= 2,

STOP_EVENT= 3,

ROTATE_EVENT= 4,

INTVAR_EVENT= 5,

LOAD_EVENT= 6,

SLAVE_EVENT= 7,

CREATE_FILE_EVENT= 8,

APPEND_BLOCK_EVENT= 9,

EXEC_LOAD_EVENT= 10,

DELETE_FILE_EVENT= 11,

/*

NEW_LOAD_EVENT is like LOAD_EVENT except that it has a longer

sql_ex, allowing multibyte TERMINATED BY etc; both types share the
same class (Load_log_event)

*/

NEW_LOAD_EVENT= 12,

RAND_EVENT= 13,

USER_VAR_EVENT= 14,

FORMAT_DESCRIPTION_EVENT= 15,

XID_EVENT= 16,

BEGIN_LOAD_QUERY_EVENT= 17,

EXECUTE_LOAD_QUERY_EVENT= 18,

TABLE_MAP_EVENT = 19,

/*

These event numbers were used for 5.1.0 to 5.1.15 and are

therefore obsolete.

*/

PRE_GA_WRITE_ROWS_EVENT = 20,

PRE_GA_UPDATE_ROWS_EVENT = 21,

PRE_GA_DELETE_ROWS_EVENT = 22,

/*

These event numbers are used from 5.1.16 and forward

*/
WRITE_ROWS_EVENT = 23,

UPDATE_ROWS_EVENT = 24,

DELETE_ROWS_EVENT = 25,

/*

Something out of the ordinary happened on the master

*/

INCIDENT_EVENT= 26,

/*

Add new events here – right above this comment!

Existing events (except ENUM_END_EVENT) should never change their

numbers

*/

ENUM_END_EVENT /* end marker */

};


附录 2 MySQL 5.1.20 Beta 各事件的附加事件头长度


下面列举了 MySQL 5.1.20 Beta 各事件的附加事件头长度(拷贝自 MySQL 源

代码):

/* event-specific post-header sizes */

// where 3.23, 4.x and 5.0 agree
#define QUERY_HEADER_MINIMAL_LEN (4 + 4 + 1 + 2)

// where 5.0 differs: 2 for len of N-bytes vars.

#define QUERY_HEADER_LEN (QUERY_HEADER_MINIMAL_LEN + 2)

#define LOAD_HEADER_LEN (4 + 4 + 4 + 1 +1 + 4)

#define START_V3_HEADER_LEN (2 + ST_SERVER_VER_LEN + 4)

#define ROTATE_HEADER_LEN 8 // this is FROZEN (the Rotate post-he

ader is frozen)

#define CREATE_FILE_HEADER_LEN 4

#define APPEND_BLOCK_HEADER_LEN 4

#define EXEC_LOAD_HEADER_LEN 4

#define DELETE_FILE_HEADER_LEN 4

#define FORMAT_DESCRIPTION_HEADER_LEN (START_V3_HEADER_L

EN+1+LOG_EVENT_TYPES)

#define ROWS_HEADER_LEN 8

#define TABLE_MAP_HEADER_LEN 8

#define EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN (4 + 4 + 4 +

1)

#define EXECUTE_LOAD_QUERY_HEADER_LEN (QUERY_HEADER_LEN

 + EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN)

#define INCIDENT_HEADER_LEN 2
post_header_len[START_EVENT_V3-1]= START_V3_HEADER_LEN;

post_header_len[QUERY_EVENT-1]= QUERY_HEADER_LEN;

post_header_len[ROTATE_EVENT-1]= ROTATE_HEADER_LEN;

post_header_len[LOAD_EVENT-1]= LOAD_HEADER_LEN;

post_header_len[CREATE_FILE_EVENT-1]= CREATE_FILE_HEADER_LEN;

post_header_len[APPEND_BLOCK_EVENT-1]= APPEND_BLOCK_HEADER

_LEN;

post_header_len[EXEC_LOAD_EVENT-1]= EXEC_LOAD_HEADER_LEN;

post_header_len[DELETE_FILE_EVENT-1]= DELETE_FILE_HEADER_LEN;

post_header_len[NEW_LOAD_EVENT-1]= post_header_len[LOAD_EVENT-

1];

post_header_len[FORMAT_DESCRIPTION_EVENT-1]= FORMAT_DESCRIP

TION_HEADER_LEN;

post_header_len[TABLE_MAP_EVENT-1]= TABLE_MAP_HEADER_LEN;

post_header_len[WRITE_ROWS_EVENT-1]= ROWS_HEADER_LEN;

post_header_len[UPDATE_ROWS_EVENT-1]= ROWS_HEADER_LEN;

post_header_len[DELETE_ROWS_EVENT-1]= ROWS_HEADER_LEN;

/*

We here have the possibility to simulate a master of before we changed

the table map id to be stored in 6 bytes: when it was stored in 4
bytes (=> post_header_len was 6). This is used to test backward

compatibility.

This code can be removed after a few months (today is Dec 21st 2005),

when we know that the 4-byte masters are not deployed anymore (chec

k

with Tomas Ulin first!), and the accompanying test (rpl_row_4_bytes)

too.

*/

DBUG_EXECUTE_IF(“old_row_based_repl_4_byte_map_id_master”,

post_header_len[TABLE_MAP_EVENT-1]=

post_header_len[WRITE_ROWS_EVENT-1]=

post_header_len[UPDATE_ROWS_EVENT-1]=

post_header_len[DELETE_ROWS_EVENT-1]= 6;);

post_header_len[BEGIN_LOAD_QUERY_EVENT-1]= post_header_len[APPE

ND_BLOCK_EVENT-1];

post_header_len[EXECUTE_LOAD_QUERY_EVENT-1]= EXECUTE_LOAD_

QUERY_HEADER_LEN;

post_header_len[INCIDENT_EVENT-1]= INCIDENT_HEADER_LEN;


附录 3 MySQL 5.1.20 Beta 中各列在内部存储时可能的各种数据类型


enum enum_field_types
{

MYSQL_TYPE_DECIMAL = 0,

MYSQL_TYPE_TINY = 1,

MYSQL_TYPE_SHORT = 2,

MYSQL_TYPE_LONG = 3,

MYSQL_TYPE_FLOAT = 4,

MYSQL_TYPE_DOUBLE = 5,

MYSQL_TYPE_NULL = 6,

MYSQL_TYPE_TIMESTAMP = 7, // 4 from_unixtime(0x)

MYSQL_TYPE_LONGLONG = 8,

MYSQL_TYPE_INT24 = 9, //field_medium

MYSQL_TYPE_DATE = 10,

MYSQL_TYPE_TIME = 11,

MYSQL_TYPE_DATETIME = 12,

MYSQL_TYPE_YEAR = 13,

MYSQL_TYPE_NEWDATE = 14,

MYSQL_TYPE_VARCHAR = 15, //field_varstring

MYSQL_TYPE_BIT = 16,

MYSQL_TYPE_NEWDECIMAL = 246,

MYSQL_TYPE_ENUM = 247,
MYSQL_TYPE_SET = 248,

MYSQL_TYPE_TINY_BLOB = 249,

MYSQL_TYPE_MEDIUM_BLOB = 250,

MYSQL_TYPE_LONG_BLOB = 251,

MYSQL_TYPE_BLOB = 252,

MYSQL_TYPE_VAR_STRING = 253,

MYSQL_TYPE_STRING = 254,

MYSQL_TYPE_GEOMETRY = 255,

};


附录 4 MySQL 5.1.20 Beta 中各 ROW_EVENT 的 m_flags 包含的标志位


在 MySQL 5.1.20 Beta 中,各 ROW_EVENT 都含有 m_flags 标志位集合。可

能的标志如下:


名称                                    值 含义


STMT_END_F                            1   语句执行结束标志


NO_FOREIGN_KEY_CHECKS_F               2   不进行外键约束检查的标志


RELAXED_UNIQUE_CHECKS_F               4   不进行唯一键约束检查的标志


相关代码如下:

/*

These definitions allow you to combine the flags into an
appropriate flag set using the normal bitwise operators. The

implicit conversion from an enum-constant to an integer is

accepted by the compiler, which is then used to set the real set

of flags.

*/

enum enum_flag

{

/* Last event of a statement */

STMT_END_F = (1U << 0),

/* Value of the OPTION_NO_FOREIGN_KEY_CHECKS flag in thd->option

s */

NO_FOREIGN_KEY_CHECKS_F = (1U << 1),

/* Value of the OPTION_RELAXED_UNIQUE_CHECKS flag in thd->option

s */

RELAXED_UNIQUE_CHECKS_F = (1U << 2)

};

typedef uint16 flag_set;

/* Special constants representing sets of flags */

enum

{
RLE_NO_FLAGS = 0U

};


MySQL row 方式的复制 relay


上次老大问我 row 方式的 binlog 复制到备机可不可能记录为 statement。根据我

对复制的了解和对 row 方式的理解,我回答的是不可能。因为 MySQL 的源码中,

我记得 row 方式的处理是 table map,row_log_event 分开的,然后 row_log_ev

ent 中记录的是行的数据(包括 bitmap 对应对应的列在该 row_log_event 有没有

记录,整个 row_log_event 的长度,每个列的长度后面跟上列的具体数据等),

这些东西记录下载,MySQL 的开发者如果要把它还原成 statement 方式,并且

将相关的 auto_increment,var,rand 等还原出来难度还是非常大的,并且,row

方式实际上已经毁坏了 statement 的结构(比如:update tbl1 set c1=3 where id

=3 记录为 row 的话,正常情况下,row_log_event 不会只记录了更新的这一列 c

1,它会记录 id 或者其他的列,虽然 id 或者其他的列值并没有更新。原因可能

是为了正确的更新对应的行。),如果想完全还原成和主机上提交的 statement

一模一样基本是不可能的。另外,我也没有看到 MySQL 源码中的相关代码有将

row 转换成 statement 的相关痕迹,所以判断 row 方式在备机中 log_slave_upd

ates 会也被记录为 row 方式。

但是,口说无凭,实践致胜,我在主备机环境下测试了上面的这个情况。确实如

上所述,row 方式的 binlog,备机利用 log_slave_updates 记录到本机 binlog 也

是 row 方式。即使你设置备机的 binlog_format 为 statement。下面是我的测试

描述和结果。
1、主机上设置 binlog_format 为 row.

root@localhost : alitest 10:01:09> set binlog_format=row;

Query OK, 0 rows affected (0.00 sec)

root@localhost : alitest 10:01:25> show variables like ‘bin%’;

+——————-+———+

| Variable_name | Value |

+——————-+———+

| binlog_cache_size | 2097152 |

| binlog_format | ROW |

+——————-+———+

2 rows in set (0.00 sec)

2、备机上设置 binlog_format 为 statement

root@localhost : (none) 10:06:44> set global binlog_format=’statement’;

Query OK, 0 rows affected (0.00 sec)

root@localhost : (none) 10:07:04> set binlog_format=’statement’;

Query OK, 0 rows affected (0.00 sec)

root@localhost : (none) 10:07:11> show variables like ‘bin%’;

+——————-+———–+

| Variable_name | Value |

+——————-+———–+

| binlog_cache_size | 2097152 |

| binlog_format | STATEMENT |
+——————-+———–+

2 rows in set (0.00 sec)

3、主机上创建测试表:

root@localhost : (none) 10:00:02> use alitest;

Database changed

root@localhost : alitest 10:00:50> create table test9 (c1 int unsigned pri

mary key, c2 varchar(24));

Query OK, 0 rows affected (0.20 sec)

4、备机上查看目前的日志位置:

root@localhost : (none) 10:04:49> show master logs;

+——————+———–+

| Log_name | File_size |

+——————+———–+

| mysql-bin.000001 | 125 |

…

| mysql-bin.000085 | 362605228 |

+——————+———–+

85 rows in set (0.00 sec)

4、主机上生成测试的 row 方式日志:

root@localhost : alitest 10:01:27> insert into test9 (c1,c2) values (44, “34

3434″);

Query OK, 1 row affected (0.00 sec)
root@localhost : alitest 10:02:53> insert into test9 (c1,c2) values (3, “343

434″);

Query OK, 1 row affected (0.00 sec)

5、备机查看自己生成 binlog:

root@localhost : (none) 10:07:53> show binlog events in ‘mysql-bin.0000

85′ from 362605228;

+——————+———–+————+———–+————-+—————————

—–+

| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |

+——————+———–+————+———–+————-+—————————

—–+

| mysql-bin.000085 | 362605228 | Query | 1 | 362605287 | BEGIN |

| mysql-bin.000085 | 362605287 | Table_map | 1 | 362605337 | table_id:

 27 (alitest.test9) |

| mysql-bin.000085 | 362605337 | Write_rows | 1 | 362605378 | table_id:

 27 flags: STMT_END_F |

| mysql-bin.000085 | 362605378 | Xid | 1 | 362605405 | COMMIT /* xid=

23537907 */ |

| mysql-bin.000085 | 362605405 | Query | 1 | 362605464 | BEGIN |

| mysql-bin.000085 | 362605464 | Table_map | 1 | 362605514 | table_id:

 27 (alitest.test9) |

| mysql-bin.000085 | 362605514 | Write_rows | 1 | 362605555 | table_id:
27 flags: STMT_END_F |

| mysql-bin.000085 | 362605555 | Xid | 1 | 362605582 | COMMIT /* xid=

23537917 */ |

+——————+———–+————+———–+————-+—————————

—–+

8 rows in set (0.00 sec)

由上可以看到,备机虽然设置自己的 binlog_format 为 statement,binlog 日志中

记录从主机过来的 binlog 仍然为 row。写到这里,我突然想起,binlog_format

是否只是对在本机提交的 sql 才有效,于是我测试了以下两种情况:

1、主机为 statement,备机为 statement。 结果备机记录为 statement.

2、主机为 statement,备机为 row。结果备机记录为 statement.

也就是说,备机记录的从主机复制过来的 binlog 不随自己的 binlog_format 方式

改变,而是忠实的依照主机记录的方式来记录。

下面是简单的测试结果:

测试 1 主机为 statement,备机为 statement。 结果备机记录为 statement.

1、主机上设置 binlog_format 为 row 并插入一行

root@localhost : alitest 10:42:41> set binlog_format=statement;

Query OK, 0 rows affected (0.00 sec)

2、备机上设置为 statement

root@localhost : (none) 10:06:44> set global binlog_format=’statement’;

Query OK, 0 rows affected (0.00 sec)
root@localhost : (none) 10:07:04> set binlog_format=’statement’;

Query OK, 0 rows affected (0.00 sec)

root@localhost : (none) 10:07:11> show variables like ‘bin%’;

+——————-+———–+

| Variable_name | Value |

+——————-+———–+

| binlog_cache_size | 2097152 |

| binlog_format | STATEMENT |

+——————-+———–+

2 rows in set (0.00 sec)

3、主机上插入一行

root@localhost : alitest 10:42:54> insert into test9 (c1,c2) values (4, “343

434″);

Query OK, 1 row affected (0.00 sec)

4、备机上查看日志:

root@localhost : (none) 10:35:48> show binlog events in ‘mysql-bin.0000

85′ from 362605228;

+——————+———–+————+———–+————-+—————————

————————————+

| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |

+——————+———–+————+———–+————-+—————————

————————————+
| mysql-bin.000085 | 362605228 | Query | 1 | 362605287 | BEGIN |

| mysql-bin.000085 | 362605287 | Table_map | 1 | 362605337 | table_id:

 27 (alitest.test9) |

| mysql-bin.000085 | 362605337 | Write_rows | 1 | 362605378 | table_id:

 27 flags: STMT_END_F |

| mysql-bin.000085 | 362605378 | Xid | 1 | 362605405 | COMMIT /* xid=

23537907 */ |

| mysql-bin.000085 | 362605405 | Query | 1 | 362605464 | BEGIN |

| mysql-bin.000085 | 362605464 | Table_map | 1 | 362605514 | table_id:

 27 (alitest.test9) |

| mysql-bin.000085 | 362605514 | Write_rows | 1 | 362605555 | table_id:

 27 flags: STMT_END_F |

| mysql-bin.000085 | 362605555 | Xid | 1 | 362605582 | COMMIT /* xid=

23537917 */ |

| mysql-bin.000085 | 362605582 | Query | 1 | 362605641 | BEGIN |

| mysql-bin.000085 | 362605641 | Query | 1 | 362605753 | use `alitest`;

insert into test9 (c1,c2) values (4, “343434″) |

| mysql-bin.000085 | 362605753 | Xid | 1 | 362605780 | COMMIT /* xid=

23537922 */ |

+——————+———–+————+———–+————-+—————————

————————————+

11 rows in set (0.00 sec)
测试 2 主机为 statement,备机为 row。结果备机记录为 statement.

1、主机上设置 binlog_format 为 row 并插入一行

root@localhost : alitest 10:42:41> set binlog_format=statement;

Query OK, 0 rows affected (0.00 sec)

2、备机上设置为 statement

root@localhost : (none) 10:46:23> set binlog_format=’row’;

Query OK, 0 rows affected (0.00 sec)

root@localhost : (none) 10:46:28> set global binlog_format=’row’;

Query OK, 0 rows affected (0.00 sec)

root@localhost : (none) 10:46:37> show variables like ‘bin%’;

+——————-+———+

| Variable_name | Value |

+——————-+———+

| binlog_cache_size | 2097152 |

| binlog_format | ROW |

+——————-+———+

2 rows in set (0.00 sec)

3、主机上插入一行

root@localhost : alitest 10:43:02> insert into test9 (c1,c2) values (5, “343

434″);

Query OK, 1 row affected (0.00 sec)
4、备机上查看日志:

root@localhost : (none) 10:46:43> show binlog events in ‘mysql-bin.0000

85′ from 362605228;

+——————+———–+————+———–+————-+—————————

————————————+

| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |

+——————+———–+————+———–+————-+—————————

————————————+

| mysql-bin.000085 | 362605228 | Query | 1 | 362605287 | BEGIN |

| mysql-bin.000085 | 362605287 | Table_map | 1 | 362605337 | table_id:

 27 (alitest.test9) |

| mysql-bin.000085 | 362605337 | Write_rows | 1 | 362605378 | table_id:

 27 flags: STMT_END_F |

| mysql-bin.000085 | 362605378 | Xid | 1 | 362605405 | COMMIT /* xid=

23537907 */ |

| mysql-bin.000085 | 362605405 | Query | 1 | 362605464 | BEGIN |

| mysql-bin.000085 | 362605464 | Table_map | 1 | 362605514 | table_id:

 27 (alitest.test9) |

| mysql-bin.000085 | 362605514 | Write_rows | 1 | 362605555 | table_id:

 27 flags: STMT_END_F |

| mysql-bin.000085 | 362605555 | Xid | 1 | 362605582 | COMMIT /* xid=

23537917 */ |
| mysql-bin.000085 | 362605582 | Query | 1 | 362605641 | BEGIN |

| mysql-bin.000085 | 362605641 | Query | 1 | 362605753 | use `alitest`;

insert into test9 (c1,c2) values (4, “343434″) |

| mysql-bin.000085 | 362605753 | Xid | 1 | 362605780 | COMMIT /* xid=

23537922 */ |

| mysql-bin.000085 | 362605780 | Query | 1 | 362605839 | BEGIN |

| mysql-bin.000085 | 362605839 | Query | 1 | 362605951 | use `alitest`;

insert into test9 (c1,c2) values (5, “343434″) |

| mysql-bin.000085 | 362605951 | Xid | 1 | 362605978 | COMMIT /* xid=

23537929 */ |

+——————+———–+————+———–+————-+—————————

————————————+

14 rows in set (0.00 sec)

附上 binlog_format 变量的介绍

–binlog-format={ROW|STATEMENT|MIXED}

Version Introduced 5.1.5

Command-Line Format –binlog-format

Config-File Format binlog-format

Option Sets Variable Yes, binlog_format

Variable Name binlog_format

Variable Scope Both

Dynamic Variable Yes
Permitted Values (>= 5.1.5, <= 5.1.7)

Type enumeration

Default STATEMENT

Valid Values ROW, STATEMENT

Permitted Values (>= 5.1.8, <= 5.1.11)

Type enumeration

Default STATEMENT

Valid Values ROW, STATEMENT, MIXED

Permitted Values (>= 5.1.12, <= 5.1.28)

Type enumeration

Default MIXED

Valid Values ROW, STATEMENT, MIXED

Permitted Values (>= 5.1.29)

Type enumeration

Default STATEMENT

Valid Values ROW, STATEMENT, MIXED

Specify whether to use row-based, statement-based, or mixed replication

(statement-based was the default prior to MySQL 5.1.12; in 5.1.12, the d

efault was changed to mixed replication; in 5.1.29, the default was chan

ged back to statement-based). See Section 16.1.2, “Replication Formats”.

 This option was added in MySQL 5.1.5.

Important
Setting the binary logging format without enabling binary logging prevents

 the MySQL server from starting. This is a known issue in MySQL 5.1

which is fixed in MySQL 5.5. (Bug#42928)

MySQL Cluster. The default value for this option in all MySQL Cluster N

DB 6.1, 6.2, 6.3, and later 6.x releases is MIXED. See Section 17.6.2,

“MySQL Cluster Replication: Assumptions and General Requirements”, for

 more information.

may your success.


eshop MySQL 数据库主备机数据 xtrabackup 同步数据


今天需要再次用到 xtrabackup 来备份和恢复数据。找了半天终于把以前写的一

个文档找到了。保存在 blog 中。以防丢失。xtrabackup 更换为 xtrabackup-1.2

-22.rhel5.x86_64.rpm 也已经测试通过

一.迁移目的

目前 ITBU eshop MySQL 数据库有 64 台数据库服务器。两两互备作为双 mas

ter 结构相互备份。但是由于应用方使用了 MySQL 的 uuid 函数,导致两两互备

的 mysql 服务器之间的数据不一致。这样两个数据库切换将导致用户数据的丢

失。

4 月底,应用方修改了 uuid 的问题,避免的新的数据不一致问题。接下来就只

有数据库中已有的数据不一致问题了。

MySQL 数据库,由于主备之间已经切换过多次,目前一部分数据已经无法找回。
跟应用沟通后,确定以目前主库的数据为准,将主库的数据同步到备库。备库的

数据备份到本机。保存一个月。

二.迁移要求

1、迁移过程中要求不影响应用,也就是说 eshop 数据库服务不能停止。

2、迁移完成后主备机数据保持一致。

三.迁移方案

这里由于管理维护的必要,我们假设主备机我们都是以 root 登录。xtrabackup

在 MySQL 中的权限为:

root@127.0.0.1 : (none) 20:57:24> show grants for ‘xtrabackup’@'localhos

t’G

*************************** 1. row ***************************

Grants for xtrabackup@localhost: GRANT SELECT, RELOAD, LOCK TAB

LES, REPLICATION CLIENT ON *.* TO ‘xtrabackup’@'localhost’ IDENTIF

IED BY PASSWORD ‘*BF9F4C1B8BC37C75BBF482C8037EC944555D371

A’

*************************** 2. row ***************************

Grants for xtrabackup@localhost: GRANT INSERT, CREATE, DROP ON

`mysql`.`ibbackup_binlog_marker` TO ‘xtrabackup’@'localhost’

2 rows in set (0.00 sec)

3.1.备份/恢复工具选择

由于迁移过程中不能够停止数据库服务,数据库备份必须选择热备份。两种选择
mysqldump 的逻辑热备以及 xtrabackup 的物理热备。为了恢复时间考虑,决定

采用 xtrabackup。

3.2.备机备份数据的选择

eshop 网店目前有两个存储,他们目前都在老聚园路。一个存储暂时没有上电,

另外一个存储空间所剩不多。目前每台 eshop 备机平均为 130G 的数据,一共 3

2 台,需要 4T 多的存储空间。而如果把 eshop 的 32 套备机数据放到存储上,

对存储空间和网络的消耗都是巨大的。

最终,我们选择在备机存储本机的老数据。而不做另外拷贝。

附件 eshop_backup_slave_data_20100422.sh 是备份备机数据的脚本

3.3.主备机数据拷贝方案选择

主机备份生成的数据大小约为 130G 左右,正常的话我们可以通过 scp 来拷贝,

但是 scp 拷贝要么需要手工输入密码,要么需要打通 ssh 隧道。如果用脚本来

操作的话,只能打通主备机之间的 ssh 隧道。另外 scp 不能够限速。数据拷贝

量大的话可能引起网络堵塞。最后,考虑采用 rsync 拷贝数据,它不仅能够限速。

并且可以在主机上启动 rsync –daemon,不用打通隧道(需要注意在拷贝完数据

以后停止 rsync 后台进程)

附件 eshop_master_rsync_daemon_local.sh 和 eshop_master_rsync_daemon_

remote.sh 是用于在 eshop 的 master 机器上添加 rsync daemon 的脚本。rsync

d.conf 是拷贝到主机的 rsync 配置文件。

3.4.主机数据库备份方案

eshop 主备机每天 5: 的时候都会有一个计划任务判断当前主机是否是备机。
             30

如果是备机则利用 xtrabackup innobackupex-1.5.1 版本为 0.7)
                  (                            生成物理备份。
这里我们只需要简单修改一下备份脚本就可以在主机上也生成备份(注意主机备

机完之后需要把该脚本修改回来)。直接利用

innobackupex-1.5.1 –slave-info –no-timestamp –user=”${XTRA_BACKUP_U

SER}” —password=”${XTRA_BACKUP_PASSWORD}” ${XTRA_BACKUP_D

IR}

生成备份。这里需要注意 xtrabackup 用户的权限。

附件 eshop_master_backup_local.sh 是用于主机 xtrabackup 备份数据库的脚本。

backup_mysql.sh 是需要拷贝到主机上执行的脚本

3.5.备机数据恢复方案

主机数据拷贝到备机以后,由于是 xtrabackup 备份出来的,所以也要用它来恢

复。注意,在恢复备机数据库数据前需要将备机数据库 MySQL 先停掉。原有的

MySQL 数据目录需要 mv 到另外的位置。并根据需要新建好数据库需要的目录,

为备份数据拷贝到对应目录准备好环境。

xtrabackup 需要先生成 logfile,然后将产生的相关数据拷贝到具体的数据文件目

录中。需要需要执行两次。

■第一次利用:

innobackupex-1.5.1 —apply-log /home/mysql/fs3/master_bak_data/eshop_al

smy_eshop13b_2010_04_27/

应用备份期间变化的数据和生成 ib_logfile。这里需要注意将该命令输出的结果

记下来。后面主备机双 maser 复制环境的搭建需要利用到这里的”Last MySQL

binlog file position”信息

■第二次利用:
innobackupex-1.5.1 —copy-back /home/mysql/fs3/master_bak_data/eshop_a

lsmy_eshop13b_2010_04_27/

将生成好的数据拷贝到/etc/my.cnf 指定的 datadir 和 innodb_data_home_dir 等

对应目录中。

3.6.备机数据库启动和清理

备机数据库数据恢复以后,需要将数据库的目录 owner 修改一下。chown -R

mysql:mysql ${MYSQL_DATA_DIR}。否则 MySQL 启动会出错。

启动 MySQL 数据库,正常的话启动是没有问题的。如果出现问题,需要查看 M

ySQL 的错误日志,并根据不同的错误类型处理。

启动成功以后,MySQL 数据库里面的数据都是主机的数据。我们需要根据不同

的情况对它进行修改。(这里最好 SET SQL_LOG_BIN=0 避免把一些数据记录

到 binlog 中,从而复制到主机去了。)eshop 这边因为复制帐号是 replicator@’${S

LAVE}’基于备机 ip 地址的,所以需要修改为基于主机的复制帐号。这样备机到

主机的复制才能搭建起来。

root@127.0.0.1 : (none) 16:13:06> show grants for replicator@’172.18.94.

25′;

+——————————————————————————————————

———————————————————————–+

| Grants for replicator@172.18.94.25 |

+——————————————————————————————————

———————————————————————–+

| GRANT SELECT, RELOAD, SUPER, REPLICATION SLAVE, REPLICAT
ION CLIENT ON *.* TO ‘replicator’@’172.18.94.25′ IDENTIFIED BY PAS

SWORD ‘*3836CCEA58805DB4CE7093BF6170F7A6027CDD86′ |

+——————————————————————————————————

———————————————————————–+

1 row in set (0.00 sec)

root@127.0.0.1 : (none) 16:14:20> GRANT SELECT, RELOAD, SUPER,

REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO ‘replicator’@’1

72.18.94.26′ IDENTIFIED BY ‘xlia9810pal’;

Query OK, 0 rows affected (0.00 sec)

root@127.0.0.1 : (none) 21:21:02> drop user replicator@’172.18.94.25′;

Query OK, 0 rows affected (0.03 sec)

root@127.0.0.1 : (none) 16:14:44> flush privileges;

Query OK, 0 rows affected (0.00 sec)

附件 eshop_slave_sync_master_data.sh 是用来恢复备机数据库和清理数据的

脚本。

3.7.主备机双 master 复制环境搭建

3.7.1.搭建备机到主机的复制

由于备机是从主机导入的数据,并且备机没有应用访问,binlog 没有变化。我们

首先搭建备机到主机的复制。首先确定备机的 binlog 位置。利用 show master

status 可以查到。

root@127.0.0.1 : (none) 21:37:38> show master status;

+——————+———–+————–+——————+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |

+——————+———–+————–+——————+

| mysql-bin.000001 | 98 | | |

+——————+———–+————–+——————+

1 row in set (0.00 sec)

如无意外,都应该是第一个文件的最开始位置。

修改主机的 master 位置:

Stop slave;

CHANGE MASTER TO MASTER_HOST=’172.18.94.25′, MASTER_USER=’

replicator’, MASTER_PASSWORD=’xlia9810pal’, MASTER_LOG_FILE=’mys

ql-bin.000001′, MASTER_LOG_POS=98;

Start slave;

检查是否复制搭建成功:

Show slave statusG

如果 Slave_IO_Running,Slave_SQL_Running 其中一个为 No。需要检查 MyS

QL 的错误日志。一般可能有两种错误:

■备机的授权有问题

■备机的 binlog 位置有问题

附件 eshop_master_change_master.sh 为配置备机到主机的复制环境的脚本。

3.7.2.搭建主机到备机的复制

保证备机到主机的复制正确以后,我们就可以搭建主机到备机的复制了。

备机从主机的哪个位置开始复制很重要,这个位置是从 备机数据恢复方案 中得
到的。在 apply-log 的时候,有一行文字很重要:

InnoDB: Last MySQL binlog file position 0 651339984, file name ./mysql-

bin.001604

这里记录了主机的复制开始文件和开始位置(这里就是 mysql-bin.001604,6513

39984)。那么我们搭建主机到备机的复制就很简单了:

Stop slave;

CHANGE MASTER TO MASTER_HOST=’172.18.94.26′, MASTER_USER=’

replicator’, MASTER_PASSWORD=’xlia9810pal’, MASTER_LOG_FILE=’mys

ql-bin.001604′, MASTER_LOG_POS=651339984;

Start slave;

检查是否复制搭建成功:

Show slave statusG

如果 Slave_IO_Running,Slave_SQL_Running 其中一个为 No。需要检查 MyS

QL 的错误日志。

这里由于备份的时间已经经过了这么长的时间,主机到备机的复制肯定有延迟,

静静的等待主备机的复制就可以了。

附件 eshop_slave_change_master.sh 为配置主机到备机的复制环境的脚本。

四.附件介绍

附件中的各个脚本介绍如下:

■附件 eshop_backup_slave_data_20100422.sh 是备份备机数据的脚本

■附件 eshop_master_backup_local.sh 是用于主机 xtrabackup 备份数据库的脚

本。backup_mysql.sh 是需要拷贝到主机上执行的脚本
■附件 eshop_master_rsync_daemon_local.sh 和 eshop_master_rsync_daemon

_remote.sh 是用于在 eshop 的 master 机器上添加 rsync daemon 的脚本。rsy

ncd.conf 是拷贝到主机的 rsync 配置文件。

■附件 eshop_slave_sync_master_data.sh 是用来恢复备机数据库和清理数据的

脚本。

■附件 eshop_master_change_master.sh 为配置备机到主机的复制环境的脚本。

■附件 eshop_slave_change_master.sh 为配置主机到备机的复制环境的脚本。

may you success.


MySQL 数据库机器搬迁 checklist


itbu 的数据库机器从老机房搬迁到新机房的项目已经接近尾声了,这次的搬迁是

我经历的一个比较大的项目,虽然整体的调控不是我主导的,但是 MySQL 数据

库的搬迁都是我在主导进行,期间出现了一些问题,还好没有出现非常重大的错

误。从中相信搬迁的大部分同学都成长了很多。我自己感觉我学到了很多,也成

长了很多,虽然加班加点的,累的够呛,还是很值得的。为了避免以后大家再走

弯路,也避免自己忘记搬迁的重要事项,特别记录下来,做为一个搬迁工程的 c

hecklist。方便大家搬迁过程中检查,不要遗漏了一些东西。

1、确认方案。尽早和应用方沟通,确认采用平滑搬迁或者停机搬迁的方案。

平滑搬迁是指 MySQL 数据库停止备机,然后搬迁过去,在新环境搭好以后,配

置 MySQL 的双 master 复制,调试通过,应用服务正常。然后直接刷 dns(或者

其他方式)使应用的真实用户访问新的服务。

停机搬迁的方式是指,应用发布停机公告,在适当的时机,将应用服务器和数据
库服务器等搬迁到新机房,部署上线。

上面的两种方案,第一种风险较小,对用户影响也非常小。万一刷过去以后,真

实用户访问有问题,还可以切回来。第二种风险比较大,甚至可能出现有些配置

修改修改不及时,造成停机时间已经过了,应用无法访问的问题。

2、确认机架位置。正常的话,sa 负责人会将搬迁的机器列表和机柜,机架位置

发给大家一份。以方便现场工程师以及对应人员确认搬迁的就是这批机器。我们

需要注意一下,并提醒现场工程师机柜和机架号。eshop 搬迁的时候,我们的一

个现场工程师就看错了机柜号,拔掉了一台正在提供应用服务的机器,还好立刻

意识到了,大家一起快速修复了这个问题。

3、确认搬迁的机器 IP 和搬迁到新机房的机器 IP。老机房需要搬迁的机器 IP 在

上一步就需要确认好了,新机房的机器 IP 需要跟 sa 和网络部门的同事沟通,我

们提供具体的分配策略,网段划分,新老机器 IP 对应关系。

4、确认时间。及早跟各应用方沟通搬迁时间,如果有变化,及时相互通知。

5、搬迁前准备工作。eshop 这边搬迁前需要重新同步主机数据到备机。其他应

用需要注意主备机的复制延迟,尽量保证复制延迟较低。

6、通知所有相关人员。包括数据仓库,应用开发人员,网络,sa。告知他们新

的数据库 IP 地址和连接方式。另外还需要检查 MySQL 里面的所有账户,看这

些用户是否都通知到了。pm 就没有及时通知数据仓库人员,导致第二天他们取

不到数据的问题。

7、修改搬迁以后的配置。包括 MySQL 账户的修改,heartbeat 的修改,cobar

的修改,脚本的修改,脚本的配置文件修改,带外登录等。

a)MySQL 帐号的修改。检查 MySQL 的账户,看是否还有依赖于老的 IP 地址段
的账户。比如:eshop 的复制帐号 replicator@172.18.%搬迁到新的机房就肯定

需要修改掉。

b)heartbeat 的修改;cobar 的修改。机器搬迁到新的机房,需要修改 heartbeat

和 cobar 的配置。这样才能让应用访问新的 VIP 地址。

c)脚本的修改。目前 MySQL 的管理有许多脚本,这些脚本有些是对 IP 有依赖

关系的,需要及时修改掉。当然,最好修改掉这样的脚本,去掉对 IP 的依赖。

d)脚本配置文件的修改。有些脚本通过读取配置文件来依赖 IP,同样需要修改配

置文件。如果可能的话,修改脚本,去掉对 IP 的依赖。

e)带外登录。带外登录地址随着机器 IP 的更换也会变化。搬迁过去以后,需要

再次验证一下带外登录的方式是否还正常。

该 checklist 可能还不是很完全,需要补充和优化。

may you success.

Weitere ähnliche Inhalte

Andere mochten auch

Full document
Full documentFull document
Full documentSUNELU
 
Cake pop how to
Cake pop how toCake pop how to
Cake pop how tokari704
 
Kklassen classpresentation
Kklassen classpresentationKklassen classpresentation
Kklassen classpresentationKim Jackson
 
Kklassen classpresentation
Kklassen classpresentationKklassen classpresentation
Kklassen classpresentationKim Jackson
 
Curricula brevi lista indipendenti di centro 2014 ottimale
Curricula brevi lista indipendenti di centro  2014   ottimaleCurricula brevi lista indipendenti di centro  2014   ottimale
Curricula brevi lista indipendenti di centro 2014 ottimaleRoberto Andreussi
 
Voglio Scorrere Libera-Presentazione
Voglio Scorrere Libera-PresentazioneVoglio Scorrere Libera-Presentazione
Voglio Scorrere Libera-PresentazioneRoberto Andreussi
 

Andere mochten auch (11)

Lecture five
Lecture fiveLecture five
Lecture five
 
Full document
Full documentFull document
Full document
 
Lecture three
Lecture threeLecture three
Lecture three
 
Cake pop how to
Cake pop how toCake pop how to
Cake pop how to
 
Kklassen classpresentation
Kklassen classpresentationKklassen classpresentation
Kklassen classpresentation
 
Kklassen classpresentation
Kklassen classpresentationKklassen classpresentation
Kklassen classpresentation
 
Curricula brevi lista indipendenti di centro 2014 ottimale
Curricula brevi lista indipendenti di centro  2014   ottimaleCurricula brevi lista indipendenti di centro  2014   ottimale
Curricula brevi lista indipendenti di centro 2014 ottimale
 
Russian idioms
Russian idiomsRussian idioms
Russian idioms
 
Voglio Scorrere Libera-Presentazione
Voglio Scorrere Libera-PresentazioneVoglio Scorrere Libera-Presentazione
Voglio Scorrere Libera-Presentazione
 
Tomosintesi Mammella
Tomosintesi MammellaTomosintesi Mammella
Tomosintesi Mammella
 
Training Presentation Time Management
Training Presentation Time ManagementTraining Presentation Time Management
Training Presentation Time Management
 

Ähnlich wie 主备备的两个备机转为双Master出现诡异的slave lag问题

Mysql展示功能与源码对应
Mysql展示功能与源码对应Mysql展示功能与源码对应
Mysql展示功能与源码对应zhaolinjnu
 
Mysql 101014202926-phpapp01
Mysql 101014202926-phpapp01Mysql 101014202926-phpapp01
Mysql 101014202926-phpapp01Bob Huang
 
My sql管理基础 李春_v2
My sql管理基础 李春_v2My sql管理基础 李春_v2
My sql管理基础 李春_v2Pickup Li
 
丁原:海量数据迁移方案
丁原:海量数据迁移方案丁原:海量数据迁移方案
丁原:海量数据迁移方案YANGL *
 
mysql总结
mysql总结mysql总结
mysql总结haiwang
 
Mysql mmm安装指南(翻译)
Mysql mmm安装指南(翻译)Mysql mmm安装指南(翻译)
Mysql mmm安装指南(翻译)Yiwei Ma
 
安博士Asec 2010年3月安全报告
安博士Asec 2010年3月安全报告安博士Asec 2010年3月安全报告
安博士Asec 2010年3月安全报告ahnlabchina
 
MySQL源码分析.01.代码结构与基本流程
MySQL源码分析.01.代码结构与基本流程MySQL源码分析.01.代码结构与基本流程
MySQL源码分析.01.代码结构与基本流程Lixun Peng
 
腾讯大讲堂24 qq show2.0重构历程
腾讯大讲堂24 qq show2.0重构历程腾讯大讲堂24 qq show2.0重构历程
腾讯大讲堂24 qq show2.0重构历程areyouok
 
腾讯大讲堂24 qq show2.0重构历程
腾讯大讲堂24 qq show2.0重构历程腾讯大讲堂24 qq show2.0重构历程
腾讯大讲堂24 qq show2.0重构历程topgeek
 
Mysql proxy+mysql-mmm
Mysql proxy+mysql-mmmMysql proxy+mysql-mmm
Mysql proxy+mysql-mmmYiwei Ma
 
Static server介绍
Static server介绍Static server介绍
Static server介绍sun jamie
 
MySQL数据库生产环境维护
MySQL数据库生产环境维护MySQL数据库生产环境维护
MySQL数据库生产环境维护mysqlops
 
lwdba – 開放原始碼的輕量級資料庫存取程式庫
lwdba – 開放原始碼的輕量級資料庫存取程式庫lwdba – 開放原始碼的輕量級資料庫存取程式庫
lwdba – 開放原始碼的輕量級資料庫存取程式庫建興 王
 
C++工程实践
C++工程实践C++工程实践
C++工程实践Shuo Chen
 
开源应用日志收集系统
开源应用日志收集系统开源应用日志收集系统
开源应用日志收集系统klandor
 
8, bes tables & api
8, bes tables & api8, bes tables & api
8, bes tables & apited-xu
 

Ähnlich wie 主备备的两个备机转为双Master出现诡异的slave lag问题 (20)

Mysql展示功能与源码对应
Mysql展示功能与源码对应Mysql展示功能与源码对应
Mysql展示功能与源码对应
 
Mysql 101014202926-phpapp01
Mysql 101014202926-phpapp01Mysql 101014202926-phpapp01
Mysql 101014202926-phpapp01
 
Ch07
Ch07Ch07
Ch07
 
My sql管理基础 李春_v2
My sql管理基础 李春_v2My sql管理基础 李春_v2
My sql管理基础 李春_v2
 
丁原:海量数据迁移方案
丁原:海量数据迁移方案丁原:海量数据迁移方案
丁原:海量数据迁移方案
 
mysql总结
mysql总结mysql总结
mysql总结
 
系統程式 -- 第 5 章
系統程式 -- 第 5 章系統程式 -- 第 5 章
系統程式 -- 第 5 章
 
Mysql mmm安装指南(翻译)
Mysql mmm安装指南(翻译)Mysql mmm安装指南(翻译)
Mysql mmm安装指南(翻译)
 
安博士Asec 2010年3月安全报告
安博士Asec 2010年3月安全报告安博士Asec 2010年3月安全报告
安博士Asec 2010年3月安全报告
 
MySQL源码分析.01.代码结构与基本流程
MySQL源码分析.01.代码结构与基本流程MySQL源码分析.01.代码结构与基本流程
MySQL源码分析.01.代码结构与基本流程
 
腾讯大讲堂24 qq show2.0重构历程
腾讯大讲堂24 qq show2.0重构历程腾讯大讲堂24 qq show2.0重构历程
腾讯大讲堂24 qq show2.0重构历程
 
腾讯大讲堂24 qq show2.0重构历程
腾讯大讲堂24 qq show2.0重构历程腾讯大讲堂24 qq show2.0重构历程
腾讯大讲堂24 qq show2.0重构历程
 
Mysql proxy+mysql-mmm
Mysql proxy+mysql-mmmMysql proxy+mysql-mmm
Mysql proxy+mysql-mmm
 
Exodus2 大局观
Exodus2 大局观Exodus2 大局观
Exodus2 大局观
 
Static server介绍
Static server介绍Static server介绍
Static server介绍
 
MySQL数据库生产环境维护
MySQL数据库生产环境维护MySQL数据库生产环境维护
MySQL数据库生产环境维护
 
lwdba – 開放原始碼的輕量級資料庫存取程式庫
lwdba – 開放原始碼的輕量級資料庫存取程式庫lwdba – 開放原始碼的輕量級資料庫存取程式庫
lwdba – 開放原始碼的輕量級資料庫存取程式庫
 
C++工程实践
C++工程实践C++工程实践
C++工程实践
 
开源应用日志收集系统
开源应用日志收集系统开源应用日志收集系统
开源应用日志收集系统
 
8, bes tables & api
8, bes tables & api8, bes tables & api
8, bes tables & api
 

Kürzlich hochgeladen

1.💥黑客接单,挑战你的想象力! 🚀💡从最炫酷的黑科技到神秘莫测的代码世界,这里都是你想要的技术。无论是破解密码、入侵系统还是开发软件,我们都能帮你实现!...
1.💥黑客接单,挑战你的想象力! 🚀💡从最炫酷的黑科技到神秘莫测的代码世界,这里都是你想要的技术。无论是破解密码、入侵系统还是开发软件,我们都能帮你实现!...1.💥黑客接单,挑战你的想象力! 🚀💡从最炫酷的黑科技到神秘莫测的代码世界,这里都是你想要的技术。无论是破解密码、入侵系统还是开发软件,我们都能帮你实现!...
1.💥黑客接单,挑战你的想象力! 🚀💡从最炫酷的黑科技到神秘莫测的代码世界,这里都是你想要的技术。无论是破解密码、入侵系统还是开发软件,我们都能帮你实现!...黑客 接单【TG/微信qoqoqdqd】
 
我曾试图入侵正方教务系统,但我发现它有一些漏洞找黑客入侵电脑,找黑客入侵服务器,找黑客破解密码,怎么找黑客?【微 tytyqqww 信】
我曾试图入侵正方教务系统,但我发现它有一些漏洞找黑客入侵电脑,找黑客入侵服务器,找黑客破解密码,怎么找黑客?【微 tytyqqww 信】我曾试图入侵正方教务系统,但我发现它有一些漏洞找黑客入侵电脑,找黑客入侵服务器,找黑客破解密码,怎么找黑客?【微 tytyqqww 信】
我曾试图入侵正方教务系统,但我发现它有一些漏洞找黑客入侵电脑,找黑客入侵服务器,找黑客破解密码,怎么找黑客?【微 tytyqqww 信】黑客 接单【TG/微信qoqoqdqd】
 
【国外大学文凭样本】多大毕业证认证Q/微:892798920办多伦多大学毕业证留信留服使馆公公证,多大硕士毕业证,U of T研究生毕业证,文凭,改U o...
【国外大学文凭样本】多大毕业证认证Q/微:892798920办多伦多大学毕业证留信留服使馆公公证,多大硕士毕业证,U of T研究生毕业证,文凭,改U o...【国外大学文凭样本】多大毕业证认证Q/微:892798920办多伦多大学毕业证留信留服使馆公公证,多大硕士毕业证,U of T研究生毕业证,文凭,改U o...
【国外大学文凭样本】多大毕业证认证Q/微:892798920办多伦多大学毕业证留信留服使馆公公证,多大硕士毕业证,U of T研究生毕业证,文凭,改U o...ggbob1
 
30T.ppt【国外大学文凭样本】TWU毕业证认证Q/微:892798920办西三一大学毕业证留信留服使馆公证,TWU硕士毕业证,TWU研究生毕业证,文凭...
30T.ppt【国外大学文凭样本】TWU毕业证认证Q/微:892798920办西三一大学毕业证留信留服使馆公证,TWU硕士毕业证,TWU研究生毕业证,文凭...30T.ppt【国外大学文凭样本】TWU毕业证认证Q/微:892798920办西三一大学毕业证留信留服使馆公证,TWU硕士毕业证,TWU研究生毕业证,文凭...
30T.ppt【国外大学文凭样本】TWU毕业证认证Q/微:892798920办西三一大学毕业证留信留服使馆公证,TWU硕士毕业证,TWU研究生毕业证,文凭...ggbob1
 
003 DSKP KSSR SEMAKAN 2017 BAHASA CINA TAHUN 3.pdf
003 DSKP KSSR SEMAKAN 2017 BAHASA CINA TAHUN 3.pdf003 DSKP KSSR SEMAKAN 2017 BAHASA CINA TAHUN 3.pdf
003 DSKP KSSR SEMAKAN 2017 BAHASA CINA TAHUN 3.pdfshanshanhui1
 
加急代办一个日本鹿儿岛纯心女子大学学位记🌈学习成绩单电子版定制🌈仿制荷兰大学毕业证🌈日语JLPT证书定制
加急代办一个日本鹿儿岛纯心女子大学学位记🌈学习成绩单电子版定制🌈仿制荷兰大学毕业证🌈日语JLPT证书定制加急代办一个日本鹿儿岛纯心女子大学学位记🌈学习成绩单电子版定制🌈仿制荷兰大学毕业证🌈日语JLPT证书定制
加急代办一个日本鹿儿岛纯心女子大学学位记🌈学习成绩单电子版定制🌈仿制荷兰大学毕业证🌈日语JLPT证书定制bairnshajjes
 
正方教务系统的小漏洞被黑客找到啦~他现在正在偷偷溜进去玩呢!(*^__^*)法国大学挂科改成绩 德国大学挂科改成绩 韩国大学挂科改成绩大学成绩修改,找黑客...
正方教务系统的小漏洞被黑客找到啦~他现在正在偷偷溜进去玩呢!(*^__^*)法国大学挂科改成绩 德国大学挂科改成绩 韩国大学挂科改成绩大学成绩修改,找黑客...正方教务系统的小漏洞被黑客找到啦~他现在正在偷偷溜进去玩呢!(*^__^*)法国大学挂科改成绩 德国大学挂科改成绩 韩国大学挂科改成绩大学成绩修改,找黑客...
正方教务系统的小漏洞被黑客找到啦~他现在正在偷偷溜进去玩呢!(*^__^*)法国大学挂科改成绩 德国大学挂科改成绩 韩国大学挂科改成绩大学成绩修改,找黑客...黑客 接单【TG/微信qoqoqdqd】
 
1.🔥承接黑客破解任务,你的难题我们来解决! 💡无论你是游戏玩家、企业用户还是个人用户,都能在这里找到满意的解决方案。 💪经验丰富的专业团队为您提供全方位...
1.🔥承接黑客破解任务,你的难题我们来解决! 💡无论你是游戏玩家、企业用户还是个人用户,都能在这里找到满意的解决方案。 💪经验丰富的专业团队为您提供全方位...1.🔥承接黑客破解任务,你的难题我们来解决! 💡无论你是游戏玩家、企业用户还是个人用户,都能在这里找到满意的解决方案。 💪经验丰富的专业团队为您提供全方位...
1.🔥承接黑客破解任务,你的难题我们来解决! 💡无论你是游戏玩家、企业用户还是个人用户,都能在这里找到满意的解决方案。 💪经验丰富的专业团队为您提供全方位...黑客 接单【TG/微信qoqoqdqd】
 
【創業簡報練習】當一個人吃飯會想起誰: (A)I-DOLL 陪吃娃娃|科技創業與營運實務
【創業簡報練習】當一個人吃飯會想起誰:(A)I-DOLL 陪吃娃娃|科技創業與營運實務【創業簡報練習】當一個人吃飯會想起誰:(A)I-DOLL 陪吃娃娃|科技創業與營運實務
【創業簡報練習】當一個人吃飯會想起誰: (A)I-DOLL 陪吃娃娃|科技創業與營運實務sardinesaying
 
🎉一键更改成绩单,轻松点亮你的未来! 💡[书]想知道自己成绩怎么样?别担心!我们来帮您解答疑惑。 在这里,只需轻轻一点按钮,就能立即查看到分数、排名和其他...
🎉一键更改成绩单,轻松点亮你的未来! 💡[书]想知道自己成绩怎么样?别担心!我们来帮您解答疑惑。 在这里,只需轻轻一点按钮,就能立即查看到分数、排名和其他...🎉一键更改成绩单,轻松点亮你的未来! 💡[书]想知道自己成绩怎么样?别担心!我们来帮您解答疑惑。 在这里,只需轻轻一点按钮,就能立即查看到分数、排名和其他...
🎉一键更改成绩单,轻松点亮你的未来! 💡[书]想知道自己成绩怎么样?别担心!我们来帮您解答疑惑。 在这里,只需轻轻一点按钮,就能立即查看到分数、排名和其他...黑客 接单【TG/微信qoqoqdqd】
 
Grade 6 Lesson 7 Environment Protection.pptx
Grade 6 Lesson 7 Environment Protection.pptxGrade 6 Lesson 7 Environment Protection.pptx
Grade 6 Lesson 7 Environment Protection.pptxPriscilleXu
 
我了解到黑客在某些领域拥有卓越的技术能力,特别是在处理系统漏洞方面。在当前的情境下,如果我想要改变我的毕业成绩,他们的帮助或许是我唯一可行的选择。【微 t...
我了解到黑客在某些领域拥有卓越的技术能力,特别是在处理系统漏洞方面。在当前的情境下,如果我想要改变我的毕业成绩,他们的帮助或许是我唯一可行的选择。【微 t...我了解到黑客在某些领域拥有卓越的技术能力,特别是在处理系统漏洞方面。在当前的情境下,如果我想要改变我的毕业成绩,他们的帮助或许是我唯一可行的选择。【微 t...
我了解到黑客在某些领域拥有卓越的技术能力,特别是在处理系统漏洞方面。在当前的情境下,如果我想要改变我的毕业成绩,他们的帮助或许是我唯一可行的选择。【微 t...黑客 接单【TG/微信qoqoqdqd】
 
保分服务在SAT考试作弊问题上的应对策略和措施是否充分,如何确保服务的可靠性??
保分服务在SAT考试作弊问题上的应对策略和措施是否充分,如何确保服务的可靠性??保分服务在SAT考试作弊问题上的应对策略和措施是否充分,如何确保服务的可靠性??
保分服务在SAT考试作弊问题上的应对策略和措施是否充分,如何确保服务的可靠性??testhelper Sobrenome
 
未毕业在线购买日本熊本县立大学学位记🏆学习成绩单电子版定制🏆克隆爱尔兰大学文凭🏆CFA证书定制
未毕业在线购买日本熊本县立大学学位记🏆学习成绩单电子版定制🏆克隆爱尔兰大学文凭🏆CFA证书定制未毕业在线购买日本熊本县立大学学位记🏆学习成绩单电子版定制🏆克隆爱尔兰大学文凭🏆CFA证书定制
未毕业在线购买日本熊本县立大学学位记🏆学习成绩单电子版定制🏆克隆爱尔兰大学文凭🏆CFA证书定制gravestomas0
 

Kürzlich hochgeladen (14)

1.💥黑客接单,挑战你的想象力! 🚀💡从最炫酷的黑科技到神秘莫测的代码世界,这里都是你想要的技术。无论是破解密码、入侵系统还是开发软件,我们都能帮你实现!...
1.💥黑客接单,挑战你的想象力! 🚀💡从最炫酷的黑科技到神秘莫测的代码世界,这里都是你想要的技术。无论是破解密码、入侵系统还是开发软件,我们都能帮你实现!...1.💥黑客接单,挑战你的想象力! 🚀💡从最炫酷的黑科技到神秘莫测的代码世界,这里都是你想要的技术。无论是破解密码、入侵系统还是开发软件,我们都能帮你实现!...
1.💥黑客接单,挑战你的想象力! 🚀💡从最炫酷的黑科技到神秘莫测的代码世界,这里都是你想要的技术。无论是破解密码、入侵系统还是开发软件,我们都能帮你实现!...
 
我曾试图入侵正方教务系统,但我发现它有一些漏洞找黑客入侵电脑,找黑客入侵服务器,找黑客破解密码,怎么找黑客?【微 tytyqqww 信】
我曾试图入侵正方教务系统,但我发现它有一些漏洞找黑客入侵电脑,找黑客入侵服务器,找黑客破解密码,怎么找黑客?【微 tytyqqww 信】我曾试图入侵正方教务系统,但我发现它有一些漏洞找黑客入侵电脑,找黑客入侵服务器,找黑客破解密码,怎么找黑客?【微 tytyqqww 信】
我曾试图入侵正方教务系统,但我发现它有一些漏洞找黑客入侵电脑,找黑客入侵服务器,找黑客破解密码,怎么找黑客?【微 tytyqqww 信】
 
【国外大学文凭样本】多大毕业证认证Q/微:892798920办多伦多大学毕业证留信留服使馆公公证,多大硕士毕业证,U of T研究生毕业证,文凭,改U o...
【国外大学文凭样本】多大毕业证认证Q/微:892798920办多伦多大学毕业证留信留服使馆公公证,多大硕士毕业证,U of T研究生毕业证,文凭,改U o...【国外大学文凭样本】多大毕业证认证Q/微:892798920办多伦多大学毕业证留信留服使馆公公证,多大硕士毕业证,U of T研究生毕业证,文凭,改U o...
【国外大学文凭样本】多大毕业证认证Q/微:892798920办多伦多大学毕业证留信留服使馆公公证,多大硕士毕业证,U of T研究生毕业证,文凭,改U o...
 
30T.ppt【国外大学文凭样本】TWU毕业证认证Q/微:892798920办西三一大学毕业证留信留服使馆公证,TWU硕士毕业证,TWU研究生毕业证,文凭...
30T.ppt【国外大学文凭样本】TWU毕业证认证Q/微:892798920办西三一大学毕业证留信留服使馆公证,TWU硕士毕业证,TWU研究生毕业证,文凭...30T.ppt【国外大学文凭样本】TWU毕业证认证Q/微:892798920办西三一大学毕业证留信留服使馆公证,TWU硕士毕业证,TWU研究生毕业证,文凭...
30T.ppt【国外大学文凭样本】TWU毕业证认证Q/微:892798920办西三一大学毕业证留信留服使馆公证,TWU硕士毕业证,TWU研究生毕业证,文凭...
 
003 DSKP KSSR SEMAKAN 2017 BAHASA CINA TAHUN 3.pdf
003 DSKP KSSR SEMAKAN 2017 BAHASA CINA TAHUN 3.pdf003 DSKP KSSR SEMAKAN 2017 BAHASA CINA TAHUN 3.pdf
003 DSKP KSSR SEMAKAN 2017 BAHASA CINA TAHUN 3.pdf
 
加急代办一个日本鹿儿岛纯心女子大学学位记🌈学习成绩单电子版定制🌈仿制荷兰大学毕业证🌈日语JLPT证书定制
加急代办一个日本鹿儿岛纯心女子大学学位记🌈学习成绩单电子版定制🌈仿制荷兰大学毕业证🌈日语JLPT证书定制加急代办一个日本鹿儿岛纯心女子大学学位记🌈学习成绩单电子版定制🌈仿制荷兰大学毕业证🌈日语JLPT证书定制
加急代办一个日本鹿儿岛纯心女子大学学位记🌈学习成绩单电子版定制🌈仿制荷兰大学毕业证🌈日语JLPT证书定制
 
正方教务系统的小漏洞被黑客找到啦~他现在正在偷偷溜进去玩呢!(*^__^*)法国大学挂科改成绩 德国大学挂科改成绩 韩国大学挂科改成绩大学成绩修改,找黑客...
正方教务系统的小漏洞被黑客找到啦~他现在正在偷偷溜进去玩呢!(*^__^*)法国大学挂科改成绩 德国大学挂科改成绩 韩国大学挂科改成绩大学成绩修改,找黑客...正方教务系统的小漏洞被黑客找到啦~他现在正在偷偷溜进去玩呢!(*^__^*)法国大学挂科改成绩 德国大学挂科改成绩 韩国大学挂科改成绩大学成绩修改,找黑客...
正方教务系统的小漏洞被黑客找到啦~他现在正在偷偷溜进去玩呢!(*^__^*)法国大学挂科改成绩 德国大学挂科改成绩 韩国大学挂科改成绩大学成绩修改,找黑客...
 
1.🔥承接黑客破解任务,你的难题我们来解决! 💡无论你是游戏玩家、企业用户还是个人用户,都能在这里找到满意的解决方案。 💪经验丰富的专业团队为您提供全方位...
1.🔥承接黑客破解任务,你的难题我们来解决! 💡无论你是游戏玩家、企业用户还是个人用户,都能在这里找到满意的解决方案。 💪经验丰富的专业团队为您提供全方位...1.🔥承接黑客破解任务,你的难题我们来解决! 💡无论你是游戏玩家、企业用户还是个人用户,都能在这里找到满意的解决方案。 💪经验丰富的专业团队为您提供全方位...
1.🔥承接黑客破解任务,你的难题我们来解决! 💡无论你是游戏玩家、企业用户还是个人用户,都能在这里找到满意的解决方案。 💪经验丰富的专业团队为您提供全方位...
 
【創業簡報練習】當一個人吃飯會想起誰: (A)I-DOLL 陪吃娃娃|科技創業與營運實務
【創業簡報練習】當一個人吃飯會想起誰:(A)I-DOLL 陪吃娃娃|科技創業與營運實務【創業簡報練習】當一個人吃飯會想起誰:(A)I-DOLL 陪吃娃娃|科技創業與營運實務
【創業簡報練習】當一個人吃飯會想起誰: (A)I-DOLL 陪吃娃娃|科技創業與營運實務
 
🎉一键更改成绩单,轻松点亮你的未来! 💡[书]想知道自己成绩怎么样?别担心!我们来帮您解答疑惑。 在这里,只需轻轻一点按钮,就能立即查看到分数、排名和其他...
🎉一键更改成绩单,轻松点亮你的未来! 💡[书]想知道自己成绩怎么样?别担心!我们来帮您解答疑惑。 在这里,只需轻轻一点按钮,就能立即查看到分数、排名和其他...🎉一键更改成绩单,轻松点亮你的未来! 💡[书]想知道自己成绩怎么样?别担心!我们来帮您解答疑惑。 在这里,只需轻轻一点按钮,就能立即查看到分数、排名和其他...
🎉一键更改成绩单,轻松点亮你的未来! 💡[书]想知道自己成绩怎么样?别担心!我们来帮您解答疑惑。 在这里,只需轻轻一点按钮,就能立即查看到分数、排名和其他...
 
Grade 6 Lesson 7 Environment Protection.pptx
Grade 6 Lesson 7 Environment Protection.pptxGrade 6 Lesson 7 Environment Protection.pptx
Grade 6 Lesson 7 Environment Protection.pptx
 
我了解到黑客在某些领域拥有卓越的技术能力,特别是在处理系统漏洞方面。在当前的情境下,如果我想要改变我的毕业成绩,他们的帮助或许是我唯一可行的选择。【微 t...
我了解到黑客在某些领域拥有卓越的技术能力,特别是在处理系统漏洞方面。在当前的情境下,如果我想要改变我的毕业成绩,他们的帮助或许是我唯一可行的选择。【微 t...我了解到黑客在某些领域拥有卓越的技术能力,特别是在处理系统漏洞方面。在当前的情境下,如果我想要改变我的毕业成绩,他们的帮助或许是我唯一可行的选择。【微 t...
我了解到黑客在某些领域拥有卓越的技术能力,特别是在处理系统漏洞方面。在当前的情境下,如果我想要改变我的毕业成绩,他们的帮助或许是我唯一可行的选择。【微 t...
 
保分服务在SAT考试作弊问题上的应对策略和措施是否充分,如何确保服务的可靠性??
保分服务在SAT考试作弊问题上的应对策略和措施是否充分,如何确保服务的可靠性??保分服务在SAT考试作弊问题上的应对策略和措施是否充分,如何确保服务的可靠性??
保分服务在SAT考试作弊问题上的应对策略和措施是否充分,如何确保服务的可靠性??
 
未毕业在线购买日本熊本县立大学学位记🏆学习成绩单电子版定制🏆克隆爱尔兰大学文凭🏆CFA证书定制
未毕业在线购买日本熊本县立大学学位记🏆学习成绩单电子版定制🏆克隆爱尔兰大学文凭🏆CFA证书定制未毕业在线购买日本熊本县立大学学位记🏆学习成绩单电子版定制🏆克隆爱尔兰大学文凭🏆CFA证书定制
未毕业在线购买日本熊本县立大学学位记🏆学习成绩单电子版定制🏆克隆爱尔兰大学文凭🏆CFA证书定制
 

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

  • 1. 主备备的两个备机转为双 Master 出现诡异的 slave lag 问题 有三台 MySQL 服务器,a,b 和 c,复制关系为 a -> b -> c。a,b,c 的 server_i d 分别为 1,2,3 因为需要切换为 a b <-> c,也就是说,a 单独出来,b 和 c 作为双 master 结 构时。 这种切换会经常出现在需要搭建备机把数据备份出来,然后把 a 独立出来的 cas e 中。 昨天,我就做了这样的切换,结果发现出现莫名奇妙的 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 statusG (这里先停 io_thread 是为了 SQL thread 和 IO thread 都执行到了同一个位置,
  • 2. change master 的时候没有风险) stop slave;change master to … ; start slave; (change master 到 show slave status 的 Master_Log_File:和 Exec_Master_Lo g_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,在 chan ge master 的位置在 event1 出现之前,那么 event1 肯定会被重新复制到 b 去, event1 的 server_id 是 1,那么 b 判断,这个 event1 不是我提交的,需要在本 地执行,并且把它记录到了自己的 binlog 中; 由于 b 和 c 是双 master 结构,event1 又复制到了 c,c 同样判断它不是我提交
  • 3. 的,那么我需要在本地执行,并且记录到本地 binlog 中。 这样 event1 就在 b 和 c 之间循环往复,时间保持不变,MySQL 的 slave lag 也 就一下子是 0,一下子是几千秒了。 这里,还需要说明一点,在环型复制里面,event 之所以能够在环内只循环一次, 而不是重复做,是因为提交的那个节点会发现这个 event 的 server_id 是自己的 server_id,也就是说是自己提交的。那么,它就不会把这个 event 再应用一次, 自然也不会记录到 binlog。这个循环就结束了。除非你闲着没事做,设置了 rep licate-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. research report for MySQL multi-master tool 把老的多主 master 向同一个 slave 复制的文档找出来了。这个是研究性质的, 不会牵涉到太多技术细节,所以不担心会有泄漏以前公司技术的嫌疑。之前公司 的这个软件已经完成了,包括事件解析,应用以及冲突处理。我自己想做一个开
  • 4. 源的,multi_master 的版本,但是架构基本上会非常不同。希望有时间能够真正 的做出来阿,这个功能相信对我们还是非常有用的。 Multi-Master 测试报告 目 录 目 录2 1 报告信息 3 2 概述 4 2.1 目的 4 2.2 相关信息 4 2.3 MySQL replication 数据复制格式 5 3 测试过程 9 3.1 测试进度安排 9 3.2 测试过程描述 9 4 测试结论 12 4.1 测试最终结论 12 5 附录 13 5.1 附录 1 MySQL 5.1.20 Beta 包含的事件类型 13 5.2 附录 2 MySQL 5.1.20 Beta 各事件的附加事件头长度 14
  • 5. 5.3 附录 3 MySQL 5.1.20 Beta 中各列在内部存储时可能的各种数据类型 16 5.4 附录 4 MySQL 5.1.20 Beta 中各 ROW_EVENT 的 m_flags 包含的标志位 17 概述 目的 测 试是否可以模拟 MySQL replication 的功能构建一个小工具。该工具模拟 re plication 端的两个线程:I/O 线程和 SQL 线程。I/O 线程用于从 master 端 (主 服务器)注册自己和申请获得 master 的 binlog 信息,把获得的 binlog 信息进行 解析并保存在本地 relay-log 中。SQL 线程负责读 取解析 relay-log 并在本地 My SQL 服务器上执行这些语句。从而达到从 master 端复制数据并在 slave 端(从 服务器)执行,以及时同步 slave 的目的。用这个工具,我们可以避免 MySQL 只能从单个 master 中同步数据的限制,实现从多个 master 中复制数据,同步 s lave 的功 能。 相关信息 项目来源 在我们 GSB 的数据库复制同步中,杭州 的 GSB 数据库需要从不同的 master 端获得数据,并同步到自己的数据库中。而 MySQL 本身不提供 multi-master 的 功能,只能从单个 master 中复制和同步数据,并且在将来一段时间也不准备增
  • 6. 加这个功能。所以我们打算先对 MySQL 的源代码进行理解和分析,并尝试着模 拟 MySQL replication 的功能,研究 multi-master 工具的可行性。 测试环境 在 这次测试中我们主要用到了两台机器,192.168.1.112 和 192.168.1.113,它 们都是虚拟机,安转的操作系统为 Linux 2.6.16。其中 112 机器中安装的 MyS QL server 版本为:5.0.27-standard-log,作为 master。113 机器安装的 MySQ L server 版本为:5.0.24-max-log,作为 slave。由于采用了增加 server_id 列标 识提交语句的 site 的策略,我们将测试环境 中的两个 MySQL 版本升级为 5.1. 20 Beta。 其他相关信息 MySQL 的 数据复制功能主要涉及到三个线程(master 端一个,slave 端两个)。 当用户在 slave 端提交 start slave;slave 端将首先创建 I/O 线程,I/O 线程会连 接到 master 并请求 master 将其 binlog 中的语句发送回来。Master 接收 到请求 后将创建一个线程(Binlog Dump)用于发送 binlog 信息给 slave,用户可以通 过 Show processlist 在 master 端看到此线程。I/O 线程读取主服务器 Binlog D ump 线程发送的内容并将该数据拷贝到从服务器数据目录中的本地文件中,即 中继日志(relay-log)。第 3 个线程是 SQL 线程,是 slave 端创建的用于读取 中继日志并执行日志中语句的线程。 如果有多个从服务器,主服务器将为每个当前连接的从服务器创建一个(Binlog Dump)线程;每个从服务器都有自己的 I/O 和 SQL 线程。
  • 7. MySQL 复制基于服务器在二进制日志中对所有数据库的更改(更新、删除等等)。 因此,要进行复制,必须在主服务器上启用二进制日志。 从 MySQL 5.1 开始,MySQL replication 可以支持两种复制类型,第一种是原 有的 statement based replication,也就是将在 master 端提交的查询语句及相 关环境一起记录到 binlog 中,slave 模拟该环境并提交语句执行。第二种是 row based replication,即将 master 端提交的查询语句改变的所有行数据记录到 b inlog 中,slave 端获得这些数据直接提交给 MySQL server,修改对应数据文件。 这两种复制类型可以单独运行或者混合起来。MySQL 默认的复制类型就是混合 类型的复制,它根据查询类型,表的类型等相 关因素确定该表的复制类型。 在每一个数据库的表中,我们增加了一列 server_id 用于模拟线程变量 thd->ser ver_id。但是由于 server_id 存在于表中并且将随表数据一同复制,在复制中不 能随意改变,所以这个方法存在一定的限 制,后面我们将详细描述。由于我们 使用 server_id 列判断表中某一行(row)数据操作的最先发起 site,所以我们必 须保证复制的列中包括这一 列,这样我们必须限制 MySQL master 和 slave 端 replication 的类型为 row based,即 row-based replication。注意:row-based replication 是 MySQL 5.1 版本才被引入的。下面我们所涉及的复制除了特殊指 明外都是指 row based replication。 在 MySQL 5.1.20 Beta 版本中,在 master 端如果一条语句改变了一个表的多 条数据,master 将首先在 binlog 中记录下一个 Tablemap 事件用于将表的相 关信息以及表的 id 信息对应记录下来。而针对每一行需要改变的数据,master 端单独记录一个 writerow(updaterow,deleterow)事件,而且每一行数据都 是二进制的 MySQL 内部格式的数据。其中 writerow 对应的查 询语句是:inser
  • 8. t 和 replace;updaterow 对应的是 update 语句;deleterow 对应的是 delete 语 句。而 writerow 事件中除了环境数据以外还包含了对应的行数据为 record[0]。 record[0]中包含有将要插入 MySQL 表中的一行数据。 Update 语句包含两个行 数据,record[0]表示将要更新存储到表中的行数据,而 record[1]中包含了 upda te 将要替换的已存在于表中 的行数据。Delete 的 record[0]中包含的是将要删除 的行数据。由于 binlog 中的数据都是二进制的 MySQL 格式的数据,而我们也没 有找到 直接将这些数据插入 MySQL server 的接口,所以在对这些数据操作之 前,我们首先必须将这些数据转换回可读数据。然后就可以直接向 MySQL ser ver 提交对应的查询语句。 每种不同的类型在 MySQL 中保存的格式都不一样,MySQL 5.1.20 Beta 的数 据类型见附录 3。 MySQL replication 数据复制格式 这 里我们基于 MySQL 5.1.20 Beta 描述 MySQL 两个 slave 端的 thread 发送 和接收数据的格式。某些字段所占的字节数跟 MySQL 的版本有关,这里我们所 描述的为 binlog 版本为 4,MySQL server 版本为 5.1.20 Beta 下的数据格式。 MySQL I/O thread 数据格式 向主服务器注册自己 向 主服务器注册自己并不是一个必须的操作,如果没有注册同样可以向主服务 器请求数据。如果需要向主服务器注册,那么可以调用 mysql.h 中的 simple_c ommand(mysql, command, arg, length, skip_check)函数,在 arg 参数中依序
  • 9. 填入下述的各个字段,并指定其中的参数 command 为 COM_REGISTER_SLA VE 以注册自 己。 名称 字节数 含义 server_id 4 本 MySQL instance 的 server_id 值 strlen(report_hos 1 or 2 标识接下来的 report_host 的长度,如果长 t) 度<251 占 1 个字节,否则占两个字节 report_host Strlen(report_hos 向主服务器注册的 MySQL instance 标识 t) strlen(report_use 1 or 2 标识接下来的 report_user 的长度,如果长 r) 度<251 占 1 个字节,否则占 2 个字节 report_user Strlen(report_use 向主服务器注册的用户名 r) strlen(report_pas 1 or 2 标识接下来的 report_password 的长度,如 sword) 果长度<251 占 1 个字节,否则占 2 个字节 report_password Strlen(report_pas 向主服务器注册的密码 sword) report_port 2 向主服务器注册的端口 rpl_recovery_ran 4 复制的恢复等级 k master_id 4 填入 0,主服务器将自行填入 master_id 值
  • 10. register_master 图 1、主服务器注册示意图 向主服务器请求数据 从 服务器向主服务器发送了请求数据的命令以后主服务器将根据要求将对应 bi nlog 文件的指定位置开始的事件记录发送给从服务器。向主服务器请求数据, 可以 调用 mysql.h 中的 simple_command(mysql, command, arg, length, ski p_check)函数,在 arg 参数中依序填入下述的各个字段,并指定其中的参数 co mmand 为 COM_BINLOG_DUMP。 名称 字节数 含义 master_log_pos 4 请求主服务器发送的事件记录在 bin log 文件中的偏移量 binlog_flags 2 暂时填 0,做扩展用 server_id 4 本 MySQL instance 的 server_id 值 logname Strlen(logname) 请求主服务器发送的 binlog 文件的 文件名 如果没有指定 MySQL 使用 methods,那么我们应该调用函数 sql_common.h 头 文件中的 cli_advanced_command()代替 simple_command()。
  • 11. 向 主服务器请求了数据以后,从服务器就可以通过 cli_safe_read(mysql);获得 主服务器发送过来的数据,每次获得一个事件记录的数据。 cli_safe_read 的返 回值标示了从主服务器发送过来的数据的数据字节数。而发送过来的数据保存在 mysql->net->read_pos 数组中。I/O thread 模块可以利用 MySQL 的 io_cache 将对应事件记录存储到 relay-log 文件中。 MySQL binlog 文件初始化 由于 MySQL binlog 的特殊性,以及为了 mysqlbinlog 工具能够识别我们 relay-l og 文件,在新建一个 relay-log 文件时必须写入一定的初始化数据。这些初始化 数据依序包括如下字段: 名称 字节数 含义 BINLOG_MAGIC BIN_LOG_HEADER_SI Binlog 文件的标识值 (即”xfex62x69x ZE(4) 6e”) MySQL SQL thread 数据格式 只 要循环的调用 cli_safe_read 函数,从服务器可以不断得到从主服务器发送过 来的事件记录。接下来我们介绍一下相关的一些事件记录格式。在提交了 COM _BINLOG_DUMP 命令后,主服务器首先给从服务器发送的两个事件依序分别为 ROTATE_EVENT 和 FORMAT_DESCRIPTION_EVENT 事件。ROTATE_EVE NT 事件用来标示接下来主服务器将从哪一个 binlog 文件的哪个位置开始 发送 事件记录。而 FORMAT_DESCRIPTION_EVENT 事件用来记录本 MySQL inst
  • 12. ance 的 server_id 值,binlog 版本号,MySQL server 的版本,本 relay-log 创建 的时间以及各个不同事件的事件头所占的字节数等信息。我们关心的其他的事件 记录的格式包括 WRITE_ROWS_EVENT,UPDATE_ROWS_EVENT,DELET E_ROWS_EVENT 等。 事件头字段描述 各个事件都包括一个事件头,事件头的字段格式如下: 名称 字 节 含义 数 When 4 事件的创建时间。 Type 1 事件的类型(见附录 1)。 server_id 4 事件发生时所在 MySQL 的 server_id 值。 data_writte 4 该事件一共占用的字节数,包括事件头的字节数。 n log_pos 4 下一事件在 binlog 文件中将要开始的位置,即本事件的结束位 置 Flags 2 事件的其他标志位。 ROTATE_EVENT 事件字段描述 由于各个事件的事件头基本一致,这里我们就不重复介绍事件头的各字段了,后 面的各个事件我们也都将忽略对事件头字段的描述。
  • 13. ROTATE_EVENT 事件的附加事件头字段主要包括: 名称 字 节 含义 数 pos 8 主服务器将要发送的事件记录在 binlog 文件中的偏移量。一般 为从服务器提交的 COM_BINLOG_DUMP 请求中的偏移量值。 ROTATE_EVENT 事件的其他信息字段主要包括: 名称 字节数 含义 new_log_i strlen(new_log_ 主服务器将要发送的事件记录的 binlog 文件名。一 dent ident) 般为从服务器提交的 COM_BINLOG_DUMP 请求中 的 binlog 文件名。 FORMAT_DESCRIPTION_EVENT 事件字段描述 FORMAT_DESCRIPTION_EVENT 事件的附加事件头的字段如下: 名称 字节数 含义 binlog_versi 2 Binlog 文件的版本号,这里一般为最新的版 on 本号 4 server_versi ST_SERVER_VER_L MySQL 的版本号。例如: 5.1.20-beta-log” ” on EN(50) Created 4 事件创建时间,这里一般和事件头中的 wh en 一致
  • 14. event_head 1 一般事件的事件头长度,一般设置为:LOG er_len _EVENT_HEADER_LEN(19) post_header ENUM_END_EVENT- 不同事件类型的附加事件头的长度,见附录 _len 1(26) 2。 TABLE_MAP_EVENT 事件字段描述 TABLE_MAP_EVENT 事件的附加事件头的字段如下: 名称 字节数 含义 m_table_id 6(5.1.4 前的版本中为 4) 表的 id 标识符 m_flags 2 表的各种标志位,见附录 4 TABLE_MAP_EVENT 事件的其他信息字段主要包括: 名称 字节数 含义 m_dbl 1 数据库名的长度 en m_dbn m_dblen+1 数据库名,以’0’结尾 am m_tblle 1 表名的长度 n m_tbln m_tbllen+1 表名,以’0’结尾 am
  • 15. m_colc net_field_len 表的字段个数,所占字节数根据第一个字节的大小由 net_f nt gth() ield_length 函数确定 m_colt m_colcnt 表的各个字段的字段类型,参见附录 3。 ype WRITE_ROWS_EVENT 事件字段描述 WRITE_ROWS_EVENT 事件的附加事件头的字段如下: 名称 字节数 含义 m_table_id 6(5.1.4 前的版本中为 4) 表的 id 标识符 m_flags 2 表的各种标志位,见附录 4 WRITE_ROWS_EVENT 事件的其他信息字段主要包括: 名称 字节数 含义 m_width net_field_length() 表的各列的位图长度,所占字节数根据第一个字节 的大小由 net_field_length 函数确定 m_cols.bit (m_width + 7) / 表的各列的位图,每一位表示 m_rows_buf 是否包 map 8 含表中一列的值,如果没有置位表示该列的值没有 包含在 m_rows_buf 中 m_rows_b 剩余字节数(len- 将要插入到表中的一行数据值。 uf 已占字节数)
  • 16. UPDATE_ROWS_EVENT 事件字段描述 UPDATE_ROWS_EVENT 事件的附加事件头的字段如下: 名称 字节数 含义 m_table_id 6(5.1.4 前的版本中为 4) 表的 id 标识符 m_flags 2 表的各种标志位,见附录 4 UPDATE_ROWS_EVENT 事件的其他信息字段主要包括: 名称 字节数 含义 m_width net_field_length() 表的各列的位图长度,所占字节数根据第一 个字节的大小由 net_field_length 函数确定 m_cols.bitm (m_width + 7) / 8 表中被匹配行数据的各列的位图,每一位表 ap 示 m_rows_buf 是否包含表中该列的值。 m_cols_ai.bi (m_width + 7) / 8 表中将要更新的行数据的各列的位图,每一 tmap 位表示 m_rows_buf 是否包含表中一列的 值。 m_rows_buf 剩余字节数(len-已占字 表中被匹配的那一行数据的值以及将要更 节数) 新的一行数据值。 DELETE_ROWS_EVENT 事件字段描述 DELETE_ROWS_EVENT 事件的附加事件头的字段如下:
  • 17. 名称 字节数 含义 m_table_id 6(5.1.4 前的版本中为 4) 表的 id 标识符 m_flags 2 表的各种标志位 DELETE _ROWS_EVENT 事件的其他信息字段主要包括: 名称 字节数 含义 m_width net_field_length() 表的各列的位图长度,所占字节数根据第一个字节 的大小由 net_field_length 函数确定 m_cols.bit (m_width + 7) / 表的各列的位图,每一位表示 m_rows_buf 是否包 map 8 含表中一列的值。 m_rows_b 剩余字节数(len- 表中将要删除的一行数据值。 uf 已占字节数) XID_EVENT 事件字段描述 XID_EVENT 一般出现在一个事务操作(transaction)之后或者其他语句提交之后。 它的主要作用是提交事务操作和把事件刷新至 binlog 文件中。 XID_EVENT 事件的信息字段包括: 名称 字节数 含义 xid sizeof(xid) 8 commit 标识符
  • 18. 测试过程 测试进度安排 2007-07-26 —— 2007-08-03 理解和分析 MySQL slave 端 I/O 线程以及 SQL 线程的实现细节。 2007-08-04 —— 2007-08-08 模拟实现 MySQL replication 的 I/O 线程,实现向 master 请求 binlog,并记录 读到的信息到一个本地日志文件 relay-log 中的功能。使用 mysqlbinlog 应该能 查看该日志。 2007-08-09 —— 2007-08-15 模拟实现 MySQL replication 的 SQL 线程,实现读取并解析 relay-log 文件,设 置 slave 端的执行环境以提交查询。 2007-09-03 —— 2007-09-07 熟悉并了解 MySQL 5.1.20 Beta 中 row based replication 配置以及实现。 2007-09-10 —— 2007-09-21 模拟实现 MySQL 5.1.20 Beta 中 insert,update,delete 等语句的在 slave 段 的解析并生成对应的语句。 2007-09-24 —— 2007-09-30 阅读和了解 replication 冲突解决方案,分析 update(delete)更新 0 row 冲突 和 update(delete)复制执行空语句之间的区别。
  • 19. 测试过程描述 通过对 MySQL 源代码的阅读,我们了解并熟悉了 MySQL replication 的基本原 理和实现细节。 对 MySQL I/O 线程的模拟相对比较简单,这个线程的向 master 注册自己及请 求发送 binlog 信息都有相应的接口提供出来,并且对于从 master 端接收的信 息也只做了比较简单的分析就直接将该信息存入 relay-log 日志文件中。所以我 们模拟 I/O 线程比较顺利。 对于 MySQL 的 SQL 线程的模拟实现中,读取和解析 relay-log 日志文件虽然比 较繁琐,但是基本实现的困难不大。主要的困难出现在如何设置 slave 端的执行 环境。 这 里设置 slave 端的执行环境包括从 master 端复制过来的一些环境信息,比如: server_id,charset,timezone, auto_increment_increment,auto_increment _offset 等等。其中最重要的是 server_id,它表示 master 发送过来的 binlog 信 息中,将要执行的这一条语句最开始是在哪一个 MySQL server 中提交执行的。 如果配置了环形的 replication 链,并且复制过来的 server_id 与本机的 server_i d 相等的话,那么说明 这条语句最开始就是在本机中执行的,它对应的语句应 该被忽略。 如果从 master 服务器复制过来的 server_id 与本机的 server_id 不同,那么就应 该在本机执行这条语句。这里我们特别要注意的是:在记录本地 binlog 日志文 件时,server_id 应该保证为复制 过来的 server_id,而不是我们普通提交查询
  • 20. 时记录的本地的 server_id。不然在配置双向复制和环形复制链时将造成数据复 制的死循环,从而造 成数据紊乱。 我们仔细察看了 SQL 线程的源代码,发现它在读取 relay-log 记录时会把复制过 来的 server_id 信息保存在对应 的 SQL 线程变量 thd->server_id 中,从而在写 本机 binlog 日志文件时记录的 server_id 将会是从 master 端复制过来的 server _id 信息。如果我们要模拟 SQL 线程,我们需要在提交查询前修改连接会话的相 应变量。但由于 server_id 在 MySQL 中是一个全局变 量,而不是一个会话期变 量,所以我们不能在连接中修改这个变量(不然,从修改了 server_id 后到恢复 本机的 server_id 值之间的这一段时间 里,在 binlog 日志文件中记录的用户提 交的语句对应的 server_id 值将保持为修改后的值,而不是本机 server_id 值)。 在写 binlog 日志时,写入的 server_id 值依赖于连接线程中的 server_id 值,而 MySQL server 也没有提供任何修改连接线程中对应的 server_id 变量值的接口。 这样,我们无法模拟 SQL 线程来执行复制过来的语句。 为 了能够标识数据最新的插入和更新 site,我们在每一个数据库表上都增加了 一列 server_id。在对表执行 insert 操作时,server_id 将 自动赋值为该 site 的 s erver_id 值。也就是说,在增加 server_id 列时设置它的 default 值为该 MySQL 的 server_id 值。 而为了保证在本机执行的 update 操作对 server_id 有同样的 影响,我们可以借助 MySQL 的 trigger 功能,使得在本机上执行的 update 操作 修改行数据值中 server_id 的值为本机的 server_id 值。另外,我们还要注意的 是用户在提交查询时不应该自己操作 server_id 值,而应该通过我们设置的 My SQL 的已有机制进行操作,以防出现数据复制的死循环。下面我们分别描述 in sert,update,delete 操作的提交和复制过程。
  • 21. 1. Insert 的提交和复制: 由 于在 MySQL 的 row based replication 中 insert 和 replace 语句在 binlog 中 都被记录为 write row 事件,所以我们把 replace 语句的提交和复制合并到 inse rt 的提交和复制中。Insert 的提交和复制相对来说比其他的的操作简单。我们只 要设置 server_id 列的 default 值为对应 MySQL server 的 server_id 值,当用 户提交 insert 查询时,server_id 将自动赋值。 MySQL 将把 insert 插入的每一 而 行数据自动 地对应一个 write row 事件并记录在 binlog 中。其他的 slave 可以 通过我们的工具将 binlog 文件复制到本地,然后解析 write row 以生成对应的 r eplace 语句。通过 server_id 列的值我们可以很容易的知道最开始提交 insert 语 句的 MySQL 的 server_id 值,从而在该 write row 事件复制到该 MySQL insta nce 时停止复制,而避免数据复制的死循环。 1. Update 的提交和复制: Update 的提交和复制比较复杂。在一个 MySQL 上提交的 update 语句将被对 应为一个 update row 事件记录在 binlog 文件中,slave 端复制并解析 update r ow 事件生成对应的 update 语句。这里我们举一个数据复制的例子:如果某一条 数据首先在 MySQL instance 1(M1)上插入,那么该数据的 server_id 列值为 1,该数据复制到 MySQL instance 2(M2)数据将保持不变。但是,如果此时 在 M2 上提交的 update 语句更新了该行数据,那么 server_id 值仍然保持为 1。 如果该 update row 事件重新复制回 M1,那么我们的复制工具发现该行数据的 s erver_id 值与本 MySQL instance 的 server_id 值一样,认为该 update 语句最开
  • 22. 始就是在本机提交执行的,它将忽略该 update 语句。针对这个问题,我们有两 种解决方案: 1. 1. 1. 由于在 MySQL server 中,update 要更新的那一行数据匹配不成 功那么对于 MySQL 数据库的将不会有任何修改,并且这一条 upd ate 语句也将不会记录到 binlog 文件中。我们可以利用这一点允许 multi-master 工具在解析 update row 事件时忽略对 server_id 的检 查,允许从其他 MySQL instance 复制的在本 MySQL instance 提交的 update 语句继续执行,实际上该语句将由于匹配不到对应 的数据而执行空操作。这里执行的空操作实际上和我们后面要讨论 的一种冲 突类型(slave 端提交的查询不能更新数据冲突)是一样 的。 这里我们仍然用上面的例子进行说明: 首 先在 M1 上提交的 insert 语句被复制到 M2 并插入对应的数据,而当用户更 新这一行数据后,update row 事件复制回 M1 时,由于 multi-master 工具不再 检查 server_id 值,那么同样的 update 语句将在 M1 执行。M2 重新复制得到 u pdate row 事件并生成对应 update 语句,但是由于该 update 语句是最先在 M2 提交成功的,那么正常情况下该 update 语句不能匹配到要修改的那一行 数据, 从而执行一条空语句,不会出现数据复制的死循环。当然,如果在复制回 M2 数
  • 23. 据前有其他的语句 insert 或者 update 生成了若干行能够被匹配的 数据的话,这 种方案是行不通的。 1. 1. 1. 第二种解决方案相对复杂一些。它保证了 update 语句更新行数据 的 server_id 值为本 MySQL server 的 server_id 值。我们利用了 MySQL 提供的 trigger, update 语句提交执行之前改变行数据 N 在 EW 的 server_id 值为本 MySQL server 的 server_id 值。(在 My SQL 的 trigger 中,OLD 行数据表示数据库中已有的将要被 updat e 匹配的数据,而 NEW 行数据 表示 update 语句将要更新为其值 的行数据)。但是,由于我们的 multi-master 工具将生成的语句直 接提交到本 MySQL instance 中执行,那么如果我们的 trigger 不 能识别工具提交的语句和用户提交的语句,而把 NEW 行数据的 s erver_id 值全部改成本 instance 的值,复制的死循环一定会出现。 例如:在上面的例子中,M1 的 server_id 为 1,在 M2 上 update 的数据复制到 M1 上提交执 行,如果 trigger 不能识别出它是从 m ulti-master 工具生成的语句,而是把它的 NEW 行数据修改为 1, 那么记录在 M1 的 binlog 中的数 据将不能标识最开始提交语句的 MySQL instance 位置,从而不能中断数据复制的循环。至少有两 种方法可以区分普通的用户提交语句和 multi-master 工具提交的 语句。 专门指定 一个用户用于 multi-master 工具提交查询语句, 1, 以区别于其他用户提交的语句。在 trigger 中我们可以用 user()
  • 24. 函数获得用户名来确定查 询语句提交的来源。2,通过 multi-mas ter 修改相应语句而且使得它提交的所有语句与普通用户提交的语 句不同。下面我们详细阐述第二种策略。 我 们发现用户提交的所有 update 语句都有一个共同的特点:OLD 行数据和 N EW 行数据的 server_id 值相等。如果我们在用工具生成 OLD 行数据和 NEW 行 数据的 server_id 值相等的 update 语句时,将 NEW 行数据的 server_id 值改变 (例如改为 0)以区别于用户提交的语句。这样 trigger 就能根据是否用户提交 的语句而进行相应的操作了。一般来说,我们的 trigger 可以写成如下的形式: delimiter // create trigger update_set_srvid_test_test001 before update on test001 for each row BEGIN IF NEW.server_id=OLD.server_id THEN SET NEW.server_id=1; ELSEIF NEW.server_id=0 THEN SET NEW.server_id=OLD.server_id; END IF; END;// delimiter ; 1. Delete 的提交和复制:
  • 25. Delete 的提交和复制和 update 的基本类似。在一个 MySQL 上提交的 delete 语 句将被对应为一个 delete row 事件记录在 binlog 文件中,slave 端复制文件并解 析 delete row 事件生成对应的 delete 语句提交执行。为了解决本 MySQL 上 in sert 的数据在别的 MySQL instance 上 delete 的问题,我们的第一个解决方案 和 update 一样,也是忽略对 server_id 值的检查。同样,它存在着两个缺点:1. 它与冲突解决方案中的一种类型是一样的,我们不能区别这样的一个空操作是 否是一个冲突。2. 如果在 delete 语句复制回本 instance 前,有其他的语句产生 了该 delete 语句能够匹配的行数据,那么这一行数据将被错误的删除掉。同时, 因 为 delete 不包括 NEW 行数据,update 的第二个解决方案对 delete 语句不适 用。 测试结论 测试最终结论 通过上面的测试和分析,我们发现可以通过增加 server_id 列和增加 trigger 等手 段模拟 MySQL 的 replication 功能,从而实现 MySQL 的多主复制工具 multi-ma ster。 附录 附录 1 MySQL 5.1.20 Beta 包含的事件类型 下面列举了各种 MySQL 的事件类型(代码拷贝自 MySQL 5.1.20 的源代码): enum Log_event_type
  • 26. { /* Every time you update this enum (when you add a type), you have to fix Format_description_log_event::Format_description_log_event(). */ UNKNOWN_EVENT= 0, START_EVENT_V3= 1, QUERY_EVENT= 2, STOP_EVENT= 3, ROTATE_EVENT= 4, INTVAR_EVENT= 5, LOAD_EVENT= 6, SLAVE_EVENT= 7, CREATE_FILE_EVENT= 8, APPEND_BLOCK_EVENT= 9, EXEC_LOAD_EVENT= 10, DELETE_FILE_EVENT= 11, /* NEW_LOAD_EVENT is like LOAD_EVENT except that it has a longer sql_ex, allowing multibyte TERMINATED BY etc; both types share the
  • 27. same class (Load_log_event) */ NEW_LOAD_EVENT= 12, RAND_EVENT= 13, USER_VAR_EVENT= 14, FORMAT_DESCRIPTION_EVENT= 15, XID_EVENT= 16, BEGIN_LOAD_QUERY_EVENT= 17, EXECUTE_LOAD_QUERY_EVENT= 18, TABLE_MAP_EVENT = 19, /* These event numbers were used for 5.1.0 to 5.1.15 and are therefore obsolete. */ PRE_GA_WRITE_ROWS_EVENT = 20, PRE_GA_UPDATE_ROWS_EVENT = 21, PRE_GA_DELETE_ROWS_EVENT = 22, /* These event numbers are used from 5.1.16 and forward */
  • 28. WRITE_ROWS_EVENT = 23, UPDATE_ROWS_EVENT = 24, DELETE_ROWS_EVENT = 25, /* Something out of the ordinary happened on the master */ INCIDENT_EVENT= 26, /* Add new events here – right above this comment! Existing events (except ENUM_END_EVENT) should never change their numbers */ ENUM_END_EVENT /* end marker */ }; 附录 2 MySQL 5.1.20 Beta 各事件的附加事件头长度 下面列举了 MySQL 5.1.20 Beta 各事件的附加事件头长度(拷贝自 MySQL 源 代码): /* event-specific post-header sizes */ // where 3.23, 4.x and 5.0 agree
  • 29. #define QUERY_HEADER_MINIMAL_LEN (4 + 4 + 1 + 2) // where 5.0 differs: 2 for len of N-bytes vars. #define QUERY_HEADER_LEN (QUERY_HEADER_MINIMAL_LEN + 2) #define LOAD_HEADER_LEN (4 + 4 + 4 + 1 +1 + 4) #define START_V3_HEADER_LEN (2 + ST_SERVER_VER_LEN + 4) #define ROTATE_HEADER_LEN 8 // this is FROZEN (the Rotate post-he ader is frozen) #define CREATE_FILE_HEADER_LEN 4 #define APPEND_BLOCK_HEADER_LEN 4 #define EXEC_LOAD_HEADER_LEN 4 #define DELETE_FILE_HEADER_LEN 4 #define FORMAT_DESCRIPTION_HEADER_LEN (START_V3_HEADER_L EN+1+LOG_EVENT_TYPES) #define ROWS_HEADER_LEN 8 #define TABLE_MAP_HEADER_LEN 8 #define EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN (4 + 4 + 4 + 1) #define EXECUTE_LOAD_QUERY_HEADER_LEN (QUERY_HEADER_LEN + EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN) #define INCIDENT_HEADER_LEN 2
  • 30. post_header_len[START_EVENT_V3-1]= START_V3_HEADER_LEN; post_header_len[QUERY_EVENT-1]= QUERY_HEADER_LEN; post_header_len[ROTATE_EVENT-1]= ROTATE_HEADER_LEN; post_header_len[LOAD_EVENT-1]= LOAD_HEADER_LEN; post_header_len[CREATE_FILE_EVENT-1]= CREATE_FILE_HEADER_LEN; post_header_len[APPEND_BLOCK_EVENT-1]= APPEND_BLOCK_HEADER _LEN; post_header_len[EXEC_LOAD_EVENT-1]= EXEC_LOAD_HEADER_LEN; post_header_len[DELETE_FILE_EVENT-1]= DELETE_FILE_HEADER_LEN; post_header_len[NEW_LOAD_EVENT-1]= post_header_len[LOAD_EVENT- 1]; post_header_len[FORMAT_DESCRIPTION_EVENT-1]= FORMAT_DESCRIP TION_HEADER_LEN; post_header_len[TABLE_MAP_EVENT-1]= TABLE_MAP_HEADER_LEN; post_header_len[WRITE_ROWS_EVENT-1]= ROWS_HEADER_LEN; post_header_len[UPDATE_ROWS_EVENT-1]= ROWS_HEADER_LEN; post_header_len[DELETE_ROWS_EVENT-1]= ROWS_HEADER_LEN; /* We here have the possibility to simulate a master of before we changed the table map id to be stored in 6 bytes: when it was stored in 4
  • 31. bytes (=> post_header_len was 6). This is used to test backward compatibility. This code can be removed after a few months (today is Dec 21st 2005), when we know that the 4-byte masters are not deployed anymore (chec k with Tomas Ulin first!), and the accompanying test (rpl_row_4_bytes) too. */ DBUG_EXECUTE_IF(“old_row_based_repl_4_byte_map_id_master”, post_header_len[TABLE_MAP_EVENT-1]= post_header_len[WRITE_ROWS_EVENT-1]= post_header_len[UPDATE_ROWS_EVENT-1]= post_header_len[DELETE_ROWS_EVENT-1]= 6;); post_header_len[BEGIN_LOAD_QUERY_EVENT-1]= post_header_len[APPE ND_BLOCK_EVENT-1]; post_header_len[EXECUTE_LOAD_QUERY_EVENT-1]= EXECUTE_LOAD_ QUERY_HEADER_LEN; post_header_len[INCIDENT_EVENT-1]= INCIDENT_HEADER_LEN; 附录 3 MySQL 5.1.20 Beta 中各列在内部存储时可能的各种数据类型 enum enum_field_types
  • 32. { MYSQL_TYPE_DECIMAL = 0, MYSQL_TYPE_TINY = 1, MYSQL_TYPE_SHORT = 2, MYSQL_TYPE_LONG = 3, MYSQL_TYPE_FLOAT = 4, MYSQL_TYPE_DOUBLE = 5, MYSQL_TYPE_NULL = 6, MYSQL_TYPE_TIMESTAMP = 7, // 4 from_unixtime(0x) MYSQL_TYPE_LONGLONG = 8, MYSQL_TYPE_INT24 = 9, //field_medium MYSQL_TYPE_DATE = 10, MYSQL_TYPE_TIME = 11, MYSQL_TYPE_DATETIME = 12, MYSQL_TYPE_YEAR = 13, MYSQL_TYPE_NEWDATE = 14, MYSQL_TYPE_VARCHAR = 15, //field_varstring MYSQL_TYPE_BIT = 16, MYSQL_TYPE_NEWDECIMAL = 246, MYSQL_TYPE_ENUM = 247,
  • 33. MYSQL_TYPE_SET = 248, MYSQL_TYPE_TINY_BLOB = 249, MYSQL_TYPE_MEDIUM_BLOB = 250, MYSQL_TYPE_LONG_BLOB = 251, MYSQL_TYPE_BLOB = 252, MYSQL_TYPE_VAR_STRING = 253, MYSQL_TYPE_STRING = 254, MYSQL_TYPE_GEOMETRY = 255, }; 附录 4 MySQL 5.1.20 Beta 中各 ROW_EVENT 的 m_flags 包含的标志位 在 MySQL 5.1.20 Beta 中,各 ROW_EVENT 都含有 m_flags 标志位集合。可 能的标志如下: 名称 值 含义 STMT_END_F 1 语句执行结束标志 NO_FOREIGN_KEY_CHECKS_F 2 不进行外键约束检查的标志 RELAXED_UNIQUE_CHECKS_F 4 不进行唯一键约束检查的标志 相关代码如下: /* These definitions allow you to combine the flags into an
  • 34. appropriate flag set using the normal bitwise operators. The implicit conversion from an enum-constant to an integer is accepted by the compiler, which is then used to set the real set of flags. */ enum enum_flag { /* Last event of a statement */ STMT_END_F = (1U << 0), /* Value of the OPTION_NO_FOREIGN_KEY_CHECKS flag in thd->option s */ NO_FOREIGN_KEY_CHECKS_F = (1U << 1), /* Value of the OPTION_RELAXED_UNIQUE_CHECKS flag in thd->option s */ RELAXED_UNIQUE_CHECKS_F = (1U << 2) }; typedef uint16 flag_set; /* Special constants representing sets of flags */ enum {
  • 35. RLE_NO_FLAGS = 0U }; MySQL row 方式的复制 relay 上次老大问我 row 方式的 binlog 复制到备机可不可能记录为 statement。根据我 对复制的了解和对 row 方式的理解,我回答的是不可能。因为 MySQL 的源码中, 我记得 row 方式的处理是 table map,row_log_event 分开的,然后 row_log_ev ent 中记录的是行的数据(包括 bitmap 对应对应的列在该 row_log_event 有没有 记录,整个 row_log_event 的长度,每个列的长度后面跟上列的具体数据等), 这些东西记录下载,MySQL 的开发者如果要把它还原成 statement 方式,并且 将相关的 auto_increment,var,rand 等还原出来难度还是非常大的,并且,row 方式实际上已经毁坏了 statement 的结构(比如:update tbl1 set c1=3 where id =3 记录为 row 的话,正常情况下,row_log_event 不会只记录了更新的这一列 c 1,它会记录 id 或者其他的列,虽然 id 或者其他的列值并没有更新。原因可能 是为了正确的更新对应的行。),如果想完全还原成和主机上提交的 statement 一模一样基本是不可能的。另外,我也没有看到 MySQL 源码中的相关代码有将 row 转换成 statement 的相关痕迹,所以判断 row 方式在备机中 log_slave_upd ates 会也被记录为 row 方式。 但是,口说无凭,实践致胜,我在主备机环境下测试了上面的这个情况。确实如 上所述,row 方式的 binlog,备机利用 log_slave_updates 记录到本机 binlog 也 是 row 方式。即使你设置备机的 binlog_format 为 statement。下面是我的测试 描述和结果。
  • 36. 1、主机上设置 binlog_format 为 row. root@localhost : alitest 10:01:09> set binlog_format=row; Query OK, 0 rows affected (0.00 sec) root@localhost : alitest 10:01:25> show variables like ‘bin%’; +——————-+———+ | Variable_name | Value | +——————-+———+ | binlog_cache_size | 2097152 | | binlog_format | ROW | +——————-+———+ 2 rows in set (0.00 sec) 2、备机上设置 binlog_format 为 statement root@localhost : (none) 10:06:44> set global binlog_format=’statement’; Query OK, 0 rows affected (0.00 sec) root@localhost : (none) 10:07:04> set binlog_format=’statement’; Query OK, 0 rows affected (0.00 sec) root@localhost : (none) 10:07:11> show variables like ‘bin%’; +——————-+———–+ | Variable_name | Value | +——————-+———–+ | binlog_cache_size | 2097152 | | binlog_format | STATEMENT |
  • 37. +——————-+———–+ 2 rows in set (0.00 sec) 3、主机上创建测试表: root@localhost : (none) 10:00:02> use alitest; Database changed root@localhost : alitest 10:00:50> create table test9 (c1 int unsigned pri mary key, c2 varchar(24)); Query OK, 0 rows affected (0.20 sec) 4、备机上查看目前的日志位置: root@localhost : (none) 10:04:49> show master logs; +——————+———–+ | Log_name | File_size | +——————+———–+ | mysql-bin.000001 | 125 | … | mysql-bin.000085 | 362605228 | +——————+———–+ 85 rows in set (0.00 sec) 4、主机上生成测试的 row 方式日志: root@localhost : alitest 10:01:27> insert into test9 (c1,c2) values (44, “34 3434″); Query OK, 1 row affected (0.00 sec)
  • 38. root@localhost : alitest 10:02:53> insert into test9 (c1,c2) values (3, “343 434″); Query OK, 1 row affected (0.00 sec) 5、备机查看自己生成 binlog: root@localhost : (none) 10:07:53> show binlog events in ‘mysql-bin.0000 85′ from 362605228; +——————+———–+————+———–+————-+————————— —–+ | Log_name | Pos | Event_type | Server_id | End_log_pos | Info | +——————+———–+————+———–+————-+————————— —–+ | mysql-bin.000085 | 362605228 | Query | 1 | 362605287 | BEGIN | | mysql-bin.000085 | 362605287 | Table_map | 1 | 362605337 | table_id: 27 (alitest.test9) | | mysql-bin.000085 | 362605337 | Write_rows | 1 | 362605378 | table_id: 27 flags: STMT_END_F | | mysql-bin.000085 | 362605378 | Xid | 1 | 362605405 | COMMIT /* xid= 23537907 */ | | mysql-bin.000085 | 362605405 | Query | 1 | 362605464 | BEGIN | | mysql-bin.000085 | 362605464 | Table_map | 1 | 362605514 | table_id: 27 (alitest.test9) | | mysql-bin.000085 | 362605514 | Write_rows | 1 | 362605555 | table_id:
  • 39. 27 flags: STMT_END_F | | mysql-bin.000085 | 362605555 | Xid | 1 | 362605582 | COMMIT /* xid= 23537917 */ | +——————+———–+————+———–+————-+————————— —–+ 8 rows in set (0.00 sec) 由上可以看到,备机虽然设置自己的 binlog_format 为 statement,binlog 日志中 记录从主机过来的 binlog 仍然为 row。写到这里,我突然想起,binlog_format 是否只是对在本机提交的 sql 才有效,于是我测试了以下两种情况: 1、主机为 statement,备机为 statement。 结果备机记录为 statement. 2、主机为 statement,备机为 row。结果备机记录为 statement. 也就是说,备机记录的从主机复制过来的 binlog 不随自己的 binlog_format 方式 改变,而是忠实的依照主机记录的方式来记录。 下面是简单的测试结果: 测试 1 主机为 statement,备机为 statement。 结果备机记录为 statement. 1、主机上设置 binlog_format 为 row 并插入一行 root@localhost : alitest 10:42:41> set binlog_format=statement; Query OK, 0 rows affected (0.00 sec) 2、备机上设置为 statement root@localhost : (none) 10:06:44> set global binlog_format=’statement’; Query OK, 0 rows affected (0.00 sec)
  • 40. root@localhost : (none) 10:07:04> set binlog_format=’statement’; Query OK, 0 rows affected (0.00 sec) root@localhost : (none) 10:07:11> show variables like ‘bin%’; +——————-+———–+ | Variable_name | Value | +——————-+———–+ | binlog_cache_size | 2097152 | | binlog_format | STATEMENT | +——————-+———–+ 2 rows in set (0.00 sec) 3、主机上插入一行 root@localhost : alitest 10:42:54> insert into test9 (c1,c2) values (4, “343 434″); Query OK, 1 row affected (0.00 sec) 4、备机上查看日志: root@localhost : (none) 10:35:48> show binlog events in ‘mysql-bin.0000 85′ from 362605228; +——————+———–+————+———–+————-+————————— ————————————+ | Log_name | Pos | Event_type | Server_id | End_log_pos | Info | +——————+———–+————+———–+————-+————————— ————————————+
  • 41. | mysql-bin.000085 | 362605228 | Query | 1 | 362605287 | BEGIN | | mysql-bin.000085 | 362605287 | Table_map | 1 | 362605337 | table_id: 27 (alitest.test9) | | mysql-bin.000085 | 362605337 | Write_rows | 1 | 362605378 | table_id: 27 flags: STMT_END_F | | mysql-bin.000085 | 362605378 | Xid | 1 | 362605405 | COMMIT /* xid= 23537907 */ | | mysql-bin.000085 | 362605405 | Query | 1 | 362605464 | BEGIN | | mysql-bin.000085 | 362605464 | Table_map | 1 | 362605514 | table_id: 27 (alitest.test9) | | mysql-bin.000085 | 362605514 | Write_rows | 1 | 362605555 | table_id: 27 flags: STMT_END_F | | mysql-bin.000085 | 362605555 | Xid | 1 | 362605582 | COMMIT /* xid= 23537917 */ | | mysql-bin.000085 | 362605582 | Query | 1 | 362605641 | BEGIN | | mysql-bin.000085 | 362605641 | Query | 1 | 362605753 | use `alitest`; insert into test9 (c1,c2) values (4, “343434″) | | mysql-bin.000085 | 362605753 | Xid | 1 | 362605780 | COMMIT /* xid= 23537922 */ | +——————+———–+————+———–+————-+————————— ————————————+ 11 rows in set (0.00 sec)
  • 42. 测试 2 主机为 statement,备机为 row。结果备机记录为 statement. 1、主机上设置 binlog_format 为 row 并插入一行 root@localhost : alitest 10:42:41> set binlog_format=statement; Query OK, 0 rows affected (0.00 sec) 2、备机上设置为 statement root@localhost : (none) 10:46:23> set binlog_format=’row’; Query OK, 0 rows affected (0.00 sec) root@localhost : (none) 10:46:28> set global binlog_format=’row’; Query OK, 0 rows affected (0.00 sec) root@localhost : (none) 10:46:37> show variables like ‘bin%’; +——————-+———+ | Variable_name | Value | +——————-+———+ | binlog_cache_size | 2097152 | | binlog_format | ROW | +——————-+———+ 2 rows in set (0.00 sec) 3、主机上插入一行 root@localhost : alitest 10:43:02> insert into test9 (c1,c2) values (5, “343 434″); Query OK, 1 row affected (0.00 sec)
  • 43. 4、备机上查看日志: root@localhost : (none) 10:46:43> show binlog events in ‘mysql-bin.0000 85′ from 362605228; +——————+———–+————+———–+————-+————————— ————————————+ | Log_name | Pos | Event_type | Server_id | End_log_pos | Info | +——————+———–+————+———–+————-+————————— ————————————+ | mysql-bin.000085 | 362605228 | Query | 1 | 362605287 | BEGIN | | mysql-bin.000085 | 362605287 | Table_map | 1 | 362605337 | table_id: 27 (alitest.test9) | | mysql-bin.000085 | 362605337 | Write_rows | 1 | 362605378 | table_id: 27 flags: STMT_END_F | | mysql-bin.000085 | 362605378 | Xid | 1 | 362605405 | COMMIT /* xid= 23537907 */ | | mysql-bin.000085 | 362605405 | Query | 1 | 362605464 | BEGIN | | mysql-bin.000085 | 362605464 | Table_map | 1 | 362605514 | table_id: 27 (alitest.test9) | | mysql-bin.000085 | 362605514 | Write_rows | 1 | 362605555 | table_id: 27 flags: STMT_END_F | | mysql-bin.000085 | 362605555 | Xid | 1 | 362605582 | COMMIT /* xid= 23537917 */ |
  • 44. | mysql-bin.000085 | 362605582 | Query | 1 | 362605641 | BEGIN | | mysql-bin.000085 | 362605641 | Query | 1 | 362605753 | use `alitest`; insert into test9 (c1,c2) values (4, “343434″) | | mysql-bin.000085 | 362605753 | Xid | 1 | 362605780 | COMMIT /* xid= 23537922 */ | | mysql-bin.000085 | 362605780 | Query | 1 | 362605839 | BEGIN | | mysql-bin.000085 | 362605839 | Query | 1 | 362605951 | use `alitest`; insert into test9 (c1,c2) values (5, “343434″) | | mysql-bin.000085 | 362605951 | Xid | 1 | 362605978 | COMMIT /* xid= 23537929 */ | +——————+———–+————+———–+————-+————————— ————————————+ 14 rows in set (0.00 sec) 附上 binlog_format 变量的介绍 –binlog-format={ROW|STATEMENT|MIXED} Version Introduced 5.1.5 Command-Line Format –binlog-format Config-File Format binlog-format Option Sets Variable Yes, binlog_format Variable Name binlog_format Variable Scope Both Dynamic Variable Yes
  • 45. Permitted Values (>= 5.1.5, <= 5.1.7) Type enumeration Default STATEMENT Valid Values ROW, STATEMENT Permitted Values (>= 5.1.8, <= 5.1.11) Type enumeration Default STATEMENT Valid Values ROW, STATEMENT, MIXED Permitted Values (>= 5.1.12, <= 5.1.28) Type enumeration Default MIXED Valid Values ROW, STATEMENT, MIXED Permitted Values (>= 5.1.29) Type enumeration Default STATEMENT Valid Values ROW, STATEMENT, MIXED Specify whether to use row-based, statement-based, or mixed replication (statement-based was the default prior to MySQL 5.1.12; in 5.1.12, the d efault was changed to mixed replication; in 5.1.29, the default was chan ged back to statement-based). See Section 16.1.2, “Replication Formats”. This option was added in MySQL 5.1.5. Important
  • 46. Setting the binary logging format without enabling binary logging prevents the MySQL server from starting. This is a known issue in MySQL 5.1 which is fixed in MySQL 5.5. (Bug#42928) MySQL Cluster. The default value for this option in all MySQL Cluster N DB 6.1, 6.2, 6.3, and later 6.x releases is MIXED. See Section 17.6.2, “MySQL Cluster Replication: Assumptions and General Requirements”, for more information. may your success. eshop MySQL 数据库主备机数据 xtrabackup 同步数据 今天需要再次用到 xtrabackup 来备份和恢复数据。找了半天终于把以前写的一 个文档找到了。保存在 blog 中。以防丢失。xtrabackup 更换为 xtrabackup-1.2 -22.rhel5.x86_64.rpm 也已经测试通过 一.迁移目的 目前 ITBU eshop MySQL 数据库有 64 台数据库服务器。两两互备作为双 mas ter 结构相互备份。但是由于应用方使用了 MySQL 的 uuid 函数,导致两两互备 的 mysql 服务器之间的数据不一致。这样两个数据库切换将导致用户数据的丢 失。 4 月底,应用方修改了 uuid 的问题,避免的新的数据不一致问题。接下来就只 有数据库中已有的数据不一致问题了。 MySQL 数据库,由于主备之间已经切换过多次,目前一部分数据已经无法找回。
  • 47. 跟应用沟通后,确定以目前主库的数据为准,将主库的数据同步到备库。备库的 数据备份到本机。保存一个月。 二.迁移要求 1、迁移过程中要求不影响应用,也就是说 eshop 数据库服务不能停止。 2、迁移完成后主备机数据保持一致。 三.迁移方案 这里由于管理维护的必要,我们假设主备机我们都是以 root 登录。xtrabackup 在 MySQL 中的权限为: root@127.0.0.1 : (none) 20:57:24> show grants for ‘xtrabackup’@'localhos t’G *************************** 1. row *************************** Grants for xtrabackup@localhost: GRANT SELECT, RELOAD, LOCK TAB LES, REPLICATION CLIENT ON *.* TO ‘xtrabackup’@'localhost’ IDENTIF IED BY PASSWORD ‘*BF9F4C1B8BC37C75BBF482C8037EC944555D371 A’ *************************** 2. row *************************** Grants for xtrabackup@localhost: GRANT INSERT, CREATE, DROP ON `mysql`.`ibbackup_binlog_marker` TO ‘xtrabackup’@'localhost’ 2 rows in set (0.00 sec) 3.1.备份/恢复工具选择 由于迁移过程中不能够停止数据库服务,数据库备份必须选择热备份。两种选择
  • 48. mysqldump 的逻辑热备以及 xtrabackup 的物理热备。为了恢复时间考虑,决定 采用 xtrabackup。 3.2.备机备份数据的选择 eshop 网店目前有两个存储,他们目前都在老聚园路。一个存储暂时没有上电, 另外一个存储空间所剩不多。目前每台 eshop 备机平均为 130G 的数据,一共 3 2 台,需要 4T 多的存储空间。而如果把 eshop 的 32 套备机数据放到存储上, 对存储空间和网络的消耗都是巨大的。 最终,我们选择在备机存储本机的老数据。而不做另外拷贝。 附件 eshop_backup_slave_data_20100422.sh 是备份备机数据的脚本 3.3.主备机数据拷贝方案选择 主机备份生成的数据大小约为 130G 左右,正常的话我们可以通过 scp 来拷贝, 但是 scp 拷贝要么需要手工输入密码,要么需要打通 ssh 隧道。如果用脚本来 操作的话,只能打通主备机之间的 ssh 隧道。另外 scp 不能够限速。数据拷贝 量大的话可能引起网络堵塞。最后,考虑采用 rsync 拷贝数据,它不仅能够限速。 并且可以在主机上启动 rsync –daemon,不用打通隧道(需要注意在拷贝完数据 以后停止 rsync 后台进程) 附件 eshop_master_rsync_daemon_local.sh 和 eshop_master_rsync_daemon_ remote.sh 是用于在 eshop 的 master 机器上添加 rsync daemon 的脚本。rsync d.conf 是拷贝到主机的 rsync 配置文件。 3.4.主机数据库备份方案 eshop 主备机每天 5: 的时候都会有一个计划任务判断当前主机是否是备机。 30 如果是备机则利用 xtrabackup innobackupex-1.5.1 版本为 0.7) ( 生成物理备份。
  • 49. 这里我们只需要简单修改一下备份脚本就可以在主机上也生成备份(注意主机备 机完之后需要把该脚本修改回来)。直接利用 innobackupex-1.5.1 –slave-info –no-timestamp –user=”${XTRA_BACKUP_U SER}” —password=”${XTRA_BACKUP_PASSWORD}” ${XTRA_BACKUP_D IR} 生成备份。这里需要注意 xtrabackup 用户的权限。 附件 eshop_master_backup_local.sh 是用于主机 xtrabackup 备份数据库的脚本。 backup_mysql.sh 是需要拷贝到主机上执行的脚本 3.5.备机数据恢复方案 主机数据拷贝到备机以后,由于是 xtrabackup 备份出来的,所以也要用它来恢 复。注意,在恢复备机数据库数据前需要将备机数据库 MySQL 先停掉。原有的 MySQL 数据目录需要 mv 到另外的位置。并根据需要新建好数据库需要的目录, 为备份数据拷贝到对应目录准备好环境。 xtrabackup 需要先生成 logfile,然后将产生的相关数据拷贝到具体的数据文件目 录中。需要需要执行两次。 ■第一次利用: innobackupex-1.5.1 —apply-log /home/mysql/fs3/master_bak_data/eshop_al smy_eshop13b_2010_04_27/ 应用备份期间变化的数据和生成 ib_logfile。这里需要注意将该命令输出的结果 记下来。后面主备机双 maser 复制环境的搭建需要利用到这里的”Last MySQL binlog file position”信息 ■第二次利用:
  • 50. innobackupex-1.5.1 —copy-back /home/mysql/fs3/master_bak_data/eshop_a lsmy_eshop13b_2010_04_27/ 将生成好的数据拷贝到/etc/my.cnf 指定的 datadir 和 innodb_data_home_dir 等 对应目录中。 3.6.备机数据库启动和清理 备机数据库数据恢复以后,需要将数据库的目录 owner 修改一下。chown -R mysql:mysql ${MYSQL_DATA_DIR}。否则 MySQL 启动会出错。 启动 MySQL 数据库,正常的话启动是没有问题的。如果出现问题,需要查看 M ySQL 的错误日志,并根据不同的错误类型处理。 启动成功以后,MySQL 数据库里面的数据都是主机的数据。我们需要根据不同 的情况对它进行修改。(这里最好 SET SQL_LOG_BIN=0 避免把一些数据记录 到 binlog 中,从而复制到主机去了。)eshop 这边因为复制帐号是 replicator@’${S LAVE}’基于备机 ip 地址的,所以需要修改为基于主机的复制帐号。这样备机到 主机的复制才能搭建起来。 root@127.0.0.1 : (none) 16:13:06> show grants for replicator@’172.18.94. 25′; +—————————————————————————————————— ———————————————————————–+ | Grants for replicator@172.18.94.25 | +—————————————————————————————————— ———————————————————————–+ | GRANT SELECT, RELOAD, SUPER, REPLICATION SLAVE, REPLICAT
  • 51. ION CLIENT ON *.* TO ‘replicator’@’172.18.94.25′ IDENTIFIED BY PAS SWORD ‘*3836CCEA58805DB4CE7093BF6170F7A6027CDD86′ | +—————————————————————————————————— ———————————————————————–+ 1 row in set (0.00 sec) root@127.0.0.1 : (none) 16:14:20> GRANT SELECT, RELOAD, SUPER, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO ‘replicator’@’1 72.18.94.26′ IDENTIFIED BY ‘xlia9810pal’; Query OK, 0 rows affected (0.00 sec) root@127.0.0.1 : (none) 21:21:02> drop user replicator@’172.18.94.25′; Query OK, 0 rows affected (0.03 sec) root@127.0.0.1 : (none) 16:14:44> flush privileges; Query OK, 0 rows affected (0.00 sec) 附件 eshop_slave_sync_master_data.sh 是用来恢复备机数据库和清理数据的 脚本。 3.7.主备机双 master 复制环境搭建 3.7.1.搭建备机到主机的复制 由于备机是从主机导入的数据,并且备机没有应用访问,binlog 没有变化。我们 首先搭建备机到主机的复制。首先确定备机的 binlog 位置。利用 show master status 可以查到。 root@127.0.0.1 : (none) 21:37:38> show master status; +——————+———–+————–+——————+
  • 52. | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | +——————+———–+————–+——————+ | mysql-bin.000001 | 98 | | | +——————+———–+————–+——————+ 1 row in set (0.00 sec) 如无意外,都应该是第一个文件的最开始位置。 修改主机的 master 位置: Stop slave; CHANGE MASTER TO MASTER_HOST=’172.18.94.25′, MASTER_USER=’ replicator’, MASTER_PASSWORD=’xlia9810pal’, MASTER_LOG_FILE=’mys ql-bin.000001′, MASTER_LOG_POS=98; Start slave; 检查是否复制搭建成功: Show slave statusG 如果 Slave_IO_Running,Slave_SQL_Running 其中一个为 No。需要检查 MyS QL 的错误日志。一般可能有两种错误: ■备机的授权有问题 ■备机的 binlog 位置有问题 附件 eshop_master_change_master.sh 为配置备机到主机的复制环境的脚本。 3.7.2.搭建主机到备机的复制 保证备机到主机的复制正确以后,我们就可以搭建主机到备机的复制了。 备机从主机的哪个位置开始复制很重要,这个位置是从 备机数据恢复方案 中得
  • 53. 到的。在 apply-log 的时候,有一行文字很重要: InnoDB: Last MySQL binlog file position 0 651339984, file name ./mysql- bin.001604 这里记录了主机的复制开始文件和开始位置(这里就是 mysql-bin.001604,6513 39984)。那么我们搭建主机到备机的复制就很简单了: Stop slave; CHANGE MASTER TO MASTER_HOST=’172.18.94.26′, MASTER_USER=’ replicator’, MASTER_PASSWORD=’xlia9810pal’, MASTER_LOG_FILE=’mys ql-bin.001604′, MASTER_LOG_POS=651339984; Start slave; 检查是否复制搭建成功: Show slave statusG 如果 Slave_IO_Running,Slave_SQL_Running 其中一个为 No。需要检查 MyS QL 的错误日志。 这里由于备份的时间已经经过了这么长的时间,主机到备机的复制肯定有延迟, 静静的等待主备机的复制就可以了。 附件 eshop_slave_change_master.sh 为配置主机到备机的复制环境的脚本。 四.附件介绍 附件中的各个脚本介绍如下: ■附件 eshop_backup_slave_data_20100422.sh 是备份备机数据的脚本 ■附件 eshop_master_backup_local.sh 是用于主机 xtrabackup 备份数据库的脚 本。backup_mysql.sh 是需要拷贝到主机上执行的脚本
  • 54. ■附件 eshop_master_rsync_daemon_local.sh 和 eshop_master_rsync_daemon _remote.sh 是用于在 eshop 的 master 机器上添加 rsync daemon 的脚本。rsy ncd.conf 是拷贝到主机的 rsync 配置文件。 ■附件 eshop_slave_sync_master_data.sh 是用来恢复备机数据库和清理数据的 脚本。 ■附件 eshop_master_change_master.sh 为配置备机到主机的复制环境的脚本。 ■附件 eshop_slave_change_master.sh 为配置主机到备机的复制环境的脚本。 may you success. MySQL 数据库机器搬迁 checklist itbu 的数据库机器从老机房搬迁到新机房的项目已经接近尾声了,这次的搬迁是 我经历的一个比较大的项目,虽然整体的调控不是我主导的,但是 MySQL 数据 库的搬迁都是我在主导进行,期间出现了一些问题,还好没有出现非常重大的错 误。从中相信搬迁的大部分同学都成长了很多。我自己感觉我学到了很多,也成 长了很多,虽然加班加点的,累的够呛,还是很值得的。为了避免以后大家再走 弯路,也避免自己忘记搬迁的重要事项,特别记录下来,做为一个搬迁工程的 c hecklist。方便大家搬迁过程中检查,不要遗漏了一些东西。 1、确认方案。尽早和应用方沟通,确认采用平滑搬迁或者停机搬迁的方案。 平滑搬迁是指 MySQL 数据库停止备机,然后搬迁过去,在新环境搭好以后,配 置 MySQL 的双 master 复制,调试通过,应用服务正常。然后直接刷 dns(或者 其他方式)使应用的真实用户访问新的服务。 停机搬迁的方式是指,应用发布停机公告,在适当的时机,将应用服务器和数据
  • 55. 库服务器等搬迁到新机房,部署上线。 上面的两种方案,第一种风险较小,对用户影响也非常小。万一刷过去以后,真 实用户访问有问题,还可以切回来。第二种风险比较大,甚至可能出现有些配置 修改修改不及时,造成停机时间已经过了,应用无法访问的问题。 2、确认机架位置。正常的话,sa 负责人会将搬迁的机器列表和机柜,机架位置 发给大家一份。以方便现场工程师以及对应人员确认搬迁的就是这批机器。我们 需要注意一下,并提醒现场工程师机柜和机架号。eshop 搬迁的时候,我们的一 个现场工程师就看错了机柜号,拔掉了一台正在提供应用服务的机器,还好立刻 意识到了,大家一起快速修复了这个问题。 3、确认搬迁的机器 IP 和搬迁到新机房的机器 IP。老机房需要搬迁的机器 IP 在 上一步就需要确认好了,新机房的机器 IP 需要跟 sa 和网络部门的同事沟通,我 们提供具体的分配策略,网段划分,新老机器 IP 对应关系。 4、确认时间。及早跟各应用方沟通搬迁时间,如果有变化,及时相互通知。 5、搬迁前准备工作。eshop 这边搬迁前需要重新同步主机数据到备机。其他应 用需要注意主备机的复制延迟,尽量保证复制延迟较低。 6、通知所有相关人员。包括数据仓库,应用开发人员,网络,sa。告知他们新 的数据库 IP 地址和连接方式。另外还需要检查 MySQL 里面的所有账户,看这 些用户是否都通知到了。pm 就没有及时通知数据仓库人员,导致第二天他们取 不到数据的问题。 7、修改搬迁以后的配置。包括 MySQL 账户的修改,heartbeat 的修改,cobar 的修改,脚本的修改,脚本的配置文件修改,带外登录等。 a)MySQL 帐号的修改。检查 MySQL 的账户,看是否还有依赖于老的 IP 地址段
  • 56. 的账户。比如:eshop 的复制帐号 replicator@172.18.%搬迁到新的机房就肯定 需要修改掉。 b)heartbeat 的修改;cobar 的修改。机器搬迁到新的机房,需要修改 heartbeat 和 cobar 的配置。这样才能让应用访问新的 VIP 地址。 c)脚本的修改。目前 MySQL 的管理有许多脚本,这些脚本有些是对 IP 有依赖 关系的,需要及时修改掉。当然,最好修改掉这样的脚本,去掉对 IP 的依赖。 d)脚本配置文件的修改。有些脚本通过读取配置文件来依赖 IP,同样需要修改配 置文件。如果可能的话,修改脚本,去掉对 IP 的依赖。 e)带外登录。带外登录地址随着机器 IP 的更换也会变化。搬迁过去以后,需要 再次验证一下带外登录的方式是否还正常。 该 checklist 可能还不是很完全,需要补充和优化。 may you success.