前言
通过对《【Mysql源码剖析】基于行的复制实现之“主从关系建设”》理解了主从复制的一些原理,本章内容会深刻对binlog、relaylog做解说。并对流程做深刻理解。
在开始交接之前,咱们带着几个问题切入:
- 如何查看binlog和relaylog事件?
- relaylog是如何写入?
- binlog是如何同步?
- binlog格局如何解析?
1.如何查看binlog和relaylog
在mysql中能够通过如下命令能够查看都有哪些binlog日志,如图1-1:
mysql> show binary logs;
<center>图1-1 查看binlog日志</center>
- Log_name 日志文件名称。
- File_size 文件大小。
- Encrypted 是否加密“No”代表未加密。
在查看binlog时、主有本人的binlog、从也有本人的binlog。
如果要查看最新的binlog,能够通过如下命令查看,如图1-2
mysql> show master status;
<center>图1-2 查看最新binlog</center>
通过如下命令能够查看以后binlog事件,如图1-3:
mysql> show binlog events;
<center>图1-3 查看binlog事件</center>
- Log_name 日志文件名称。
- Pos 代表文件开始的地位。
- Event_type 代表事件的类型。
- Server_id 是创立事件的服务器ID。
- End_log_pos 代表事件在文件中的完结地位,以下面为例,第一次查问的完结地位是125,第二次insert之后文件的开始地位就是从125开始。
- Info 代表事件信息,是一段可读的文本内容。
除了查看binlog事件以外,咱们还能够查看relaylog的事件如图1-4所示,查看事件命令如下:
mysql> show relaylog events;
<center>图1-4 查看relaylog事件</center>
relaylog事件的参数含意和binlog的统一,可参考binlog。
除了查看relaylog事件外,还能够查看relaylog参数,如图1-5所示。通过如下命令能够查看:
mysql> show variables like '%relay%';
<center>图1-5 relaylog参数 </center>
- max_relay_log_size:标记relay log 容许的最大值,如果该值为0,则默认值为max_binlog_size(1G);如果不为0,则max_relay_log_size则为最大的relay_log文件大小;
- relay_log:定义relay_log的地位和名称,如果值为空,则默认地位在数据文件的目录,文件名为host_name-relay-bin.nnnnnn;
- relay_log_index:同relay_log,定义relay_log的地位和名称;
- relay_log_info_file:设置relay-log.info的地位和名称(relay-log.info记录MASTER的binary_log的复原地位和relay_log的地位)
- relay_log_purge:是否主动清空不再须要中继日志时。默认值为1(启用)。
- relay_log_recovery:当slave从库宕机后,如果relay-log损坏了,导致一部分中继日志没有解决,则主动放弃所有未执行的relay-log,并且从新从master上获取日志,这样就保障了relay-log的完整性。默认状况下该性能是敞开的,将relay_log_recovery的值设置为 1时,可在slave从库上开启该性能,倡议开启。
- relay_log_space_limit:避免中继日志写满磁盘,这里设置中继日志最大限额。但此设置存在主库解体,从库中继日志不全的状况,不到万不得已,不举荐应用;
- sync_relay_log:这个参数和sync_binlog是一样的,当设置为1时,slave的I/O线程每次接管到master发送过去的binlog日志都要写入零碎缓冲区,而后刷入relay log中继日志里,这样是最平安的,因为在解体的时候,你最多会失落一个事务,但会造成磁盘的大量I/O。当设置为0时,并不是马上就刷入中继日志里,而是由操作系统决定何时来写入,尽管安全性升高了,但缩小了大量的磁盘I/O操作。这个值默认是0,可动静批改,倡议采纳默认值。
- sync_relay_log_info:这个参数和sync_relay_log参数一样,当设置为1时,slave的I/O线程每次接管到master发送过去的binlog日志都要写入零碎缓冲区,而后刷入relay-log.info里,这样是最平安的,因为在解体的时候,你最多会失落一个事务,但会造成磁盘的大量I/O。当设置为0时,并不是马上就刷入relay-log.info里,而是由操作系统决定何时来写入,尽管安全性升高了,但缩小了大量的磁盘I/O操作。这个值默认是0,可动静批改,倡议采纳默认值。
2.slave_relay_log_info与slave_master_info
slave_relay_log_info和slave_master_info别离为relaylog-info和master-info信息。relaylog-info和master-info信息能够应用FILE或这TABLE存储。relaylog-info有三种模式,分为为FILE、TABLE、DUMMY。
<center>图2-1 查看relaylog-info和master-info </center>
通过relay_log_info_repository和master_info_repository能够得悉relaylog-info和master-info是用什么模式存储,如图2-1所示。
3.binlog文件格式解析
想理解binlog的格局,能够通过十六进制的模式去查看,如图3-1所示。
<center>图3-1 binlog十六进制格局 </center>
查看十六进制格局能够应用hexdump命令,命令格局如下:
#hexdump -C mysql-bin.000024
通过hexdump失去如下内容:
00000000 fe 62 69 6e 41 93 92 5f 0f 0a 00 00 00 79 00 00 |.binA.._.....y..|00000010 00 7d 00 00 00 01 00 04 00 38 2e 30 2e 32 30 2d |.}.......8.0.20-|00000020 64 65 62 75 67 00 00 00 00 00 00 00 00 00 00 00 |debug...........|00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|00000040 00 00 00 00 00 00 00 00 00 00 00 41 93 92 5f 13 |...........A.._.|00000050 00 0d 00 08 00 00 00 00 04 00 04 00 00 00 61 00 |..............a.|00000060 04 1a 08 00 00 00 08 08 08 02 00 00 00 0a 0a 0a |................|00000070 2a 2a 00 12 34 00 0a 28 01 3a 50 74 ce 41 93 92 |**..4..(.:Pt.A..|00000080 5f 23 0a 00 00 00 1f 00 00 00 9c 00 00 00 80 00 |_#..............|00000090 00 00 00 00 00 00 00 00 56 4b 92 11 |........VK..|
也能够mysqlbinlog命令失去binlog信息,如图3-2。命令格局如下:
#mysqlbinlog --base64-output='decode-rows' mysql-bin.000024
<center>图3-2 binlog信息 </center>
能够通过一张图解析下图3-2中内容,从十六进制格局中解析一下binlog格局,如图3-3所示。
<center>图3-3 binlog格局解析 </center>
能够关注下如下内容和图3-3还有图3-2中关系:
fe 62 69 6e 对应魔术头 0xFE 'bin'41 93 92 5f timestamp工夫戳1603441473,对应工夫"2020/10/23 16:24:33"0f event_type对应十进制16对应XID_EVENT0a 00 00 00 server_id 0x0a对应十进制1079 00 00 00 event_size 对应十进制1217d 00 00 00 log_pos 对应十进制12501 00 flags 状态04 00 binlog version对应版本438 2e 30 2e 32 30 2d 64 65 62 75 67 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 mysql-server version 对应版本号8.0.20-debug41 93 92 5f create-timestamp 创立工夫戳,对应1603441473,对应工夫2020/10/23 16:24:33 13 Event-header-length对应1900 0d 00 08 00 00 00 00 04 00 04 00 00 00 61 00 04 1a 08 00 00 00 08 08 08 02 00 00 00 0a 0a 0a 2a 2a 00 12 34 00 0a 28 01 Event-type-header-length 对应41个event类型3a 50 74 ce CRC32 对应 0xce74503a 41 93 92 5f timestamp对应工夫2020/10/23 16:24:3323 event_type对应35对应PREVIOUS_GTIDS_LOG_EVENT0a 00 00 00 server_id对应服务ID为101f 00 00 00 event_size对应大小319c 00 00 00 log_pos对应15680 00 flags 状态00 00 00 00 00 00 00 00 Next pos56 4b 92 11 CRC 0x11924b56
Event-type-header-length对41为什么是41?能够看一下源码,对应libbinlogevents/include/binlog_event.h
enum Log_event_type { /** Every time you add a type, you have to - Assign it a number explicitly. Otherwise it will cause trouble if a event type before is deprecated and removed directly from the enum. - Fix Format_description_event::Format_description_event(). */ UNKNOWN_EVENT = 0, /* 自MySQL8.0.2以来已弃用。它只是一个占位符,不应该在其余中央应用。 */ START_EVENT_V3 = 1, //起始事件是二进制日志版本1至3的二进制日志的第一个事件。 QUERY_EVENT = 2, //查问事件用于向binlog发送文本查问。 STOP_EVENT = 3, //进行事件 ROTATE_EVENT = 4, //将Rotate事件作为最初一个事件增加到binlog中,以通知读者接下来要申请的binlog。 INTVAR_EVENT = 5, //基于整数的会话变量 SLAVE_EVENT = 7, //从事件 APPEND_BLOCK_EVENT = 9, //将块数据追加到文件 DELETE_FILE_EVENT = 11, //删除文件事件 RAND_EVENT = 13, //RAND() 函数的 外部状态。 USER_VAR_EVENT = 14, //用户变量 FORMAT_DESCRIPTION_EVENT = 15, //格局形容事件是binlog版本4的binlog的第一个事件。它形容了其余事件的布局形式。 XID_EVENT = 16, //2PC的事务处理ID,在须要时写入 COMMIT。 BEGIN_LOAD_QUERY_EVENT = 17, //截断文件并设置块数据 EXECUTE_LOAD_QUERY_EVENT = 18, TABLE_MAP_EVENT = 19, //基于行的复制中 应用的第一个事件 申明如何定义将要更改的表。 /** V1事件号从5.1.16始终应用到mysql-5.6。 */ WRITE_ROWS_EVENT_V1 = 23, UPDATE_ROWS_EVENT_V1 = 24, DELETE_ROWS_EVENT_V1 = 25, /** 主产生了不寻常的事 */ INCIDENT_EVENT = 26, /** 主机在闲暇工夫发送的心跳事件确保主机联机状态为从机 */ HEARTBEAT_LOG_EVENT = 27, /** 在某些状况下,有必要把不可漠视的货色送过来数据到从机: 在这种状况下,从机能够解决的数据是用于解决它的代码, 但如果不是,则能够疏忽它辨识。 */ IGNORABLE_LOG_EVENT = 28, ROWS_QUERY_LOG_EVENT = 29, //ROWS_EVENT的查问 /** 行事件的版本2 */ WRITE_ROWS_EVENT = 30, UPDATE_ROWS_EVENT = 31, DELETE_ROWS_EVENT = 32, GTID_LOG_EVENT = 33, //gtid ANONYMOUS_GTID_LOG_EVENT = 34, //匿名gtid PREVIOUS_GTIDS_LOG_EVENT = 35, //上一个gtid TRANSACTION_CONTEXT_EVENT = 36, //事务上下文事件 VIEW_CHANGE_EVENT = 37, /* 筹备了相似于Xid的XA事务终端事件 */ XA_PREPARE_LOG_EVENT = 38, /** UPDATE_ROWS_事件的扩大,容许依据到 binlog_row_value_options配置中。 */ PARTIAL_UPDATE_ROWS_EVENT = 39, TRANSACTION_PAYLOAD_EVENT = 40, ENUM_END_EVENT /* 起点标记 */};
在mysql5中只有39个event-type。
基于行复制实现的的事件为TABLE_MAP_EVENT、ROWS_EVENT、ROWS_QUERY_EVENT三大类。
能够看官网: https://dev.mysql.com/doc/internals/en/row-based-replication.html
4.Mysql主从同步源码解析
<center>图4-1 主从同步 </center>
在主从同步过程中、咱们能够把同步过程分为10个阶段,如图4-1。
通过对之前的《【Mysql源码剖析】基于行的复制实现之“主从关系建设”》的学习,得悉dump线程会接管到COM_BINLOG_DUMP指令后触发。然而从服务器申请二进制日志流中分为几个参数:
1字节 [12] COM_BINLOG_DUMP4字节 binlog-pos binlog对应到地位2字节 flags 状态4字节 server-id 服务IDstring[EOF] binlog-filename 文件名称
当接管到COM_BINLOG_DUMP指令后会触发调用com_binlog_dump办法,com_binlog_dump办法中会调用mysql_binlog_send函数用于发送binlog,如图图4-2所示。
<center>图4-2 发送binlog </center>
void mysql_binlog_send(THD *thd, char *log_ident, my_off_t pos, Gtid_set *slave_gtid_executed, uint32 flags) { //log_ident发送binlog名称 //pos 以后行号,后续会作为开始行号 Binlog_sender sender(thd, log_ident, pos, slave_gtid_executed, flags); sender.run();}
在调用mysql_binlog_send办法后,会调用Binlog_sender::send_events办法发送事件,在发送事件时会调用Binlog_sender::check_event_type办法对事件进行检测,如图4-3。
<center>图4-3 事件检测办法 </center>
用Binlog_sender::check_event_type检测办法原型如下:
bool Binlog_sender::check_event_type(Log_event_type type, const char *log_file, my_off_t log_pos) { if (type == binary_log::ANONYMOUS_GTID_LOG_EVENT) { /* 通常状况下,当主动地位被启用,因为主设施和从设施 如果主机未应用GTID_MODE=ON,则回绝连贯。 然而,如果主机在连贯后更改了GTID_模式初始化, 或者如果隶属申请复制呈现在最初一个匿名事件之前 的事务,则这可能产生。而后生成此谬误以阻止发送 到隶属服务器的匿名事务。 */ if (m_using_gtid_protocol) { //判断是否应用gtid DBUG_EXECUTE_IF("skip_sender_anon_autoposition_error", { return false; };); char buf[MYSQL_ERRMSG_SIZE]; snprintf(buf, MYSQL_ERRMSG_SIZE, ER_THD(m_thd, ER_CANT_REPLICATE_ANONYMOUS_WITH_AUTO_POSITION), log_file, log_pos); set_fatal_error(buf); return true; } /* 通常状况下,当master有GTID_MODE=ON,因为当GTID_MODE=ON。然而,如果主控形态产生更改,则可能会产生这种状况当隶属服务器尚未复制所有匿名交互。 */ else if (get_gtid_mode_from_copy(GTID_MODE_LOCK_NONE) == GTID_MODE_ON) { char buf[MYSQL_ERRMSG_SIZE]; snprintf(buf, MYSQL_ERRMSG_SIZE, ER_THD(m_thd, ER_CANT_REPLICATE_ANONYMOUS_WITH_GTID_MODE_ON), log_file, log_pos); set_fatal_error(buf); return true; } } else if (type == binary_log::GTID_LOG_EVENT) { /* 通常状况下,当主服务器有GTID_MODE=OFF,因为当 GTID_MODE=OFF。然而,如果主控形态产生更改,则 可能会产生这种状况当从机尚未复制所有GTID时,GTID_MODE敞开交互。 */ if (get_gtid_mode_from_copy(GTID_MODE_LOCK_NONE) == GTID_MODE_OFF) { char buf[MYSQL_ERRMSG_SIZE]; snprintf(buf, MYSQL_ERRMSG_SIZE, ER_THD(m_thd, ER_CANT_REPLICATE_GTID_WITH_GTID_MODE_OFF), log_file, log_pos); set_fatal_error(buf); return true; } } return false;}
<center>图4-4 Log_event::write </center>
在写入binlog时会调用Log_event::write进行写入,如图4-4。Log_event::write在log_event.h头文件中。
写入master-info信息调度如下:
(lldb) bt* thread #36, stop reason = breakpoint 8.1 * frame #0: 0x0000000109a10c80 mysqld`Master_info::write_info(this=0x00007fe416209200, to=0x00007fe418204230) at rpl_mi.cc:666 frame #1: 0x0000000109a0e9ba mysqld`Master_info::flush_info(this=0x00007fe416209200, force=false) at rpl_mi.cc:380 frame #2: 0x0000000109a987eb mysqld`flush_master_info(mi=0x00007fe416209200, force=false, need_lock=false, do_flush_relay_log=false) at rpl_slave.cc:1424 frame #3: 0x0000000109ab4565 mysqld`queue_event(mi=0x00007fe416209200, buf="\a�\x8e_\"\n", event_len=77, do_flush_mi=true) at rpl_slave.cc:7956 frame #4: 0x0000000109a9e063 mysqld`::handle_slave_io(arg=0x00007fe416209200) at rpl_slave.cc:5462 frame #5: 0x000000010ac5a6c5 mysqld`pfs_spawn_thread(arg=0x00007fe4175b28b0) at pfs.cc:2854 frame #6: 0x00007fff7e063305 libsystem_pthread.dylib`_pthread_body + 126 frame #7: 0x00007fff7e06626f libsystem_pthread.dylib`_pthread_start + 70 frame #8: 0x00007fff7e062415 libsystem_pthread.dylib`thread_start + 13
写入Relay-log-info信息调度如下:
(lldb) bt* thread #41, stop reason = breakpoint 2.1 * frame #0: 0x000000010c36f990 mysqld`Relay_log_info::write_info(this=0x00007f8a53de3400, to=0x00007f8a5512af50) at rpl_rli.cc:2200 frame #1: 0x000000010c3677e1 mysqld`Relay_log_info::flush_info(this=0x00007f8a53de3400, force=true) at rpl_rli.cc:1905 frame #2: 0x000000010c371fe8 mysqld`Relay_log_info::commit_positions(this=0x00007f8a53de3400) at rpl_rli.cc:2730 frame #3: 0x000000010a4b72eb mysqld`Relay_log_info::pre_commit(this=0x00007f8a53de3400) at rpl_rli.h:1986 frame #4: 0x000000010a4b6655 mysqld`ha_commit_trans(thd=0x00007f8a5605fa00, all=true, ignore_global_read_lock=false) at handler.cc:1656 frame #5: 0x000000010aefeab4 mysqld`trans_commit(thd=0x00007f8a5605fa00, ignore_global_read_lock=false) at transaction.cc:241 frame #6: 0x000000010ab202d3 mysqld`mysql_create_db(thd=0x00007f8a5605fa00, db="t136", create_info=0x000070000d37a898) at sql_db.cc:405 frame #7: 0x000000010ac6f026 mysqld`mysql_execute_command(thd=0x00007f8a5605fa00, first_level=true) at sql_parse.cc:3660 frame #8: 0x000000010ac676ad mysqld`mysql_parse(thd=0x00007f8a5605fa00, parser_state=0x000070000d37e028) at sql_parse.cc:5306 frame #9: 0x000000010c24dc6c mysqld`Query_log_event::do_apply_event(this=0x00007f8a520d0620, rli=0x00007f8a53de3400, query_arg="create database t136", q_len_arg=20) at log_event.cc:4804 frame #10: 0x000000010c24af13 mysqld`Query_log_event::do_apply_event(this=0x00007f8a520d0620, rli=0x00007f8a53de3400) at log_event.cc:4415 frame #11: 0x000000010c2458fe mysqld`Log_event::apply_event(this=0x00007f8a520d0620, rli=0x00007f8a53de3400) at log_event.cc:3241 frame #12: 0x000000010c4047e7 mysqld`apply_event_and_update_pos(ptr_ev=0x000070000d37f860, thd=0x00007f8a5605fa00, rli=0x00007f8a53de3400) at rpl_slave.cc:4350 frame #13: 0x000000010c3f2032 mysqld`exec_relay_log_event(thd=0x00007f8a5605fa00, rli=0x00007f8a53de3400, applier_reader=0x000070000d3803d8, in=0x00007f8a520d0620) at rpl_slave.cc:4867 frame #14: 0x000000010c3db543 mysqld`::handle_slave_sql(arg=0x00007f8a52b43200) at rpl_slave.cc:7022 frame #15: 0x000000010d5936c5 mysqld`pfs_spawn_thread(arg=0x00007f8a554bcbf0) at pfs.cc:2854 frame #16: 0x00007fff7e063305 libsystem_pthread.dylib`_pthread_body + 126 frame #17: 0x00007fff7e06626f libsystem_pthread.dylib`_pthread_start + 70 frame #18: 0x00007fff7e062415 libsystem_pthread.dylib`thread_start + 13
Master断点
#发送binlog(lldb)b mysql_binlog_send#发送binlog包(lldb)b Binlog_sender::send_packet#循环事件(lldb)b Binlog_sender::run#发送binlog(lldb)b Binlog_sender::send_binlog#发送心跳包事件(lldb)b Binlog_sender::send_heartbeat_event
Slave 断点
#读取头文件事件b Binlog_event_data_istream::read_event_header#填充工夫数据b Binlog_event_data_istream::fill_event_data#读取下一个事件b Rpl_applier_reader::read_next_event#slave sal解决b handle_slave_sql#写入relay-log-infob Relay_log_info::write_info#relay_log事件b exec_relay_log_event #写入binlog或者relaylog断点b handler::ha_write_rowb MYSQL_BIN_LOG::write_eventb binlog_cache_data::write_event#写入relaylog之后操作MYSQL_BIN_LOG::after_write_to_relay_log#写入binlogb MYSQL_BIN_LOG::write_bufferb MYSQL_BIN_LOG::Binlog_ofile::write
总结
- 通过“show binary logs;” 能够查看binlog日志。
- 通过“show relaylog events;”能够查看relaylog事件。
- 在mysql8.0.20中有41个事件,mysql5.1中有39个。
- 基于行复制实现的的事件为TABLE_MAP_EVENT、ROWS_EVENT、ROWS_QUERY_EVENT三大类。
- relaylog-info和binlog-info能够应用file或者table模式记录。
参考献文
复制协定
https://dev.mysql.com/doc/internals/en/replication-protocol.html
事件含意
https://dev.mysql.com/doc/internals/en/event-meanings.html
基于行的二进制日志记录
[https://dev.mysql.com/doc/int...
](https://dev.mysql.com/doc/int...