目录
背景
server的逻辑概览
canal server启动逻辑
com.alibaba.otter.canal.deployer.CanalLauncher#main
图1、server启动概览和状态配置变更逻辑
对于server状态变更的操作(图1右上角部分),是admin向server发出的:
com.alibaba.otter.canal.admin.handler.SessionHandler#messageReceived
对于server配置的变更(图1左上角部分),是server轮询admin的:
com.alibaba.otter.canal.deployer.CanalLauncher#canalLaunch
canal server监听消息逻辑
初始化instance的各个组件:
com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded#start(java.lang.String)
图2、instance启动和事件获取生产
获取event:
com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher#fetch
event解析(如果想要加业务日志,就可以在这里追溯):
com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert#parse
event的竖式结构转换:
com.alibaba.otter.canal.connector.core.util.MessageUtil#convert
(整个项目中,对文件落盘,文件读取,数据库位点解析,信息组装和发送,全部都采用了监听者模式;互相隔离,能够做到功能解耦,对功能的增删改比较方便。
但是排查问题起来比较难,同一个接收方可能接受来自多个生产者的消息,一般是消费,但也可以堆积不消费。)
通过admin的配置,可以调整server的部分行为,
但有如下问题
需要解决的问题:
0、基于一个前提:目前canal已经接入了公司框架,可以使用框架内组件;讨论基于现在的发版逻辑
问题1
canal server重新发版,canal admin的条目会变更,需要重复配置,增加维护成本;
问题2
canal重新发版,不会保留meta.dat(记录上一次读取binlog的位置),导致漏掉变更信息。而且server管理着部分对外数据交互,不利于由外部(admin)对server的功能进行干预
为解决问题需要做的:
解决问题1:canal发版固定信息处理
0、canal当做不同的服务来开启
不能一个服务多实例,否则资源分配难均衡
不同服务在初始化的时候通过读取“公司发版中心配置的环境变量”来决定启动哪个服务
要用同一份代码,方便功能升级;
-
在server与admin交互部分进行操作
1、定义几个canal 服务名字
如
canal-common
canal-shop
canal-product
2、发版的时候制定参数
例如需要启动common服务,则在发版中心配置 common 作为当前启动的环境变量,其余服务的启动类似;
则同一份代码可以根据配置的不同而启动不同的服务,并以此注册到canal admin中。
在admin中显示的名字是由canal.properties中
canal.admin.register.name
控制的,修改代码时可按照上述关键词搜索。
因为启动后,在canal admin中配置的admin相关值都不会再生效,因此需要在服务第一次启动的时候就制定好admin的相关参数
修改相应代码使之生效
同时admin需要做一定工作,要仅根据name来判断(而不是和ip联合判断)
目前admin对server的干预流程如下:
图3、admin对server的识别与干预概览
图3红框部分,则是需要改动的版块。
解决问题2:meta.dat备份与重用
0、meta.dat数据
meta.dat数据是记录着某个instance所配置的数据库连接信息,以及上一次记录变更的binlog文件与位点,为下一次读取mysql的变更设置了起点(offset)
目前canal server写死了clientId=1001,因此不会区分请求来源client,消费位点只会和destination(可以理解为配置到同一个instance的配置)有关,所有的消费者都是跟着destination走,即便是新进来的消费者,也不会从位置0开始读取。
存储json对应的java类:
com.alibaba.otter.canal.meta.FileMixedMetaManager.FileMetaInstanceData
监听和存入内存:
com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded#subscribe
com.alibaba.otter.canal.meta.MemoryMetaManager#subscribe
图4、meta.dat文件写入流程
图4中的embedded server可以理解为图2的组件,由server中不同的instance(worker)来刷写destinations。默认落盘速度是每1秒一次。
图5、meta.dat读取流程
读取的位点信息使用在:
com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser#findStartPositionInternal
为数据库读取提供一个起始位点,如果没有位点信息,则从目前mysql到的位置读取。如果啥都没有,从连接建立的前60s读取。这个机制主要是防止删除meta.dat等读取不到存储的起始位点的情况
文件内容格式示例:
{"clientDatas":[{"clientIdentity":{"clientId":1001,"destination":"6shop2","filter":""},"cursor":{"identity":{"slaveId":-1,"sourceAddress":{"address":"shop.db.rj-info.com","port":3306}},"postion":{"gtid":"","included":false,"journalN
ame":"mysql-bin.000151","position":497978414,"serverId":10,"timestamp":1667375100000}}}],"destination":"6shop2"}
内容不多,
-
最终的存储和读取都由java.io.File#File(java.lang.String)一个步骤控制,我们可以采用mysql来做为远端存储,来解决重新发版,本地文件丢失的问题。
1、mysql存储meta.dat表设计
CREATE TABLE `canal_manager`.`Untitled` (
`id` int(0) NOT NULL COMMENT 'id',
`service_name` varchar(100) NOT NULL DEFAULT '' COMMENT '服务名称,用于检索',
`meta_data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '存储的binlog位点数据',
`write_time` datetime(255) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '写入时间',
`create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '生成时间',
`update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE INDEX `idx_service_name`(`service_name`) USING BTREE
) ENGINE = InnoDB COMMENT = 'canal服务,binlog位点记录表';
-
id存在完全是因为公司不让去掉,实际无任何业务场景需要用到id
2、存储读取策略
-
对meta管理器部分进行操作
(1)修改存储
com.alibaba.otter.canal.meta.FileMixedMetaManager#flushDataToFile(java.lang.String)
admin开启接口,将server中原本写到文件的逻辑,改为调用admin开启的接口,将数据发送到admin,并由admin去管理数据是存储文件或到数据库。
(2)修改读取
com.alibaba.otter.canal.meta.FileMixedMetaManager#loadClientIdentity
com.alibaba.otter.canal.meta.FileMixedMetaManager#getDataFile
admin开启接口,原本读取文件逻辑为server读取meta.dat文件数据。修改为调用admin接口获取上次保存的原本属于meta.dat数据
(3)定时存的代码:
com/alibaba/otter/canal/meta/FileMixedMetaManager.java:89
(可搜索“// 启动定时工作任务”)
此项目为辅助查找和检查逻辑缺漏。实际上上述(1)(2)已经可以涵盖meta.dat的操作
其他说明:
阻塞读取:从远端mysql获取位点数据和初始化
读取失败需要走初始化策略。相当于丢失数据(没法处理,因为没有存起来,用文件也一样有这个问题);
异步不阻塞,但顺序写入:写入远端mysql,刷新速度可为1s,因为无task时不会调用接口,因此无需担心高TPS。
方案风险点和短板
1、上述destinations(binlog信息的存储)、cursors(binlog位点的存储)、parser(binlog信息解析)等等具有多种策略,使用监听者模式,方法多且冗杂,在出现问题后排查需要花费较长时间。这个需要慢慢熟悉
2、暂时没有涉及到性能优化,可以留到下一版做。前提是需要了解解析binlog的原理。目前可通过设置parallel、parallelThreadSize、batchsize等参数来调整,加快解析时间或者传输批量更大;
3、具有一个中心,admin,于是有下述问题
(1)初次启动服务,需要在admin上对应服务进行配置,否则报错;
(2)需要单独在server开后门的开关接口来控制启动停止机器,防止admin宕机导致对server失去控制;
(3)admin管理除监听和解析binlog外所有server与外部的数据交换,如meta.dat数据的处理。admin宕机或失去连接,会导致数据写入失败,这个需要做额外异常处理否则报错。(可以一直使用缓存数据直到admin恢复)