跟我一起学canal原理

247 阅读5分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。

MySQL主备复制原理

canal1.png

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 流) canal2.png

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

MySql-Binlog协议详解-流程

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

binlog dump报文

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

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

// 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

canal5.png

解析数据

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

image.png

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

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

canal架构

canal7.png

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

  • eventParser (数据源接入,模拟slave协议和master进行交互,协议解析)
  • eventSink (Parser和Store链接器,进行数据过滤,加工,分发的工作)
  • eventStore (数据存储)
  • metaManager (增量订阅&消费信息管理器)

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

EventParser设计

canal8.png 整个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: image.png

EventSink设计

image.png

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

image.png

image.png

EventStore

1、目前仅实现了Memory内存模式,后续计划增加本地file存储,mixed混合模式 2、借鉴了Disruptor的RingBuffer的实现思路,RingBuffer设计: image.png 定义了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 image.png 针对这个环形队列,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