cover_image

跟我学canal原理

我是大明哥 皮皮特的AI工厂
2022年01月20日 14:39

MySQL主备复制原理

图片

MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)MySQL slave 重放 relay log 中事件,将数据变更为它自己的数据

canal工作原理

canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送 dump 协议MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )canal 解析 binary log 对象(原始为 byte 流)

图片

canal [kə’næl],译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费目前canal只支持mysql和本地binlong文件解析

MySql-Binlog协议详解-流程

Binlog发送接收流程,流程如下图所示:

图片

binlog dump报文

该消息是备份连接时由从服务器向主服务器发送的最后一个请求,主服务器收到后,会响应一系列的报文,
每个报文都包含一个二进制日志事件。如果主服务器出现故障时,会发送一个EOF报文

图片

备注:大字节序是高位数据存储在内存高位地址,低位数据存储在低位地址,小字节序是高位数据存储在内存低位地址,低位数据存储在高位地址

// 0. write command number (byte) 0x12
// 1. write 4 bytes bin-log position to start at
// 2. write 2 bytes bin-log flags
// 3. write 4 bytes server id of the slave
// 4. write bin-log file name

图片

解析数据

dump消息发送成功后,MySQL主服务器已经接受了 canal 这个从服务器,那么canal之后的工作就是解析拿到的binlog内容解析之前必须要把binlog-format设置为ROW模式,canal是基于行的复制的。binlog中每一个数据变更可以叫做事件,在ROW模式下,有几个主要的事件类型:

图片

每一次数据的变更,都会触发2个事件,先把要更改的表信息告诉你,然后再告诉你更改的row内容,比如TABLE_MAP_EVENT + WRITE_ROWS_EVENT

图片


canal在接收到binlog数据后,并不会马上把它解析成我们熟悉的JSON数据,而是在发送的时候才开始。比如我们选择使用RocketMQ,那么在发送之前才开始将binlog里面的byte数组转化为对象。

canal架构

图片

说明:
server代表一个canal运行实例,对应于一个jvm,instance对应于一个数据队列 (1个server对应1…n个instance)
instance模块:

  • eventParser (数据源接入,模拟slave协议和master进行交互,协议解析)

  • eventSink (Parser和Store链接器,进行数据过滤,加工,分发的工作)

  • eventStore (数据存储)

  • metaManager (增量订阅&消费信息管理器)

目前canal支持所有模式的增量订阅(但配合同步时,因为statement只有sql,没有数据,无法获取原始的变更日志,所以一般建议为ROW模式)

EventParser设计

图片

整个parser过程大致可分为几步:
1、Connection获取上一次解析成功的位置 (如果第一次启动,则获取初始指定的位置或者是当前数据库的binlog位点)
2、Connection建立链接,发送BINLOG_DUMP指令
   0) write command number:0x12
   1) write 4 bytes bin-log position to start at
   2) write 2 bytes bin-log flags
   3) write 4 bytes server id of the slave
   4) write bin-log file name
3、Mysql开始推送Binaly Log
4、接收到的Binaly Log的通过Binlog parser进行协议解析,补充一些特定信息
// 补充字段名字,字段类型,主键信息,unsigned类型处理
5、传递给EventSink模块进行数据存储,是一个阻塞操作,直到存储成功
6、存储成功后,定时记录Binaly Log位置

All events have a common general structure consisting of an event header followed by event data:

图片

EventSink设计

图片

Sink阶段所做的事情,就是根据一定的规则,对binlog数据进行一定的过滤一般有两次过滤,分别是:
根据表名、库名等进行过滤,通过AviaterRegexFilter,根据库名和表名称进行过滤主要内容是HEARTBEAT类型的事件表名、库名过滤:

图片
图片

EventStore

1、目前仅实现了Memory内存模式,后续计划增加本地file存储,mixed混合模式
2、借鉴了Disruptor的RingBuffer的实现思路,RingBuffer设计:

图片


定义了3个cursor
Put : Sink模块进行数据存储的最后一次写入位置
Get : 数据订阅获取的最后一次提取位置
Ack : 数据消费成功的最后一次消费位置
借鉴Disruptor的RingBuffer的实现,将RingBuffer拉直来看

说明:

Put/Get/Ack cursor用于递增,采用long型存储(AtomicLong)
buffer的get操作,通过取余或者与操作。(与操作:cusor & (size - 1) , size需要为2的指数,效率比较高)
indexMask = size-1

图片

针对这个环形队列,canal定义了3类操作:Put、Get、Ack,其中:
  • Put 操作:添加数据。event parser模块拉取到binlog后,并经过event sink模块过滤,最终就通过Put操作存储到了队列中。

  • Get操作:获取数据。canal client连接到canal server后,最终获取到的binlog都是从这个队列中取得。

  • Ack操作:确认消费成功。canal client获取到binlog事件消费后,需要进行Ack。你可以认为Ack操作实际上就是将消费成功的事件从队列中删除,如果一直不Ack的话,队

列满了之后,Put操作就无法添加新的数据了。对应的,我们需要使用3个变量来记录Put、Get、Ack这三个操作的位置,其中:

  • putSequence: 每放入一个数据putSequence +1,可表示存储数据存储的总数量

  • getSequence: 每获取一个数据getSequence +1,可表示数据订阅获取的最后一次提取位置

  • ackSequence: 每确认一个数据ackSequence + 1,可表示数据最后一次消费成功位置

另外,putSequence、getSequence、ackSequence这3个变量初始值都是-1,且都是递增的,均用long型表示。由于数据只有被Put进来后,才能进行Get;Get之后才能进行Ack。所以,这三个变量满足以下关系:
ackSequence <= getSequence <= putSequence


继续滑动看下一个
皮皮特的AI工厂
向上滑动看下一个