共计 8678 个字符,预计需要花费 22 分钟才能阅读完成。
概述
canal 是阿里巴巴旗下的一款开源我的项目,纯 Java 开发。基于数据库增量日志解析,提供增量数据订阅 & 生产,目前次要反对了 MySQL(也反对 mariaDB)。
背景
晚期,阿里巴巴 B2B 公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需要。不过晚期的数据库同步业务,次要是基于 trigger 的形式获取增量变更,不过从 2010 年开始,阿里系公司开始逐渐的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅 & 生产的业务,从此开启了一段新纪元。ps. 目前外部应用的同步,曾经反对 mysql5.x 和 oracle 局部版本的日志解析
基于日志增量订阅 & 生产反对的业务:
- 数据库镜像
- 数据库实时备份
- 多级索引 (卖家和买家各自分库索引)
- search build
- 业务 cache 刷新
- 价格变动等重要业务音讯
以后的 canal 反对源端 MySQL 版本包含 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x
工作原理
Mysql 的 BinLog
它记录了所有的 DDL 和 DML(除了数据查问语句)语句,以事件模式记录,还蕴含语句所执行的耗费的工夫。次要用来备份和数据同步。
binlog 有三种模式:STATEMENT、ROW、MIXED
- STATEMENT 记录的是执行的 sql 语句
- ROW 记录的是实在的行数据记录
- MIXED 记录的是 1 +2,优先依照 1 的模式记录
举例说明
举例来说,上面的 sql
COPYupdate user set age=20
对应 STATEMENT
模式只有一条记录,对应 ROW
模式则有可能有成千上万条记录(取决数据库中的记录数)。
MySQL 主备复制原理
- Slave 下面的 IO 线程连贯上 Master,并申请从指定日志文件的指定地位 (或者从最开始的日志) 之后的日志内容;
- Master 接管到来自 Slave 的 IO 线程的申请后,通过负责复制的 IO 线程依据申请信息读取指定日志指定地位之后的日志信息,返回给 Slave 端的 IO 线程。返回信息中除了日志所蕴含的信息之外,还包含本次返回的信息在 Master 端的 Binary Log 文件的名称以及在 Binary Log 中的地位;
- Slave 的 IO 线程接管到信息后,将接管到的日志内容顺次写入到 Slave 端的 Relay Log 文件 (mysql-relay-bin.xxxxxx) 的最末端,并将读取到的 Master 端的 bin-log 的文件名和地位记录到 master- info 文件中,以便在下一次读取的时候可能分明的高速 Master“我须要从某个 bin-log 的哪个地位开始往后的日志内容,请发给我”
- Slave 的 SQL 线程检测到 Relay Log 中新减少了内容后,会马上解析该 Log 文件中的内容成为在 Master 端实在执行时候的那些可执行的 Query 语句,并在本身执行这些 Query。这样,实际上就是在 Master 端和 Slave 端执行了同样的 Query,所以两端的数据是齐全一样的。
当然这个过程实质上还是存在肯定的提早的。
mysql 的 binlog 文件长这个样子。
COPYmysql-bin.003831
mysql-bin.003840
mysql-bin.003849
mysql-bin.003858
启用 Binlog 留神以下几点:
- Master 主库个别会有多台 Slave 订阅,且 Master 主库要反对业务零碎实时变更操作,服务器资源会有瓶颈;
- 须要同步的数据表肯定要有主键;
canal 可能同步数据的原理
了解了 mysql 的主从同步的机制再来看 canal 就比拟清晰了,canal 次要是听过伪装成 mysql 从 server 来向主 server 拉取数据。
- canal 模仿 mysql slave 的交互协定,假装本人为 mysql slave,向 mysql master 发送 dump 协定
- mysql master 收到 dump 申请,开始推送 binary log 给 slave(也就是 canal)
- canal 解析 binary log 对象(原始为 byte 流)
Canal 架构
canal 的设计理念
canal 的组件化设计十分好,有点相似于 tomcat 的设计。应用组合设计,依赖倒置,面向接口的设计。
canal 的组件
- canal server 这个代表了咱们部署的一个 canal 利用
- canal instance 这个代表了一个 canal server 中的多个 mysql instance , 从这一点阐明一个 canal server 能够收集多个库的数据,在 canal 中叫 destionation。
每个 canal instance 有多个组件形成。在 conf/spring/default-instance.xml 中配置了这些组件。他其实是应用了 spring 的容器来进行这些组件治理的。
instance 蕴含的组件
这里是一个 cannalInstance 工作所蕴含的大组件。截取自
conf/spring/default-instance.xml
COPY<bean id="instance" class="com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring">
<property name="destination" value="${canal.instance.destination}" />
<property name="eventParser">
<ref local="eventParser" />
</property>
<property name="eventSink">
<ref local="eventSink" />
</property>
<property name="eventStore">
<ref local="eventStore" />
</property>
<property name="metaManager">
<ref local="metaManager" />
</property>
<property name="alarmHandler">
<ref local="alarmHandler" />
</property>
</bean>
EventParser 设计
eventParser 最根本的组件,相似于 mysql 从库的 dump 线程,负责从 master 中获取 bin_log
整个 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 地位
EventSink 设计
eventSink 数据的归集,应用设置的 filter 对 bin log 进行过滤,工作的过程如下。
阐明:
数据过滤:反对通配符的过滤模式,表名,字段内容等
数据路由 / 散发:解决 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 设计
eventStore 用来存储 filter 过滤后的数据,canal 目前的数据只在这里存储,工作流程如下
- 目前仅实现了 Memory 内存模式,后续打算减少本地 file 存储,mixed 混合模式
- 借鉴了 Disruptor 的 RingBuffer 的实现思路
定义了 3 个 cursor
- Put : Sink 模块进行数据存储的最初一次写入地位
- Get : 数据订阅获取的最初一次提取地位
- Ack : 数据生产胜利的最初一次生产地位
借鉴 Disruptor 的 RingBuffer 的实现,将 RingBuffer 拉直来看:
实现阐明:
- Put/Get/Ack cursor 用于递增,采纳 long 型存储
- buffer 的 get 操作,通过取余或者与操作。(与操作:cusor & (size – 1) , size 须要为 2 的指数,效率比拟高)
metaManager
metaManager 用来存储一些原数据,比方生产到的游标,以后流动的 server 等信息
alarmHandler
alarmHandler 报警,这个个别状况下就是谬误日志,实践上应该是能够定制成邮件等模式,然而目前不反对
各个组件目前反对的类型
canal 采纳了 spring bean container 的形式来组装一个 canal instance , 目标是为了可能更加灵便。
canal 通过这些组件的选取能够达到不同应用场景的成果,比方单机的话,个别应用 file 来存储 metadata 就行了,HA 的话个别应用 zookeeper 来存储 metadata。
eventParser
eventParser 目前只有三种
- MysqlEventParser 用于解析 mysql 的日志
- GroupEventParser 多个 eventParser 的汇合,实践上是对应了分表的状况,能够通过这个合并到一起
- RdsLocalBinlogEventParser 基于 rds 的 binlog 的复制
eventSink
eventSink 目前只有 EntryEventSink 就是基于 mysql 的 binlog 数据对象的解决操作
eventStore
eventStore 目前只有一种 MemoryEventStoreWithBuffer,外部应用了一个 ringbuffer 也就是说 canal 解析的数据都是存在内存中的,并没有到 zookeeper 当中。
metaManager
metaManager 这个比拟多,其实依据元数据寄存的地位能够分为三大类,memory,file,zookeeper
Canal-HA 机制
canal 是反对 HA 的,其实现机制也是依赖 zookeeper 来实现的,用到的个性有 watcher 和 EPHEMERAL 节点(和 session 生命周期绑定),与 HDFS 的 HA 相似。
canal 的 ha 分为两局部,canal server 和 canal client 别离有对应的 ha 实现
- canal server: 为了缩小对 mysql dump 的申请,不同 server 上的 instance(不同 server 上的雷同 instance)要求同一时间只能有一个处于 running,其余的处于 standby 状态(standby 是 instance 的状态)。
- canal client: 为了保障有序性,一份 instance 同一时间只能由一个 canal client 进行 get/ack/rollback 操作,否则客户端接管无奈保障有序。
server ha 的架构图如下
大抵步骤:
- canal server 要启动某个 canal instance 时都先向 zookeeper_进行一次尝试启动判断_(实现:创立 EPHEMERAL 节点,谁创立胜利就容许谁启动)
- 创立 zookeeper 节点胜利后,对应的 canal server 就启动对应的 canal instance,没有创立胜利的 canal instance 就会处于 standby 状态。
- 一旦 zookeeper 发现 canal server A 创立的 instance 节点 隐没后,立刻告诉其余的 canal server 再次进行步骤 1 的操作,从新选出一个 canal server 启动 instance。
- canal client 每次进行 connect 时,会首先向 zookeeper 询问以后是谁启动了 canal instance,而后和其建设链接,一旦链接不可用,会从新尝试 connect。
Canal Client 的形式和 canal server 形式相似,也是利用 zookeeper 的抢占 EPHEMERAL 节点的形式进行管制.
canal 的工作过程
dump 日志
启动时去 MySQL 进行 dump 操作的 binlog 地位确定
工作的过程。在启动一个 canal instance 的时候,首先启动一个 eventParser 线程来进行数据的 dump 当他去 master 拉取 binlog 的时候须要 binlog 的地位,这个地位的确定是依照如下的程序来确定的(这个中央讲述的是 HA 模式哈)。
- 在启动的时候判断是否应用 zookeeper,如果是 zookeeper, 看是否拿到 cursor (也就是 binlog 的信息),如果可能拿到,把这个信息存到内存中(MemoryLogPositionManager), 而后拿这个信息去 mysql 中 dump binlog
- 通过 1 拿不到的话(个别是 zookeeper 当中每一,比方第一次搭建的时候,或者因为某些起因 zk 中的数据被删除了),就去配置文件配置当中的去拿, 把这个信息存到内存中(MemoryLogPositionManager), 而后拿这个信息去 mysql 中 dump binlog
- 通过 2 仍然没有拿到的话,就去 mysql 中执行一个 sql
show master status
这个语句会显示以后 mysql binlog 最初地位的信息,也就是刚写入的 binlog 所在的地位信息。把这个信息存到内存中(MemoryLogPositionManager), 而后拿这个信息去 mysql 中 dump binlog。
前面的 eventParser 的操作就会以内存中(MemoryLogPositionManager)存储的 binlog 地位去 master 进行 dump 操作了。
mysql 的
show master status
操作
COPYmysql> show master status\G
*************************** 1. row ***************************
File: mysql-bin.000028
Position: 635762367
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set: 18db0532-6a08-11e8-a13e-52540042a113:1-2784514,
318556ef-4e47-11e6-81b6-52540097a9a8:1-30002,
ac5a3780-63ad-11e8-a9ac-52540042a113:1-5,
be44d87c-4f25-11e6-a0a8-525400de9ffd:1-156349782
1 row in set (0.00 sec
归集 (sink) 和存储(store)
数据在 dump 回来之后进行的归集 (sink) 和存储(store)
sink 操作是能够撑持将多个 eventParser 的数据进行过滤 filter
filter 应用的是 instance.properties 中配置的 filter, 当然这个 filter 也能够由 canal 的 client 端在进行 subscribe 的时候进行设置。如果在 client 端进行了设置,那么服务端配置文件 instance.properties 的配置都会生效
sink 之后将过滤后的数据存储到 eventStore 当中去。
目前 eventStore 的实现只有一个 MemoryEventStoreWithBuffer
, 也就是基于内存的 ringbuffer, 应用这个 store 有一个特点,这个 ringbuffer 是基于内存的,大小是有限度的(bufferSize = 16 * 1024
也就是 16M),所以,当 canal 的客户端生产比较慢的时候,ringbuffer 中存满了就会阻塞 sink 操作,那么正读取mysql binlog
的eventParser
线程也会碰壁。
这种设计其实也是有情理的。因为 canal 的操作是 pull
模型,不是producer push
的模型,所以他没必要存储太多数据,这样就能够防止了数据存储和长久化治理的一些问题。使数据管理的复杂度大大降低。
下面这些整个是 canal 的 parser 线程的工作流程,次要对应的就是将数据从 mysql 搞下来,做一些根本的归集和过滤,而后存储到内存中。
binlog 的消费者
canal 从 mysql 订阅了 binlog 当前次要还是想要给消费者应用。那么 binlog 是在什么时候被生产呢。这就是另一条主线了。就像咱们做一个 toC
的零碎,管理系统是必须的,用户应用的 app 或者 web 又是一套,eventParser
线程就像是管理系统,往里面录入根底数据。canal 的 client 就像是 app 端一样,是这些数据的生产方。
binlog 的次要消费者就是 canal 的 client 端。应用的协定是基于 tcp 的 google.protobuf
, 当然 tcp 的模式是 io 多路复用,也就是 nio。当咱们的 client 发动申请之后,canal 的 server 端就会从eventStore
中将数据传输给客户端。依据客户端的 ack 机制,将 binlog
的元数据信息定期同步到 zookeeper
当中。
canal 的目录构造
配置父目录:
在上面能够看到
COPYcanal
├── bin
│ ├── canal.pid
│ ├── startup.bat
│ ├── startup.sh
│ └── stop.sh
└── conf
├── canal.properties
├── gamer --- 目录
├── ww_social --- 目录
├── wother --- 目录
├── nihao --- 目录
├── liveim --- 目录
├── logback.xml
├── spring --- 目录
├── ym --- 目录
└── xrm_ppp --- 目录
这里是全副开展的目录
COPYcanal
├── bin
│ ├── canal.pid
│ ├── startup.bat
│ ├── startup.sh
│ └── stop.sh
└── conf
├── canal.properties
├── game_center
│ └── instance.properties
├── ww_social
│ ├── h2.mv.db
│ ├── h2.trace.db
│ └── instance.properties
├── wwother
│ ├── h2.mv.db
│ └── instance.properties
├── nihao
│ ├── h2.mv.db
│ ├── h2.trace.db
│ └── instance.properties
├── movie
│ ├── h2.mv.db
│ └── instance.properties
├── logback.xml
├── spring
│ ├── default-instance.xml
│ ├── file-instance.xml
│ ├── group-instance.xml
│ ├── local-instance.xml
│ ├── memory-instance.xml
│ └── tsdb
│ ├── h2-tsdb.xml
│ ├── mysql-tsdb.xml
│ ├── sql
│ └── sql-map
└── ym
└── instance.properties
Canal 利用场景
同步缓存 redis/ 全文搜寻 ES
canal 一个常见利用场景是同步缓存 / 全文搜寻,当数据库变更后通过 binlog 进行缓存 /ES 的增量更新。当缓存 /ES 更新呈现问题时,应该回退 binlog 到过来某个地位进行从新同步,并提供全量刷新缓存 /ES 的办法,如下图所示。
下发工作
另一种常见利用场景是下发工作,当数据变更时须要告诉其余依赖零碎。其原理是工作零碎监听数据库变更,而后将变更的数据写入 MQ/kafka 进行工作下发,比方商品数据变更后须要告诉商品详情页、列表页、搜寻页等先关零碎。这种形式能够保证数据下发的精确性,通过 MQ 发送音讯告诉变更缓存是无奈做到这一点的,而且业务零碎中不会散落着各种下发 MQ 的代码,从而实现了下发归集,如下图所示。
数据异构
在大型网站架构中,DB 都会采纳分库分表来解决容量和性能问题,但分库分表之后带来的新问题。比方不同维度的查问或者聚合查问,此时就会十分辣手。个别咱们会通过数据异构机制来解决此问题。
所谓的数据异构,那就是将须要 join 查问的多表依照某一个维度又聚合在一个 DB 中。让你去查问。canal 就是实现数据异构的伎俩之一。
本文由
传智教育博学谷狂野架构师
教研团队公布。如果本文对您有帮忙,欢送
关注
和点赞
;如果您有任何倡议也可留言评论
或私信
,您的反对是我保持创作的能源。转载请注明出处!