数据库同步中间件Canal+Otter - 前日篇(2)
MySQL+InnoDB架构体系
MySQL体系前端接受连接,并提供多种API,连接池化可重用。这里连接可以理解为线程,来处理来自客户端的请求。后台存储引擎负责控制IO策略,内存缓冲和线程调度,以及会话事务管理。
我们这里分析在MySQL5.6以后的默认引擎InnoDB。
InnoDB引擎结构:
1. 内存:
innoDB 将数据库文件按页读取到内存,按照最少使用算法。来保留数据。修改数据时,先修改的是缓冲中的页(脏页),之后按照一定频率将脏页刷新到文件。
缓冲池缓存的数据页类型有:索引页,数据页,undo页,插入缓冲,自适应哈希索引,InnoDB存储的锁信息和数据字典信息等。
日志缓冲池将重做日志信息先放入这个缓冲区,然后按一定频率将其刷新到日志文件,因此我们只要保证每秒产生的事务量不超过这个缓冲大小即可。
**额外内存池:**innodb申请缓冲池(buffer pool),但每个缓冲池中的页缓冲有对应的缓冲控制对象(buffer control block),这些对象记录LRU、锁、等待等信息,这些对象的内存需要多额外内存池中申请;因此当buffer pool较大时,也需相应增大该值
**线程缓冲:**MySQL数据库支持线程缓存,在多线程连接模式下,如果连接断开后,将这个线程放入空闲线程缓冲区,在下次有连接到来时,先去缓冲池中查找是否有空闲线程,有则用之,无则创建。
查询缓冲:查询缓冲的作用就是当查询接收到一个和之前同样的查询,服务器将会从查询缓冲中检索结果,而不是再次分析和执行上次的查询。这样就大大提高了性能,节省时间。
插入缓冲:插入时检查缓冲中对应索引页是否存在,若不存在则载入,并写入。这样可以把几个在同一个缓冲页上的插入操作放入一次IO中
2.后台线程
2.1 io thread:
read thread:读线程,默认个数为4
write thread:写线程,默认个数为4
insert buffer thread:插入缓冲线程
log thread:日志线程
2.2 master thread:
- 主循环:
- 每秒一次的操作:日志缓冲刷新到磁盘,即使这个事务未提交(总是);合并插入缓冲(可能),会根据前一秒内的io次数判断,如果小于5次,可以执行合并插入缓冲;至多刷新100个脏页至磁盘(可能),通过判断脏页比例是否超过了innodb_max_dirty_pages_pct这个设置值来进行,未超过则不执行;无用户活动,切换到background loop(可能);
- 每10秒一次的操作:刷新100个脏页到磁盘(可能),如果过去10秒磁盘io操作小于200次,则执行本操作;合并至多5个插入缓冲(总是);日志缓冲刷新到磁盘(总是);删除无用的undo页(总是);刷新100个或10个脏页到磁盘(总是),判断缓冲池脏页比例,超过70%则刷新100个脏页,比例小于10%则刷新10个脏页;产生一个检查点checkpoint(总是),注意此时并不是把所有脏页都刷新到了磁盘,只是将最老日志序列号的页写入磁盘;
- 后台循环 background loop:
- 删除无用的undo页(总是);
合并20个插入缓冲(总是);
跳回到主循环(总是);
不断刷新100个页,直到符合条件(可能,跳转到flush loop中完成);
- 删除无用的undo页(总是);
- flush loop:由background loop跳转到此loop中完成刷新脏页的工作;当flush loop中无事可做时会切换到suspend loop;
- suspend loop: 该loop将master thread挂起,等待事件发生;
MySQL binlog原理:
MySQL有四种log:
- Error Log:记录 mysqld 的一些错误
- General Query Log:一般查询日志,记录 mysqld 正在做的事情,比如客户端的连接和断开、来自客户端每条 Sql Statement 记录信息;如果你想准确知道客户端到底传了玩意儿给服务端,这个日志就非常管用了,不过它非常影响性能。
- Slow Query Log:记录一些查询比较慢的 SQL 语句——这种日志非常常用,主要是给开发者调优用的
- Binary Log:包含了一些事件,这些事件描述了数据库的改动,如建表、数据改动等。
- 两个重要的用途——复制和恢复。比如主从表的复制,和备份恢复什么的。
由于innoDB处理更新是在脏页中,而且脏页是存在于内存中,如果断电,则会丢失。为了防止这个问题,引入了redo log:将每次的页面修改存入redo log中。由于写入redo log近似于顺序读写,相比于直接刷入磁盘的随机读写快了很多。每次写redo log都要更新文件头的两个checkpoint值,所以为近似顺序读写。由于master thread每秒都会将log缓存刷入,所以我们可以认为log的记录一定比数据库的更新一些。异常断电重启时,mysql会根据checkpoint检查redo log来做恢复。
不管你将二进制日志文件记录的格式设为哪一种,其记录的都是关于一个事务的具体操作内容,而InnoDB存储引擎的重做日志文件记录的关于每个页的更改的物理情况。
在一个AB复制环境下主库crash,然后进行crash recovery,此时如果binlog里面的的事务信息与redo log里面的信息不一致,那么就会出现主库利用redo log进行恢复后,然后binlog部分的内容复制到从库去,然后出现主从数据不一致状态。所以需要保证binlog与redo log两者事务一致性。
所以,为保证一致性,记录二进制日志过程中:
- 两个重要的用途——复制和恢复。比如主从表的复制,和备份恢复什么的。
- 取得全局锁(为保证全局事务顺序一致性),然后写redo log
- 如果成功,继续写binlog
- 如果成功,最后在redo log加上commit
- 以上任一失败都将回滚重做
- 释放全局锁
Binlog文件:
- 文件头
- 四字节 Magic Number,用来识别是正常的binlog文件
- 事件
- 在文件头之后,跟随的是一个一个事件依次排列。每个事件都由一个事件头和事件体组成(事实上在 Binlog 事件中应该是有三个部分组成,header、post-header 和 payload,不过通常情况下我们把 post-header 和 payload 都归结为事件体,实际上这个 post-header 里面放的是一些定长的数据,只不过有时候我们不需要特别地关心。想要深入了解可以去查看 MySQL 的官方文档。)。
- 事件头里面的内容包含了这个事件的类型(如新增、删除等)、事件执行时间以及是哪个服务器执行的事件等信息。
- timestamp 4 bytes四字节的时间戳
- type code 1 byte一字节的当前事件类型
- server_id 4 bytes四字节的服务端ID
- event_length 4 bytes四字节的当前事件长度描述
- next_position 4 bytes四字节的下个事件位置
- flags 2 bytes 标识(可以标识binlog是否正常关闭)
- 第一个事件是一个格式描述事件,描述了这个 Binlog 文件格式的版本。接下去的一堆事件将会按照第一个事件描述符所描述的结构版本进行解读。最后一个事件是一个衔接事件,指定了下一个 Binlog 文件名
Binlog格式:
- ROW:日志中会记录成每一行数据被修改的形式
优点:在row模式下,bin-log中可以不记录执行的sql语句的上下文相关的信息,仅仅只需要记录那一条记录被修改了,修改成什么样了,所以row的日志内容会非常清楚的记录下每一行数据修改的细节,非常容易理解。而且不会出现某些特定情况下的存储过程和function,以及trigger的调用和出发无法被正确复制问题。
缺点:在row模式下,所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容。 - STATEMENT:每一条会修改数据的sql都会记录。
- MiXED:在 Mixed 模式下,MySQL 会根据执行的每一条具体的 SQL 语句来区分对待记录的日志形式,也就是在 statement 和 row 之间选择一种。新版本中的 statment 还是和以前一样,仅仅记录执行的语句。而新版本的 MySQL 中对 row 模式也被做了优化,并不是所有的修改都会以 row 模式来记录,比如遇到表结构变更的时候就会以 statement 模式来记录,如果 SQL 语句确实就是 update 或者 delete 等修改数据的语句,那么还是会记录所有行的变更。