A-A+

TenDB 1.5 binlog压缩功能介绍

2015年06月16日 binlog, TenDB 评论 2 条

背景

Mysql的binlog可以简单的按照其字面意思理解为二进制日志,binlog包括一系列描述数据修改的“event”。binlog有下面两个重要用途:

1. 用于同步,master把数据修改写入binlog中,然后将这些包含一系列event的binlog发送给slave,由它执行这些event来对数据做出相同的变更。

2. 用于数据恢复,当一个全量备份创建好了以后,binlog可以用event记录下备份以后执行的操作。可以用这些信息将备份的数据恢复到当前的时间点上。

虽然有这么多的用处,但是在一个繁忙的DB上,有可能短时间产生大量的binlog,这不仅会占用大量的磁盘空间,也会给IO带来不小的压力。显然binlog是不能关闭的,但是在不影响binlog各项功能的情况下,是否有方案能减少binlog的大小呢?答案当然是可行的,在TenDB 1.5中实现了binlog压缩功能,该功能比较独立,可以在运行中开启和关闭,适用于statement,row,mixed各种格式。

实现方案

在tmysql 1.4中,对innodb中的blob字段增加了compressed属性来对存储数据进行压缩,因此在binlog压缩中也使用了相同的判断标准来判断某个事件是否需要压缩:

1、写操作涉及的字段中含有compressed属性

2、该语句的总长度大于256

3、全局变量bin_log_compress 为1

在实现binlog的压缩过程中,前期我们采用的是修改原有的event中的字段来压缩,并在header中置上一个标志位来标志这个event是已经压缩过的。但是在实现过程中我们发现,如果这样实现的话,对于老版本的mysqlbinlog来说,由于它不能识别出我们设置的这个标志位。因此,它会按照原来的流程解析生成的binlog,关键问题是,这样做会解析出一串乱码而不报错。这样就会导致解析完了到执行的过程中才发现解析的sql语句是错误的。

同时,在原有的结构上修改字段也意味着对现有流程做较大的改动,不利于TenDB迁移到mysql的更高版本。

为了解决这两个问题,我们决定新增事件类型来表示压缩过的event,新增的三个类型分别为:

QUERY_COMPRESSED_EVENT = 50,

WRITE_ROWS_COMPRESSED_EVENT = 51,

UPDATE_ROWS_COMPRESSED_EVENT = 52,

他们分别代表statement模式下的事件和row模式下的write和update事件。在此基础上新增3个类继承原有的类,这几个类只重写了write函数和构造函数。这几个函数的流程基本上做的事情都是一样的,对于write来说,先申请一块内存,将压缩过的内容写到这个地方,并将类中的buf指针改到这个内存区,然后调用父类的write函数。写完以后将指针改回原有的值并释放掉刚刚申请的内存。而对于构造函数来说,它只负责申请内存,由构造函数释放内存。

这样的实现方案意味着,在这个类的整个生命周期中,除了写入文件外,他的表现和他们的父类是完全一致的。

对于解压来说,也有两种方案,一种是拉取binlog以后直接写入relaylog,然后在执行的时候解压,这种方案的好处在于,整个执行过程基本可以做到无需改动。令一种方案是,在IO线程中解压,解压后写入relaylog,这样做的好处是分担sql线程的压力,减少relaylog的积压,但是缺点也显而易见,就是事件解压后大小是会变化的,就要重新计算master_log_pos和relay_log_pos。

综合这两个方案,最后还是决定采用第二个方案,毕竟性能还是最重要的,代码的正确性可以由后期多做测试来保证。

技术细节

Mysql定义的binlog的格式为:

 

+=====================================+
| event  | timestamp         0 : 4    |
| header +----------------------------+
|        | type_code         4 : 1    |
|        +----------------------------+
|        | server_id         5 : 4    |
|        +----------------------------+
|        | event_length      9 : 4    |
|        +----------------------------+
|        | next_position    13 : 4    |
|        +----------------------------+
|        | flags            17 : 2    |
|        +----------------------------+
|        | extra_headers    19 : x-19 |
+=====================================+
| event  | fixed part        x : y    |
| data   +----------------------------+
|        | variable part              |
+=====================================+

对于一个query_event事件来说它的data部分为:

+=======================================+
| fixed  | thread id            0 : 4   |
| part   +------------------------------+
|        | execute time         4 : 4   |
|        +------------------------------+
|        | database name len    8 : 1   |
|        +------------------------------+
|        | error code           9 : 2   |
|        +------------------------------+
|        | variable block len   11 : 2  |
+=======================================+
|Variable|      status variables        |
| part   +------------------------------+
|        |       database name          |
|        +------------------------------+
|        |       SQL statement          |
+=======================================+

 

当然,我们不会将整个部分全部压缩,一方面其他字段压缩收益不高,另一方面其他字段读写频繁,不适合压缩。被压缩的只有SQL statement这个字段,它就是完整的sql语句,压缩过的格式为:

a

对于row_log_event,它的格式则比较复杂,data部分为:

Fixed data :

    Table_id (6bytes)

    extra  not use (2bytes)

 

Variables data:

    lenenc_int           number of columns

    string.var_len       columns-present-bitmap1, length: (num of columns+7)/8

    if UPDATE_ROWS_EVENT {

       string.var_len       columns-present-bitmap2, length: (num of columns+7)/8

    }

 

    rows:

        string.var_len       nul-bitmap, length (bits set in 'columns-present-bitmap1'+7)/8

        string.var_len       value of each field as defined in table-map

        if UPDATE_ROWS_EVENT {

            string.var_len    nul-bitmap, length (bits set in 'columns-present-bitmap2'+7)/8

            string.var_len    value of each field as defined in table-map

        }

  ... repeat rows until event-end

 

由于这个格式复杂,而且write事件和update事件格式还是不一样的,最重要的是,这个格式5.5和5.6之间也有变化,因此对于这类事件采用的是压缩整个rows部分的数据,只有这样才能保证从5.5升级到5.6时无需改动代码,因为这样整体压缩是不需要感知内部格式变化的。压缩完的格式于上面的query_event中使用的相同。

此外,为了支持该功能,也相应的对mysqlbinlog做了些改动,具体是,对于压缩过的事件,将其转换成未压缩的事件打印出来,这样做的目的是为了让生成的sql文件可以在之前的版本上正确执行,做到向后兼容。

b

如何使用

根据上面列出的压缩条件,如果想要打开binlog压缩,首先,要保证涉及的blob字段已经设置了compressed属性,具体可以参考http://tencentdba.com/blog/innodb-compress/这篇文章。其次,要设置名为log_bin_compress的全局变量值为1,可以使用mysql命令:set global lob_bin_compress=1修改,或者在配置文件中加入:log-bin-compress=1来实现。

这样配置了以后,凡是sql语句长度大于256,并且对compressed字段有写操作的binlog就会被压缩了。

测试结果

根据目前的测试结果来看,启用binlog压缩功能,可以达到7~9的压缩比。

开启了binlog压缩功能以后,在A5机器上,使用非全cache对QPS几乎无影响,大概会增加12%的CPU使用率。在全cache时,QPS会稍微减少(约15%),原因是此时CPU成为了瓶颈,增加了较多的CPU消耗(约20%),不过IO也相应的减少约20%。

下面是在100并发下的数据:

binlog未压缩 binlog压缩
QPS 517/1507 526/1254
CPU(%) 22/60 35/80
IO(%) 100/41 100/21

100并发数据(非全cache/全cache)

在实际业务中,CPU一般不是瓶颈,此时对QPS的影响基本可以忽略,因此,启用binlog压缩功能的收益还是十分明显的。

完整的测试数据在附件中给出。

http://tencentdba.com/wp-content/uploads/2015/06/binlog压缩数据.xlsx

原创文章,转载请注明: 转载自腾讯游戏DBA团队

本文链接地址: TenDB 1.5 binlog压缩功能介绍

文章的脚注信息由WordPress的wp-posturl插件自动生成

Copyright © 腾讯游戏DBA团队 保留所有权利.  

用户登录

分享到: