binlog

event

EventParser

EventSink

过滤,路由

EventStore

mysql

​​​通过协议,不断地从Mysql获取binlog,交给EventParser处理


http://www.iocoder.cn/Canal/tianshouzhi/tutorials/


1.Client:客户端 启动和建立连接,发送和接收数据,核心接口为CanalConnector


2.Client-adapter : 客户端的适配器


3.common : 公用的组件和工具类


4.dbsync: 数据库同步的,包括解析出来的event


5.deployer: canal 部署,加载配置;通过该模块提供的CanalLauncher来启动canal server


6.driver : canal和数据库 连接,数据传输的一些实现


7.filter : 数据过滤机制


8.instance : canal 的实例,模拟成为一个MySQL实例的slave


9.meta : 增量订阅 & 消费信息管理器,核心接口为CanalMetaManager,主要用于记录canal消费到的mysql binlog的位置。


10.parse : 把二进制文件解析成mysqlEvent;模拟slave协议和master进行交互,协议解析


11.promotheus: 数据监控和采集


12.protocol: 协议和position,client和server模块之间的通信协议


13.server : 服务端启动和收发消息,核心接口为CanalServer


14.sink : event事件消费,路由分发,过滤,加工。parser和store连接器,核心接口为CanalEventSink


15.store : canal 数据存储,核心接口为CanalEventStore。



通过deployer模块,启动一个canal-server,一个cannal-server内部包含多个instance,每个instance都会伪装成一个mysql实例的slave。client与server之间的通信协议由protocol模块定义。client在订阅binlog信息时,需要传递一个destination参数,server会根据这个destination确定由哪一个instance为其提供服务。



负责解析Mysql的二级制文件binlog成为event


核心类:AbstractEventParser


start方法


1. 初始化缓冲队列,然后start()启动


2. 初始化BinLogParser,然后start()启动


3.启动工作线程parseThread


4.开始执行


4.1 先构造Erosa连接


4.2 创造一个心跳线程

Canal 源码分析01 -- Canal 入门_客户端

   


4.3 preDump 执行dump前的准备工作,具体实现在MysqlEventParser中


4.3.1 校验连接类型必须是MysqlConnection,如果不是,就直接抛出异常


4.3.2 初始化metaConnection,并且连接(connected设置为true,然后打开一个SocketChannel,通过channel 发送对应的headPackage,handshakePacket,认证AuthSwitchRequestPacket,ClientAuthenticationPacket


4.3.3 然后从metaConnection获取二进制文件的格式 BinlogFormat,判断是否在配置的支持的BinLogFormat列表中,不在就抛异常。


重复再执行一遍检查,作为doubleCheck


4.3.4 初始化tableMetaTSDB,然后放入到缓存对象tableMetaCache中


4.4 erosaConnection.connect();// 连接 (同样是开启channel,然后发送对应的package数据包


4.5 从erosaConnection中获取queryServerId


4.6 从erosaConnection中获取startPosition,调用方法findStartPosition

Canal 源码分析01 -- Canal 入门_mysql_02     


4.6.1 GTID模式下,CanalLogPositionManager里取最后的gtid;如果为空,就返回masterPosition


4.6.2 非GTID模式,调用findStartPositionInternal获取,也是先从CanalLogPositionManager获取,找不到就返回主库位点masterPosition,如果主库位点也没了,就获取从库位点standbyPosition;


如果还是找不到位点,就根据给定的时间戳,在指定的binlog中找到最接近于该时间戳(必须小于等于时间戳)的一个事务起始位置


(遍历文件,使用logDecoder解析出event事件对象,然后转化成CanalEntry,LogEventConvert的parse方法;


如果entry类型是事务终止TRANSACTIONEND,初始化entryPosition,然后先设置poisition,再设置Gtid


如果entry类型是事务终止TRANSACTIONBEGIN,初始化entryPosition,然后先设置gtid,再设置position


4.6.3 继续,如果获取到了位点


4.6.3.1 位点的sourceAddress和连接的的address相等;


如果binlog 定位位点失败,1.binlog位点删除,可以直接返回null,走oss binlog处理


2.发生了主备切换,serverId匹配不上,就发起一次基于时间戳查找合适的位点 :


endPosition : ”show master status“ 这个sql语句,对应的位点


startPosition : "show binlog events limit 1" 这个对应的位点


调用findAsPerTimestampInSpecificLogFile,在指定的文件中寻找,最接近于该时间戳(小于)的一个事务起始位置(同上)


最后返回startPosition


4.6.4 如果是事务的位点,就去寻找preTransactionStartPosition:


1.开始扫描,解析出event,然后转化成CanalEntry


2. 如果类型是TRANSACTIONBEGIN,并且事务的位点要小于上面startPosition,记录一下transaction begin position


如果事务的位点大于等于startPosition,就直接退出了


3.更新startPosition为事务的位点


++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


4.7 校验startPosition和table meta不能为空


4.8 erosaConnection重新连接,调用reconnect()方法,因为在找position过程中可能有状态,需要断开后重建。


4.9 调用parseAndProfilingIfNesscary解析出event,转化成entry


  Canal 源码分析01 -- Canal 入门_通信协议_03


    4.9.1 首先校验一下profilingEnabled,如果是true,就获取当前时间为开始startTs


    4.9.2  调用binlogParser.parse解析 ,根据各种不同的LogEvent类型分别解析,如下


   Canal 源码分析01 -- Canal 入门_mysql_04


4.9.3 以UPDATE_ROWS_EVENT 举例,调用parseRowsEvent进行解析


(1) 先判断是否开启了rows过滤,如果开启,直接返回null


(2) 通过event解析出表结构tableMeta:首先从event获取tableMapLogevent,如果为空,直接抛出异常


(3) 接下来,要根据table的dbName和tableName获取 isHeartBeat还是isRDSHeartBeat


(4) 根据dbName和tableName拼接出fullName,校验名称必须符合nameFilter的要求


(5) rds心跳和aliSQL心跳,就构造出对应的tableMeta


(6) 校验tableMeta不能为空


(7) 进一步把eventType分类为EventType.INSERT, EventType.UPDATE 和 EventType.DELETE


其余的类型不支持


(8) 然后初始化rowChangeBuilder


(9) 分INSERT,DELETE和UPDATE类型解析,调用parseOneRow


(10) 开始解析parseOneRow,获取列信息columnInfo和表结构tableMeta


(11) 根据event创建entryPosition;


(12) 如果没有主键,新增一张临时表,先全量导入历史数据,然后老表建立trigger,增量数据也导入新表


(13) 把应用请求锁定,临时表rename为老表的名字,完成增加字段


(14) 从缓存中获取tableMeta


(15) 对于每一行的解析,开始循环每1列,获取对应的ColumnInfo,如果该列没值,就continue下一列


(16) 不解析最后一列,对于循环的当前列,初始化ColumnBuilder,设置name, isKey, mysqlType,index


(17) 获取fieldMeta, 然后获取该列的value,根据value的java类型处理,分成INTEGER,TINYINT,SMALLINT


SHORT等等; 根据value的javaType,设置sqlType,和update/before这种标志位


(18) 构建好rowChangeBuilder后,调用build获取到rowChange


(19) 根据rowChange的值,创建entry对象


4.10 然后记录一下最后的position,和最后一次有数据的时间。如果抛出了异常,需要记录一下出错的位点信息,然后继续向上抛出异常。


5. 开始dump数据


5.1 如果是并行,构建对应的processor

Canal 源码分析01 -- Canal 入门_通信协议_05



判断所属instance是否启用GTID模式,是的话调用ErosaConnection中GTID对应方法dump数据


解析出gtidSet, 然后启动multiStageCoprocessor


调用erosaConnection.dump方法进行dump数据

Canal 源码分析01 -- Canal 入门_mysql_06



1.更新配置,调用updateSettings


2.获取主库checkSum信息


3.sendBinlogDumpGTID : 构造binlogDumpCmd,然后把命令转化成对应的字节数组,写入到channel中


4.构造出DirectLogFetcher,并且初始化接收的大小,然后调用start方法启动,receivedBinlogBytes累加


调用fetcher.consume()


5. finally方法 : 关闭连接,


6. 重置缓冲队列,binlogParser重新置位


7. 判断如果当前是running状态, 就sleep一段时间以后再重试