初探 MySQL 的 Binlog

原文地址:https://xcoder.in/2015/08/10/mysql-binlog-try/html

  花瓣网的搜索架构须要重构,尤为是在索引创建或者更新层面。mysql

  目前的一个架构致使的结果就是时间越久,数据本体与搜索引擎索引中的数据越不一样步,相差甚大。linux

  新的一个架构打算从 MySQL 的 Binlog 中读取数据更新、删除、新增等历史记录,并把相应信息提取出来丢到队列中慢慢去同步。git

  因此我就在这里小小去了解一下 Binlog。github

准备工做

什么是 Binlog

  MySQL Server 有四种类型的日志——Error Log、General Query Log、Binary Log 和 Slow Query Log。sql

  第一个是错误日志,记录 mysqld 的一些错误。第二个是通常查询日志,记录 mysqld 正在作的事情,好比客户端的链接和断开、来自客户端每条 Sql Statement 记录信息;若是你想准确知道客户端到底传了什么瞎 [哔哔] 玩意儿给服务端,这个日志就很是管用了,不过它很是影响性能。第四个是慢查询日志,记录一些查询比较慢的 SQL 语句——这种日志很是经常使用,主要是给开发者调优用的。数据库

  剩下的第三种就是 Binlog 了,包含了一些事件,这些事件描述了数据库的改动,如建表、数据改动等,也包括一些潜在改动,好比 DELETE FROM ran WHERE bing = luan,然而一条数据都没被删掉的这种状况。除非使用 Row-based logging,不然会包含全部改动数据的 SQL Statement。数组

  那么 Binlog 就有了两个重要的用途——复制和恢复。好比主从表的复制,和备份恢复什么的。xcode

启用 Binlog

  一般状况 MySQL 是默认关闭 Binlog 的,因此你得配置一下以启用它。服务器

  启用的过程就是修改配置文件 my.cnf 了。

  至于 my.cnf 位置请自行寻找。例如经过 OSX 的 brew 安装的 mysql 默认配置目录一般在

/usr/local/Cellar/mysql/$VERSION/support-files/my-default.cnf

  这个时候须要将它拷贝到 /etc/my.cnf 下面。

详见 <StackOverflow - MySQL 'my.cnf' location?>。

  紧接着配置 log-binlog-bin-index 的值,若是没有则自行加上去。

inilog-bin=master-bin
log-bin-index=master-bin.index

  这里的 log-bin 是指之后生成各 Binlog 文件的前缀,好比上述使用 master-bin,那么文件就将会是 master-bin.000001master-bin.000002 等。而这里的 log-bin-index 则指 binlog index 文件的名称,这里咱们设置为 master-bin.index

  若是上述工做作完以后重启 MySQL 服务,你能够进入你的 MySQL CLI 验证一下是否真的启用了。

sh$ mysql -u $USERNAME ...

  而后在终端里面输入下面一句 SQL 语句:

sqlSHOW VARIABLES LIKE '%log_bin%';

  若是结果里面出来这样相似的话就表示成功了:

sh+---------------------------------+---------------------------------------+
| Variable_name                   | Value                                 |
+---------------------------------+---------------------------------------+
| log_bin                         | ON                                    |
| log_bin_basename                | /usr/local/var/mysql/master-bin       |
| log_bin_index                   | /usr/local/var/mysql/master-bin.index |
| log_bin_trust_function_creators | OFF                                   |
| log_bin_use_v1_row_events       | OFF                                   |
| sql_log_bin                     | ON                                    |
+---------------------------------+---------------------------------------+
6 rows in set (0.00 sec)

  更多的一些相关配置能够参考这篇《MySQL 的 binary log 初探》。

随便玩玩

  而后你就能够随便去执行一些数据变更的 SQL 语句了。当你执行了一堆语句以后就能够看到你的 Binlog 里面有内容了。

  如上表所示,log_bin_basename 的值是 /usr/local/var/mysql/master-bin 就是 Binlog 的基础文件名了。

  那咱们进去看,好比个人这边就有这么几个文件:

Binlog 文件

  很容易发现,里面有 master-bin.indexmaster-bin.000001 两个文件,这两个文件在上文中有提到过了。

  咱们打开那个 master-bin.index 文件,会发现这个索引文件就是一个普通的文本文件,而后列举了各 binlog 的文件名。而 master-bin.000001 文件就是一堆乱码了——毕竟人家是二进制文件。

结构解析

索引文件

  索引文件就是上文中的 master-bin.index 文件,是一个普通的文本文件,以换行为间隔,一行一个文件名。好比它多是:

master-bin.000001
master-bin.000002
master-bin.000003

  而后对应的每行文件就是一个 Binlog 实体文件了。

Binlog 文件

  Binlog 的文件结构大体由以下几个方面组成。

文件头

  文件头由一个四字节 Magic Number,其值为 1852400382,在内存中就是 "\xfe\x62\x69\x6e",参考 MySQL 源码的 log_event.h,也就是 '\0xfe' 'b' 'i' 'n'

  与日常二进制同样,一般都有一个 Magic Number 进行文件识别,若是 Magic Number 不吻合上述的值那么这个文件就不是一个正常的 Binlog。

事件

  在文件头以后,跟随的是一个一个事件依次排列。每一个事件都由一个事件头和事件体组成。

  事件头里面的内容包含了这个事件的类型(如新增、删除等)、事件执行时间以及是哪一个服务器执行的事件等信息。

  第一个事件是一个事件描述符,描述了这个 Binlog 文件格式的版本。接下去的一堆事件将会按照第一个事件描述符所描述的结构版本进行解读。最后一个事件是一个衔接事件,指定了下一个 Binlog 文件名——有点相似于链表里面的 next 指针。

  根据《[High-Level Binary Log Structure and Contents](High-Level Binary Log Structure and Contents)》所述,不一样版本的 Binlog 格式不必定同样,因此也没有一个定性。在我写这篇文章的时候,目前有三种版本的格式。

  • v1,用于 MySQL 3.2.3
  • v3,用于 MySQL 4.0.2 以及 4.1.0
  • v4,用于 MySQL 5.0 以及更高版本

  实际上还有一个 v2 版本,不过只在早期 4.0.x 的 MySQL 版本中使用过,可是 v2 已通过于陈旧而且再也不被 MySQL 官方支持了。

一般咱们如今用的 MySQL 都是在 5.0 以上的了,因此就略过 v1 ~ v3 版本的 Binlog,若是须要了解 v1 ~ v3 版本的 Binlog 能够自行前往上述的《High-level...》文章查看。

事件头

  一个事件头有 19 字节,依次排列为四字节的时间戳、一字节的当前事件类型、四字节的服务端 ID、四字节的当前事件长度描述、四字节的下个事件位置(方便跳转)以及两字节的标识。

  用 ASCII Diagram 表示以下:

+---------+---------+---------+------------+-------------+-------+
|timestamp|type code|server_id|event_length|next_position|flags  |
|4 bytes  |1 byte   |4 bytes  |4 bytes     |4 bytes      |2 bytes|
+---------+---------+---------+------------+-------------+-------+

  也能够字节编造一个结构体来解读这个头:

cstruct BinlogEventHeader
{
    int   timestamp;
    char  type_code;
    int   server_id;
    int   event_length;
    int   next_position;
    char  flags[2];
};

若是你要直接用这个结构体来读取数据的话,须要加点手脚。

由于默认状况下 GCC 或者 G++ 编译器会对结构体进行字节对齐,这样读进来的数据就不对了,由于 Binlog 并非对齐的。为了统一咱们须要取消这个结构体的字节对齐,一个方法是使用 #pragma pack(n),一个方法是使用 __attribute__((__packed__)),还有一种状况是在编译器编译的时候强制把全部的结构体对其取消,即在编译的时候使用 fpack-struct 参数,如:

```sh
$ g++ temp.cpp -o a -fpack-struct=1


  根据上述的结构咱们能够明确获得各变量在结构体里面的偏移量,因此在 MySQL 源码里面([libbinlogevents/include/binlog_event.h](https://github.com/mysql/mysql-server/blob/5.7/libbinlogevents/include/binlog_event.h#L353))有下面几个常量以快速标记偏移: ```c #define EVENT_TYPE_OFFSET 4 #define SERVER_ID_OFFSET 5 #define EVENT_LEN_OFFSET 9 #define LOG_POS_OFFSET 13 #define FLAGS_OFFSET 17

  而具体有哪些事件则在 libbinlogevents/include/binlog_event.h#L245 里面被定义。若有个 FORMAT_DESCRIPTION_EVENT 事件的 type_code 是 1五、UPDATE_ROWS_EVENTtype_code 是 31。

  还有那个 next_position,在 v4 版本中表明从 Binlog 一开始到下一个事件开始的偏移量,好比到第一个事件的 next_position 就是 4,由于文件头有一个字节的长度。而后接下去对于事件 n 和事件 n + 1 来讲,他们有这样的关系:

next_position(n + 1) = next_position(n) + event_length(n)

  关于 flags 暂时不须要了解太多,若是真的想了解的话能够看看 MySQL 的相关官方文档

事件体

  事实上在 Binlog 事件中应该是有三个部分组成,headerpost-headerpayload,不过一般状况下咱们把 post-headerpayload 都归结为事件体,实际上这个 post-header 里面放的是一些定长的数据,只不过有时候咱们不须要特别地关心。想要深刻了解能够去查看 MySQL 的官方文档。

  因此实际上一个真正的事件体由两部分组成,用 ASCII Diagram 表示就像这样:

+=====================================+
| event  | fixed part (post-header)   |
| data   +----------------------------+
|        | variable part (payload)    |
+=====================================+

  而这个 post-header 对于不一样类型的事件来讲长度是不同的,同种类型来讲是同样的,而这个长度的预先规定将会在一个“格式描述事件”中定好。

格式描述事件

  在上文咱们有提到过,在 Magic Number 以后跟着的是一个格式描述事件(Format Description Event),其实这只是在 v4 版本中的称呼,在之前的版本里面叫起始事件(Start Event)。

  在 v4 版本中这个事件的结构以下面的 ASCII Diagram 所示。

+=====================================+
| event  | timestamp         0 : 4    |
| header +----------------------------+
|        | type_code         4 : 1    | = FORMAT_DESCRIPTION_EVENT = 15
|        +----------------------------+
|        | server_id         5 : 4    |
|        +----------------------------+
|        | event_length      9 : 4    | >= 91
|        +----------------------------+
|        | next_position    13 : 4    |
|        +----------------------------+
|        | flags            17 : 2    |
+=====================================+
| event  | binlog_version   19 : 2    | = 4
| data   +----------------------------+
|        | server_version   21 : 50   |
|        +----------------------------+
|        | create_timestamp 71 : 4    |
|        +----------------------------+
|        | header_length    75 : 1    |
|        +----------------------------+
|        | post-header      76 : n    | = array of n bytes, one byte per event
|        | lengths for all            |   type that the server knows about
|        | event types                |
+=====================================+

  这个事件的 type_code 是 15,而后 event_length 是大于等于 91 的值的,这个主要取决于全部事件类型数。

  由于从第 76 字节开始后面的二进制就表明一个字节类型的数组了,一个字节表明一个事件类型的 post-header 长度,即每一个事件类型固定数据的长度。

  那么按照上述的一些线索来看,咱们能很是快地写出一个简单的解读 Binlog 格式描述事件的代码。

如上文所述,若是须要正常解读 Binlog 文件的话,下面的代码编译时候须要加上 -fpack-struct=1 这个参数。

cpp#include <cstdio>
#include <cstdlib>

struct BinlogEventHeader
{
    int  timestamp;
    unsigned char type_code;
    int  server_id;
    int  event_length;
    int  next_position;
    short flags;
};

int main()
{
    FILE* fp = fopen("/usr/local/var/mysql/master-bin.000001", "rb");
    int magic_number;
    fread(&magic_number, 4, 1, fp);

    printf("%d - %s\n", magic_number, (char*)(&magic_number));

    struct BinlogEventHeader format_description_event_header;
    fread(&format_description_event_header, 19, 1, fp);

    printf("BinlogEventHeader\n{\n");
    printf("    timestamp: %d\n", format_description_event_header.timestamp);
    printf("    type_code: %d\n", format_description_event_header.type_code);
    printf("    server_id: %d\n", format_description_event_header.server_id);
    printf("    event_length: %d\n", format_description_event_header.event_length);
    printf("    next_position: %d\n", format_description_event_header.next_position);
    printf("    flags[]: %d\n}\n", format_description_event_header.flags);

    short binlog_version;
    fread(&binlog_version, 2, 1, fp);
    printf("binlog_version: %d\n", binlog_version);

    char server_version[51];
    fread(server_version, 50, 1, fp);
    server_version[50] = '\0';
    printf("server_version: %s\n", server_version);

    int create_timestamp;
    fread(&create_timestamp, 4, 1, fp);
    printf("create_timestamp: %d\n", create_timestamp);

    char header_length;
    fread(&header_length, 1, 1, fp);
    printf("header_length: %d\n", header_length);

    int type_count = format_description_event_header.event_length - 76;
    unsigned char post_header_length[type_count];
    fread(post_header_length, 1, type_count, fp);
    for(int i = 0; i < type_count; i++) 
    {
        printf("  - type %d: %d\n", i + 1, post_header_length[i]);
    }

    return 0;
}

  这个时候你获得的结果有可能就是这样的了:

1852400382 - �binpz�
BinlogEventHeader
{
    timestamp: 1439186734
    type_code: 15
    server_id: 1
    event_length: 116
    next_position: 120
    flags[]: 1
}
binlog_version: 4
server_version: 5.6.24-log
create_timestamp: 1439186734
header_length: 19
  - type 1: 56
  - type 2: 13
  - type 3: 0
  - type 4: 8
  - type 5: 0
  - type 6: 18
  - ...

  一共会输出 40 种类型(从 1 到 40),如官方文档所说,这个数组从 START_EVENT_V3 事件开始(type_code 是 1)。

跳转事件

  跳转事件即 ROTATE_EVENT,其 type_code 是 4,其 post-header 长度为 8。

  当一个 Binlog 文件大小已经差很少要分割了,它就会在末尾被写入一个 ROTATE_EVENT——用于指出这个 Binlog 的下一个文件。

  它的 post-header 是 8 字节的一个东西,内容一般就是一个整数 4,用于表示下一个 Binlog 文件中的第一个事件起始偏移量。咱们从上文就能得出在通常状况下这个数字只多是四,就偏移了一个魔法数字。固然咱们讲的是在 v4 这个 Binlog 版本下的状况。

  而后在 payload 位置是一个字符串,即下一个 Binlog 文件的文件名。

各类不一样的事件体

  因为篇幅缘由这里就不详细举例其它普通的不一样事件体了,具体的详解在 MySQL 文档中同样有介绍,用到什么类型的事件体就能够本身去查询。

小结

  本文大概介绍了 Binlog 的一些状况,以及 Binlog 的内部二进制解析结构。方便你们造轮子用——否则老用别人的轮子,只知其然而不知其因此然多没劲。

  好了要下班了,就写到这里过吧。

参考

  1. MySQL's binary log 结构简介,目测原文在 TaobaoDBA(已没法访问)
  2. MySQL Binlog 的介绍
  3. MySQL 的 binary log 初探
  4. High-Level Binary Log Structure and Contents and related official documents
  5. #pragma pack vs -fpack-struct for Intel C
相关文章
相关标签/搜索