canal源码分析,canal server源码分析,canal deployer源码分析。转载保留记录。当做笔记。侵权告知。
以下为转载内容
早期,阿里巴巴 B2B 公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于 trigger 的方式获取增量变更,不过从 2010 年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅 & 消费的业务,从此开启了一段新纪元。ps. 目前内部使用的同步,已经支持 mysql5.x 和 oracle 部分版本的日志解析
基于日志增量订阅 & 消费支持的业务:
- 数据库镜像
- 数据库实时备份
- 多级索引 (卖家和买家各自分库索引)
- search build
- 业务 cache 刷新
- 价格变化等重要业务消息
项目介绍
名称:canal [k?’n?l]
译意: 水道 / 管道 / 沟渠
语言: 纯 java 开发
定位: 基于数据库增量日志解析,提供增量数据订阅 & 消费,目前主要支持了 mysql
工作原理
mysql 主备复制实现
从上层来看,复制分成三步:
- master 将改变记录到二进制日志 (binary log) 中(这些记录叫做二进制日志事件,binary log events,可以通过 show binlog events 进行查看);
- slave 将 master 的 binary log events 拷贝到它的中继日志 (relay log);
- slave 重做中继日志中的事件,将改变反映它自己的数据。
canal 的工作原理:
原理相对比较简单:
- canal 模拟 mysql slave 的交互协议,伪装自己为 mysql slave,向 mysql master 发送 dump 协议
- mysql master 收到 dump 请求,开始推送 binary log 给 slave(也就是 canal)
- canal 解析 binary log 对象 (原始为 byte 流)
说明:
- server 代表一个 canal 运行实例,对应于一个 jvm
- instance 对应于一个数据队列 (1 个 server 对应 1…n 个 instance)
instance 模块:
- eventParser (数据源接入,模拟 slave 协议和 master 进行交互,协议解析)
- eventSink (Parser 和 Store 链接器,进行数据过滤,加工,分发的工作)
- eventStore (数据存储)
- metaManager (增量订阅 & 消费信息管理器)
知识科普
mysql 的 Binlay Log 介绍
简单点说:
- mysql 的 binlog 是多文件存储,定位一个 LogEvent 需要通过 binlog filename + binlog position,进行定位
- mysql 的 binlog 数据格式,按照生成的方式,主要分为:statement-based、row-based、mixed。
Java 代码
- mysql> show variables like ‘binlog_format’;
- ±--------------±------+
- | Variable_name | Value |
- ±--------------±------+
- | binlog_format | ROW |
- ±--------------±------+
- 1 row in set (0.00 sec)
目前 canal 只能支持 row 模式的增量订阅 (statement 只有 sql,没有数据,所以无法获取原始的变更日志)
EventParser 设计
大致过程:
整个 parser 过程大致可分为几步:
- Connection 获取上一次解析成功的位置 (如果第一次启动,则获取初始指定的位置或者是当前数据库的 binlog 位点)
- Connection 建立链接,发送 BINLOG_DUMP 指令
// 0. write command number // 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 - Mysql 开始推送 Binaly Log
- 接收到的 Binaly Log 的通过 Binlog parser 进行协议解析,补充一些特定信息
// 补充字段名字,字段类型,主键信息,unsigned 类型处理 - 传递给 EventSink 模块进行数据存储,是一个阻塞操作,直到存储成功
- 存储成功后,定时记录 Binaly Log 位置
mysql 的 Binlay Log 网络协议:
说明:
EventSink 设计
说明:
- 数据过滤:支持通配符的过滤模式,表名,字段内容等
- 数据路由 / 分发:解决 1:n (1 个 parser 对应多个 store 的模式)
- 数据归并:解决 n:1 (多个 parser 对应 1 个 store)
- 数据加工:在进入 store 之前进行额外的处理,比如 join
数据 1:n 业务
为了合理的利用数据库资源, 一般常见的业务都是按照 schema 进行隔离,然后在 mysql 上层或者 dao 这一层面上,进行一个数据源路由,屏蔽数据库物理位置对开发的影响,阿里系主要是通过 cobar/tddl 来解决数据源路由问题。
所以,一般一个数据库实例上,会部署多个 schema,每个 schema 会有由 1 个或者多个业务方关注
数据 n:1 业务
同样,当一个业务的数据规模达到一定的量级后,必然会涉及到水平拆分和垂直拆分的问题,针对这些拆分的数据需要处理时,就需要链接多个 store 进行处理,消费的位点就会变成多份,而且数据消费的进度无法得到尽可能有序的保证。
所以,在一定业务场景下,需要将拆分后的增量数据进行归并处理,比如按照时间戳 / 全局 id 进行排序归并.
EventStore 设计
-
- 目前仅实现了 Memory 内存模式,后续计划增加本地 file 存储,mixed 混合模式
-
- 借鉴了 Disruptor 的 RingBuffer 的实现思路
RingBuffer 设计:
定义了 3 个 cursor
- Put : Sink 模块进行数据存储的最后一次写入位置
- Get : 数据订阅获取的最后一次提取位置
- Ack : 数据消费成功的最后一次消费位置
借鉴 Disruptor 的 RingBuffer 的实现,将 RingBuffer 拉直来看:
实现说明:
- Put/Get/Ack cursor 用于递增,采用 long 型存储
- buffer 的 get 操作,通过取余或者与操作。(与操作: cusor & (size - 1) , size 需要为 2 的指数,效率比较高)
Instance 设计
instance 代表了一个实际运行的数据队列,包括了 EventPaser,EventSink,EventStore 等组件。
抽象了 CanalInstanceGenerator,主要是考虑配置的管理方式:
- manager 方式: 和你自己的内部 web console/manager 系统进行对接。(目前主要是公司内部使用)
- spring 方式:基于 spring xml + properties 进行定义,构建 spring 配置.
Server 设计
server 代表了一个 canal 的运行实例,为了方便组件化使用,特意抽象了 Embeded(嵌入式) / Netty(网络访问) 的两种实现
- Embeded : 对 latency 和可用性都有比较高的要求,自己又能 hold 住分布式的相关技术 (比如 failover)
- Netty : 基于 netty 封装了一层网络协议,由 canal server 保证其可用性,采用的 pull 模型,当然 latency 会稍微打点折扣,不过这个也视情况而定。(阿里系的 notify 和 metaq,典型的 push/pull 模型,目前也逐步的在向 pull 模型靠拢,push 在数据量大的时候会有一些问题)
增量订阅 / 消费设计
具体的协议格式,可参见:CanalProtocol.proto
get/ack/rollback 协议介绍:
- Message getWithoutAck(int batchSize),允许指定 batchSize,一次可以获取多条,每次返回的对象为 Message,包含的内容为:
a. batch id 唯一标识 b. entries 具体的数据对象,对应的数据对象格式:EntryProtocol.proto - void rollback(long batchId),顾命思议,回滚上次的 get 请求,重新获取数据。基于 get 获取的 batchId 进行提交,避免误操作
- void ack(long batchId),顾命思议,确认已经消费成功,通知 server 删除数据。基于 get 获取的 batchId 进行提交,避免误操作
canal 的 get/ack/rollback 协议和常规的 jms 协议有所不同,允许 get/ack 异步处理,比如可以连续调用 get 多次,后续异步按顺序提交 ack/rollback,项目中称之为流式 api.
流式 api 设计的好处:
- get/ack 异步化,减少因 ack 带来的网络延迟和操作成本 (99% 的状态都是处于正常状态,异常的 rollback 属于个别情况,没必要为个别的 case 牺牲整个性能)
- get 获取数据后,业务消费存在瓶颈或者需要多进程 / 多线程消费时,可以不停的轮询 get 数据,不停的往后发送任务,提高并行化. (作者在实际业务中的一个 case:业务数据消费需要跨中美网络,所以一次操作基本在 200ms 以上,为了减少延迟,所以需要实施并行化)
流式 api 设计:
- 每次 get 操作都会在 meta 中产生一个 mark,mark 标记会递增,保证运行过程中 mark 的唯一性
- 每次的 get 操作,都会在上一次的 mark 操作记录的 cursor 继续往后取,如果 mark 不存在,则在 last ack cursor 继续往后取
- 进行 ack 时,需要按照 mark 的顺序进行数序 ack,不能跳跃 ack. ack 会删除当前的 mark 标记,并将对应的 mark 位置更新为 last ack cusor
- 一旦出现异常情况,客户端可发起 rollback 情况,重新置位:删除所有的 mark, 清理 get 请求位置,下次请求会从 last ack cursor 继续往后取
Java 代码
-
Entry -
Header
-
logfileName [binlog 文件名]
-
logfileOffset [binlog position]
-
executeTime [发生的变更]
-
schemaName
-
tableName
-
eventType [insert/update/delete 类型]
-
entryType [事务头 BEGIN / 事务尾 END / 数据 ROWDATA]
-
storeValue [byte 数据, 可展开,对应的类型为 RowChange]
-
RowChange -
isDdl [是否是 ddl 变更操作,比如 create table/drop table]
-
sql [具体的 ddl sql]
-
rowDatas [具体 insert/update/delete 的变更数据,可为多条,1 个 binlog event 事件可对应多条变更,比如批处理]
-
beforeColumns [Column 类型的数组]
-
afterColumns [Column 类型的数组]
-
Column -
index
-
sqlType [jdbc type]
-
name [column name]
-
isKey [是否为主键]
-
updated [是否发生过变更]
-
isNull [值是否为 null]
-
value [具体的内容,注意为文本]
说明:
- 可以提供数据库变更前和变更后的字段内容,针对 binlog 中没有的 name,isKey 等信息进行补全
- 可以提供 ddl 的变更语句
HA 机制设计
canal 的 ha 分为两部分,canal server 和 canal client 分别有对应的 ha 实现
- canal server: 为了减少对 mysql dump 的请求,不同 server 上的 instance 要求同一时间只能有一个处于 running,其他的处于 standby 状态.
- canal client: 为了保证有序性,一份 instance 同一时间只能由一个 canal client 进行 get/ack/rollback 操作,否则客户端接收无法保证有序。
整个 HA 机制的控制主要是依赖了 zookeeper 的几个特性,watcher 和 EPHEMERAL 节点 (和 session 生命周期绑定),可以看下我之前 zookeeper 的相关文章。
Canal Server:
大致步骤:
- canal server 要启动某个 canal instance 时都先向 zookeeper 进行一次尝试启动判断 (实现:创建 EPHEMERAL 节点,谁创建成功就允许谁启动)
- 创建 zookeeper 节点成功后,对应的 canal server 就启动对应的 canal instance,没有创建成功的 canal instance 就会处于 standby 状态
- 一旦 zookeeper 发现 canal server A 创建的节点消失后,立即通知其他的 canal server 再次进行步骤 1 的操作,重新选出一个 canal server 启动 instance.
- canal client 每次进行 connect 时,会首先向 zookeeper 询问当前是谁启动了 canal instance,然后和其建立链接,一旦链接不可用,会重新尝试 connect.
Canal Client 的方式和 canal server 方式类似,也是利用 zokeeper 的抢占 EPHEMERAL 节点的方式进行控制.
最后
项目的代码: https://github.com/alibabatech/canal
这里给出了如何快速启动 Canal Server 和 Canal Client 的例子,如有问题可随时联系
Quick Start
Client Example
|