Canal是阿里开源的一款基于Mysql数据库binlog的增量订阅和消费组件,通过它可以订阅数据库的binlog日志,然后进行一些数据消费,如数据镜像、数据异构、数据索引、缓存更新等。相对于消息队列,通过这种机制可以实现数据的有序化和一致性,本文主要讲解Canal的原理,大部分内容来源于github上的介绍,这里主要做一个整合,方便自己和大家更好的理解Canal

github地址:https://github.com/alibaba/canal

一:简介

cola 架构源码 canal架构_阿里巴巴canal

                                                                       

cola 架构源码 canal架构_canal原理_02

 

 

由上面两张图片可知

  • canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
  • MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
  • canal 解析 binary log 对象(原始为 byte 流)
  • canal对外提供增量数据订阅和消费,提供Kafka、RocketMQ、RabbitMq、Es、Tcp等组件来消费

二:场景

1、同步缓存redis/全文搜索ES

        canal一个常见应用场景是同步缓存/全文搜索,当数据库变更后通过binlog进行缓存/ES的增量更新。当缓存/ES更新出现问题时,应该回退binlog到过去某个位置进行重新同步,并提供全量刷新缓存/ES的方法

2、下发任务

另一种常见应用场景是下发任务,当数据变更时需要通知其他依赖系统。其原理是任务系统监听数据库变更,然后将变更的数据写入MQ/kafka进行任务下发,比如商品数据变更后需要通知商品详情页、列表页、搜索页等先关系统。这种方式可以保证数据下发的精确性,通过MQ发送消息通知变更缓存是无法做到这一点的,而且业务系统中不会散落着各种下发MQ的代码,从而实现了下发归集

3、数据异构

在大型网站架构中,DB都会采用分库分表来解决容量和性能问题,但分库分表之后带来的新问题。比如不同维度的查询或者聚合查询,此时就会非常棘手。一般我们会通过数据异构机制来解决此问题。所谓的数据异构,那就是将需要join查询的多表按照某一个维度又聚合在一个DB中。让你去查询。canal就是实现数据异构的手段之一

三:CanalServer设计

cola 架构源码 canal架构_阿里巴巴canal_03

  • server 代表一个 canal 运行实例,对应于一个 jvm
  • instance 对应于一个数据队列 (1个 canal server 对应 1..n 个 instance )
  • instance 下的子模块
  • eventParser: 数据源接入,模拟 slave 协议和 master 进行交互,协议解析
  • eventSink: Parser 和 Store 链接器,进行数据过滤,加工,分发的工作
  • eventStore: 数据存储
  • metaManager: 增量订阅 & 消费信息管理器

整体类图设计

 

          

cola 架构源码 canal架构_canal原理_04

  • CanalLifeCycle: 所有 canal 模块的生命周期接口
  • CanalInstance: 组合 parser,sink,store 三个子模块,三个子模块的生命周期统一受 CanalInstance 管理
  • CanalServer: 聚合了多个 CanalInstance

EventParser 设计

cola 架构源码 canal架构_canal_05

 

  • 每个EventParser都会关联两个内部组件
  • CanalLogPositionManager : 记录binlog 最后一次解析成功位置信息,主要是描述下一次canal启动的位点
  • CanalHAController: 控制 EventParser 的链接主机管理,判断当前该链接哪个mysql数据库
  • 目前开源版本只支持 MySQL binlog , 默认通过 MySQL binlog dump 远程获取 binlog ,但也可以使用 LocalBinlog - 类 relay log 模式,直接消费本地文件中的 binlog

CanalLogPositionManager 设计

cola 架构源码 canal架构_canal原理_06

  • 如果 CanalEventStore 选择的是内存模式,可不保留解析位置,下一次 canal 启动时直接依赖 CanalMetaManager 记录的最后一次消费成功的位点即可. (最后一次ack提交的数据位点)
  • 如果 CanalEventStore 选择的是持久化模式,可通过 zookeeper 记录位点信息,canal instance 发生 failover 切换到另一台机器,可通过读取 zookeeper 获取位点信息
  • 可通过实现自己的 CanalLogPositionManager,比如记录位点信息到本地文件/nas 文件实现简单可用的无 HA 模式

CanalHAController类图设计

cola 架构源码 canal架构_阿里巴巴canal_07

  • 失败检测常见方式可定时发送心跳语句到当前链接的数据库,超过一定次数检测失败时,尝试切换到备机
  • 如果有一套数据库主备信息管理系统,当数据库主备切换或者机器下线,推送配置到各个应用节点,HAController 收到后,控制 EventParser 进行链接切换

EventSink类图设计和扩展

cola 架构源码 canal架构_canal解析_08

                           

cola 架构源码 canal架构_canal_09

 

 

  • 数据过滤:支持通配符的过滤模式,表名,字段内容等
  • 数据路由/分发:解决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类图设计和扩展

cola 架构源码 canal架构_canal解析_10

  • 抽象 CanalStoreScavenge , 解决数据的清理,比如定时清理,满了之后清理,每次 ack 清理等
  • CanalEventStore 接口,主要包含 put/get/ack/rollback 的相关接口. put/get 操作会组成一个生产者/消费者模式,每个 store 都会有存储大小设计,存储满了,put 操作会阻塞等待 get 获取数据,所以不会无线占用存储,比如内存大小
  • EventStore 目前实现了 memory 模式,支持按照内存大小和内存记录数进行存储大小限制
  • 后续可开发基于本地文件的存储模式
  • 基于文件存储和内存存储,开发 mixed 模式,做成两级队列,内存 buffer 有空位时,将文件的数据读入到内存 buffer 中(可以通过配置进行配置)
  • mixed 模式实现可以让 canal 落地消费/订阅的模型,取 1 份binlog数据,提供多个客户端消费,消费有快有慢,各自保留消费位点

MetaManager类图设计和扩展

cola 架构源码 canal架构_canal原理_11

  • metaManager 目前支持了多种模式,最顶层 memory 和 zookeeper 模式,然后是 mixed 模式-先写内存,再写zookeeper
  • 可通过实现自己的 CanalMetaManager,比如记录位点信息到本地文件/nas文件,简单可用的无 HA 模式

四:Client设计

在了解具体API之前,需要提前了解下canal client的类设计,这样才可以正确的使用好canal.

cola 架构源码 canal架构_阿里巴巴canal_12

大致分为几部分:

  • ClientIdentity
    canal client和server交互之间的身份标识,目前clientId写死为1001. (目前canal server上的一个instance只能有一个client消费,clientId的设计是为1个instance多client消费模式而预留的,暂时不需要理会)
  • CanalConnector
    SimpleCanalConnector/ClusterCanalConnector : 两种connector的实现,simple针对的是简单的ip直连模式,cluster针对多ip的模式,可依赖CanalNodeAccessStrategy进行failover控制
  • CanalNodeAccessStrategy
    SimpleNodeAccessStrategy/ClusterNodeAccessStrategy:两种failover的实现,simple针对给定的初始ip列表进行failover选择,cluster基于zookeeper上的cluster节点动态选择正在运行的canal server.
  • ClientRunningMonitor/ClientRunningListener/ClientRunningData
    client running相关控制,主要为解决client自身的failover机制。canal client允许同时启动多个canal client,通过running机制,可保证只有一个client在工作,其他client做为冷备. 当运行中的client挂了,running会控制让冷备中的client转为工作模式,这样就可以确保canal client也不会是单点. 保证整个系统的高可用性.

server/client交互协议

cola 架构源码 canal架构_canal原理_13

get/ack/rollback协议介绍:

  • Message getWithoutAck(int batchSize),允许指定batchSize,一次可以获取多条,每次返回的对象为Message,包含的内容为:
    a. batch id 唯一标识
    b. entries 具体的数据对象,可参见下面的数据介绍
  • getWithoutAck(int batchSize, Long timeout, TimeUnit unit),相比于getWithoutAck(int batchSize),允许设定获取数据的timeout超时时间
    a. 拿够batchSize条记录或者超过timeout时间
    b. timeout=0,阻塞等到足够的batchSize
  • 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设计:

cola 架构源码 canal架构_canal解析_14

  • 每次get操作都会在meta中产生一个mark,mark标记会递增,保证运行过程中mark的唯一性
  • 每次的get操作,都会在上一次的mark操作记录的cursor继续往后取,如果mark不存在,则在last ack cursor继续往后取
  • 进行ack时,需要按照mark的顺序进行数序ack,不能跳跃ack. ack会删除当前的mark标记,并将对应的mark位置更新为last ack cursor
  • 一旦出现异常情况,客户端可发起rollback情况,重新置位:删除所有的mark, 清理get请求位置,下次请求会从last ack cursor继续往后取

流式api带来的异步响应模型:

cola 架构源码 canal架构_阿里巴巴canal_15

五:配置信息

                                                         

cola 架构源码 canal架构_canal原理_16

canal配置方式有两种

  1. ManagerCanalInstanceGenerator: 基于manager管理的配置方式,目前alibaba内部配置使用这种方式。大家可以实现CanalConfigClient,连接各自的管理系统,即可完成接入。
  2. SpringCanalInstanceGenerator:基于本地spring xml的配置方式,目前开源版本已经自带该功能所有代码,建议使用

Spring配置

spring配置的原理是将整个配置抽象为两部分:

  • xxxx-instance.xml   (canal组件的配置定义,可以在多个instance配置中共享)
  • xxxx.properties   (每个instance通道都有各自一份定义,因为每个mysql的ip,帐号,密码等信息不会相同)

通过spring的PropertyPlaceholderConfigurer通过机制将其融合,生成一份instance实例对象,每个instance对应的组件都是相互独立的,互不影响

properties配置文件

properties配置分为两部分:

  • canal.properties  (系统根配置文件)
  • instance.properties  (instance级别的配置文件,每个instance一份)

  canal.properties介绍:

canal配置主要分为两部分定义:

1.   instance列表定义, (列出当前server上有多少个instance,每个instance的加载方式是spring/manager等)   以下选一些重要的参数说明一下     

参数名字

参数说明

默认值

canal.auto.scan

开启instance自动扫描

如果配置为true,canal.conf.dir目录下的instance配置变化会自动触发:

a. instance目录新增: 触发instance配置载入,lazy为true时则自动启动

b. instance目录删除:卸载对应instance配置,如已启动则进行关闭

c. instance.properties文件变化:reload instance配置,如已启动自动进行重启操作

true

canal.instance.global.spring.xml

全局的spring配置方式的组件文件

lasspath:spring/memory-instance.xml 

 (spring目录相对于canal.conf.dir)

2.  common参数定义,比如可以将instance.properties的公用参数,抽取放置到这里,这样每个instance启动的时候就可以共享.  【instance.properties配置定义优先级高于canal.properties】以下选一些重要的参数说明一下

参数名字

参数说明

默认值

canal.register.ip

canal server注册到外部zookeeper、admin的ip信息 (针对docker的外部可见ip)


canal.zookeeper.flush.period

canal持久化数据到zookeeper上的更新频率,单位毫秒

1000

canal.instance.memory.batch.mode

canal内存store中数据缓存模式

1. ITEMSIZE : 根据buffer.size进行限制,只限制记录的数量

2. MEMSIZE : 根据buffer.size  * buffer.memunit的大小,限制缓存记录的大小

MEMSIZE

canal.instance.memory.buffer.size

canal内存store中可缓存buffer记录数,需要为2的指数

16384

canal.instance.memory.buffer.memunit

内存记录的单位大小,默认1KB,和buffer.size组合决定最终的内存使用大小

1024

canal.instance.filter.druid.ddl

是否使用druid处理所有的ddl解析来获取库和表名

true

canal.instance.filter.query.dml

是否忽略dml语句

(mysql5.6之后,在row模式下每条DML语句也会记录SQL到binlog中,可参考MySQL文档)

false

canal.instance.parser.parallel

是否开启binlog并行解析模式

(串行解析资源占用少,但性能有瓶颈, 并行解析可以提升近2.5倍+)

true

canal.admin.manager

canal链接canal-admin的地址 (v1.1.4新增)


 

instance.properties介绍:

a. 在canal.properties定义了canal.destinations后,需要在canal.conf.dir对应的目录下建立同名的文件

b. 如果canal.properties未定义instance列表,但开启了canal.auto.scan时

  • server第一次启动时,会自动扫描conf目录下,将文件名做为instance name,启动对应的instance
  • server运行过程中,会根据canal.auto.scan.interval定义的频率,进行扫描
    1. 发现目录有新增,启动新的instance
    2. 发现目录有删除,关闭老的instance
    3. 发现对应目录的instance.properties有变化,重启instance

instance.properties参数列表(部分):

参数名字

参数说明

默认值

canal.instance.mysql.slaveId

mysql集群配置中的serverId概念,需要保证和当前mysql集群中id唯一

(v1.1.x版本之后canal会自动生成,不需要手工指定)


canal.instance.filter.regex

mysql 数据解析关注的表,Perl正则表达式.

多个正则之间以逗号(,)分隔,转义符需要双斜杠(\\)

常见例子:

1.  所有表:.*   or  .*\\..*
2.  canal schema下所有表: canal\\..*
3.  canal下的以canal打头的表:canal\\.canal.*
4.  canal schema下的一张表:canal\\.test1

5.  多个规则组合使用:canal\\..*,mysql.test1,mysql.test2 (逗号分隔)

.*\\..*

canal.instance.filter.black.regex

mysql 数据解析表的黑名单,表达式规则见白名单的规则


canal.instance.master.journal.name

mysql主库链接时起始的binlog文件


canal.instance.master.position

mysql主库链接时起始的binlog偏移量


canal.instance.master.timestamp

mysql主库链接时起始的binlog的时间戳


几点说明:

1.  mysql链接时的起始位置

  • canal.instance.master.journal.name +  canal.instance.master.position :  精确指定一个binlog位点,进行启动
  • canal.instance.master.timestamp :  指定一个时间戳,canal会自动遍历mysql binlog,找到对应时间戳的binlog位点后,进行启动
  • 不指定任何信息:默认从当前数据库的位点,进行启动。(show master status)

2. mysql解析关注表定义

  • 标准的Perl正则,注意转义时需要双斜杠:\\

3. mysql链接的编码

  • 目前canal版本仅支持一个数据库只有一种编码,如果一个库存在多个编码,需要通过filter.regex配置,将其拆分为多个canal instance,为每个instance指定不同的编码

 

instance.xml配置文件

目前默认支持的instance.xml有以下几种:

  1. spring/memory-instance.xml
  2. spring/default-instance.xml
  3. spring/group-instance.xml

在介绍instance配置之前,先了解一下canal如何维护一份增量订阅&消费的关系信息:

  • 解析位点
  • 消费位点 (canal server在接收了客户端的ack后,就会记录客户端提交的最后位点,对应的组件为:CanalMetaManager)

对应的两个位点组件,目前都有几种实现:

  • memory  (memory-instance.xml中使用)
  • zookeeper
  • mixed  
  • period   (default-instance.xml中使用,集合了zookeeper+memory模式,先写内存,定时刷新数据到zookeeper上)

-------------------

memory-instance.xml介绍:

   所有的组件(parser , sink , store)都选择了内存版模式,记录位点的都选择了memory模式,重启后又会回到初始位点进行解析 

   特点:速度最快,依赖最少(不需要zookeeper)

   场景:一般应用在quickstart,或者是出现问题后,进行数据分析的场景,不应该将其应用于生产环境

 

default-instance.xml介绍:

   store选择了内存模式,其余的parser/sink依赖的位点管理选择了持久化模式,目前持久化的方式主要是写入zookeeper,保证数据集群共享. 

   特点:支持HA

   场景:生产环境,集群化部署. 

 

group-instance.xml介绍:

    主要针对需要进行多库合并时,可以将多个物理instance合并为一个逻辑instance,提供客户端访问。

    场景:分库业务。 比如产品数据拆分了4个库,每个库会有一个instance,如果不用group,业务上要消费数据时,需要启动4个客户端,分别链接4个instance实例。使用group后,可以在canal server上合并为一个逻辑instance,只需要启动1个客户端,链接这个逻辑instance即可. 

 

                               放下不是成长的代价,放下就是成长本身