概述
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便是完成数据异构的手法之一。
往期干货:
-
为什么大家都说 SELECT * 功率低?
-
从阿里规约看Spring事务
-
【代码级】全链路压测的全体架构规划,以及5种完成计划(流量染色、数据隔离、接口隔离、零侵入、服务监控)
-
最近沉浸Redis网络模型,无法自拔!总算知道Redis为啥这么快了
-
拆开Netty,我发现了这个8个从来没见过的东西?
-
学习 Shell准没错
-
最近迷上了源码,Tomcat源码,看我这篇就够了
-
为什么这11道JVM面试题这么重要(附答案)
-
芯片战役50年,Intel为什么干不掉AMD?
-
翻了ConcurrentHashMap1.7 和1.8的源码,我总结了它们的首要差异。
-
爱上源码,重学Spring AOP深入
-
9000字,唠唠架构中的规划形式
-
号外号外!Ant Design Mobile 5.6.0最新实用攻略!
-
15755字,解锁MySQL功能优化新姿态
本文由
传智教育博学谷狂野架构师
教研团队发布。假如本文对您有协助,欢迎
关注
和点赞
;假如您有任何主张也可留言谈论
或私信
,您的支撑是我坚持创作的动力。转载请注明出处!