概述
簡介
canal譯意為水道/管道/溝渠,主要用途是基於 MySQL 資料庫增量日誌解析,提供增量資料訂閱和消費。
基於日誌增量訂閱和消費的業務包括
資料庫映象
資料庫實時備份
索引構建和實時維護(拆分異構索引、倒排索引等)
業務 cache 重新整理
帶業務邏輯的增量資料處理
當前的 canal(1.1.5) 支援源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x
工作原理
MySQL主備複製原理
- MySQL master 將資料變更寫入二進位制日誌( binary log, 其中記錄叫做二進位制日誌事件binary log events,可以通過 show binlog events 進行檢視)
- MySQL slave 將 master 的 binary log events 拷貝到它的中繼日誌(relay log)
- MySQL slave 重放 relay log 中事件,將資料變更反映它自己的資料
canal 工作原理
- canal 模擬 MySQL slave 的互動協議,偽裝自己為 MySQL slave ,向 MySQL master 傳送dump 協議
- MySQL master 收到 dump 請求,開始推送 binary log 給 slave (即 canal )
- canal 解析 binary log 物件(原始為 byte 流)
安裝canal
版本資訊
由於最近需要做mysql資料實時分析,經過比較決定選擇canal作為同步工具。
安裝過程步驟很簡單,但是在具體使用過程中確存在一些問題,官網對此也沒過多的解釋,於是記錄下安裝過程。
軟體 | 版本 | 備註 |
---|---|---|
canal | 1.1.5 | 建議1.1.4+, 引入了WebUI能力, 引入canal-admin工程, 支援面向WebUI的canal動態管理能力, 支援配置、任務、日誌等線上白屏運維能力, |
mysql | 5.6+ | 注意,此處mysql為admin的後設資料庫,並不是我們的資料來源 |
下載canal
# 選定安裝路徑
cd /home
# 下載canal.admin
wget https://github.com/alibaba/canal/releases/download/canal-1.1.5/canal.admin-1.1.5.tar.gz
# 解壓canal-admin
mkdir canal-admin
tar -zxvf canal.admin-1.1.5.tar.gz -C canal-admin/
# 下載canal.deployer
wget https://github.com/alibaba/canal/releases/download/canal-1.1.5/canal.deployer-1.1.5.tar.gz
# 解壓canal-deployer
mkdir canal-deployer
tar -zxvf canal.deployer-1.1.5.tar.gz -C canal-deployer/
配置canal-admin
# 進入配置檔案目錄
cd canal-admin/conf/
# 修改配置檔案
vim application.yml
# 加入以下內容
server:
port: 8189 # web埠,預設8089,但是我的埠已經被佔用改為8189
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
spring.datasource:
address: 10.0.x.x:3306 # 資料庫ip、埠
database: canal_manager # 資料庫名稱,預設
username: canal #使用者,預設
password: canal #密碼,預設
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://${spring.datasource.address}/${spring.datasource.database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false
hikari:
maximum-pool-size: 30
minimum-idle: 1
canal:
adminUser: admin #登陸webui 使用者名稱
adminPasswd: 123456 #登陸密碼,注意要1.1.5版本需要六位數以上密碼,預設密碼設定成admin登陸的時候會提示長度錯誤,預設的canal_manager.sql md5解密出來也是123456
生成admin後設資料表
配置完application.yml,我們需要在後設資料庫生成後設資料資訊。
有兩種方法:
canal-admin/conf/canal_manager.sql的語句
- 在資料庫中逐條執行
canal-admin/conf/canal_manager.sql
的語句 - 在資料庫中source canal_manager.sql
mysql> source /home/canal-admin/conf/canal_manager.sql
啟動canal-admin
cd /home/canal-admin/bin
# 啟動canal-admin
sh startup.sh
# 進入日誌目錄
cd /home/canal-admin/logs
# 檢視日誌
cat admin.log
日誌如下即啟動成功
登陸canal-admin
http://10.0.x.x:8189 # 賬號密碼為application.yml配置的admin 123456
- 登陸後頁面如下
新建叢集
- 新建叢集
- 配置叢集資訊
- 載入模板
配置叢集資訊的時候我們可以通過載入模板,然後進行配置
- 修改內容
修改過的或建議修改的配置用中文標記,其餘的預設
#################################################
######### common argument #############
#################################################
# tcp bind ip
canal.ip = 1 #每個canal server例項的唯一標識
# register ip to zookeeper
canal.register.ip =
canal.port = 11111 #canal server提供socket tcp服務的埠
canal.metrics.pull.port = 11112
# canal instance user/passwd
canal.user = canal
canal.passwd = E3619321C1A937C46A0D8BD1DAC39F93B27D4458
# canal admin config
canal.admin.manager = x-x-DATACENTER04:8189 #admin中配置的10.0.x.x:8189 因為我配置hosts 此處寫x-x-DATACENTER04也可以
canal.admin.port = 11110
canal.admin.user = admin
canal.admin.passwd = 6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9
# admin auto register
#canal.admin.register.auto = true
#canal.admin.register.cluster =
#canal.admin.register.name =
canal.zkServers = x-x-datacenter08,x-x-datacenter09,x-x-datacenter10 #canal server連結zookeeper叢集的連結資訊
# flush data to zk
canal.zookeeper.flush.period = 1000 #canal持久化資料到zookeeper上的更新頻率,單位毫秒
canal.withoutNetty = false
# tcp, kafka, rocketMQ, rabbitMQ
canal.serverMode = kafka
# flush meta cursor/parse position to file
canal.file.data.dir = ${canal.conf.dir} #canal持久化資料到file上的目錄
canal.file.flush.period = 1000
## memory store RingBuffer size, should be Math.pow(2,n)
canal.instance.memory.buffer.size = 16384 #canal記憶體store中可快取buffer記錄數,需要為2的指數
## memory store RingBuffer used memory unit size , default 1kb
canal.instance.memory.buffer.memunit = 1024
## meory store gets mode used MEMSIZE or ITEMSIZE
#canal記憶體store中資料快取模式
# 1. ITEMSIZE : 根據buffer.size進行限制,只限制記錄的數量
# 2. MEMSIZE : 根據buffer.size * buffer.memunit的大小,限制快取記錄的大小
canal.instance.memory.batch.mode = MEMSIZE
canal.instance.memory.rawEntry = true
## detecing config
canal.instance.detecting.enable = false #是否開啟心跳檢查
#canal.instance.detecting.sql = insert into retl.xdual values(1,now()) on duplicate key update x=now()
canal.instance.detecting.sql = select 1 #心跳檢查sql
canal.instance.detecting.interval.time = 3 #心跳檢查頻率,單位秒
canal.instance.detecting.retry.threshold = 3 #心跳檢查失敗重試次數
#非常注意:interval.time * retry.threshold值,應該參考既往DBA同學對資料庫的故障恢復時間,
#“太短”會導致叢集執行態角色“多跳”;“太長”失去了活性檢測的意義,導致叢集的敏感度降低,Consumer斷路可能性增加。
canal.instance.detecting.heartbeatHaEnable = false #心跳檢查失敗後,是否開啟自動mysql自動切換
#說明:比如心跳檢查失敗超過閥值後,如果該配置為true,canal就會自動鏈到mysql備庫獲取binlog資料
# support maximum transaction size, more than the size of the transaction will be cut into multiple transactions delivery
canal.instance.transaction.size = 1024 #支援最大事務大小,將超過事務大小的事務切成多個事務交付
# mysql fallback connected to new master should fallback times
canal.instance.fallbackIntervalInSeconds = 60 #canal發生mysql切換時,在新的mysql庫上查詢 binlog時需要往前查詢的時間,單位秒
# 說明:mysql主備庫可能存在解析延遲或者時鐘不統一,需要回退一段時間,保證資料不丟
# network config
canal.instance.network.receiveBufferSize = 16384 #網路連結引數,SocketOptions.SO_RCVBUF
canal.instance.network.sendBufferSize = 16384 #網路連結引數,SocketOptions.SO_SNDBUF
canal.instance.network.soTimeout = 30 #網路連結引數,SocketOptions.SO_TIMEOUT
# binlog filter config
canal.instance.filter.druid.ddl = true
canal.instance.filter.query.dcl = true #ddl語句是否隔離傳送,開啟隔離可保證每次只返回傳送一條ddl資料,不和其他dml語句混合返回.(otter ddl同步使用)
canal.instance.filter.query.dml = true #是否忽略DML的query語句,比如insert/update/delete table.(mysql5.6的ROW模式可以包含statement模式的query記錄)
canal.instance.filter.query.ddl = true #是否忽略DDL的query語句,比如create table/alater table/drop table/rename table/create index/drop index. (目前支援的ddl型別主要為table級別的操作,create databases/trigger/procedure暫時劃分為dcl型別)
# 注意:上面三個引數預設都是false 如無必要建議設定為true,否則後面你過濾表的時候可能會出現不生效的情況
anal.instance.filter.table.error = true
canal.instance.filter.rows = false
canal.instance.filter.transaction.entry = true # 把事務頭尾過濾
canal.instance.filter.dml.insert = false
canal.instance.filter.dml.update = false
canal.instance.filter.dml.delete = false
# binlog format/image check
#canal.instance.binlog.format = ROW,STATEMENT,MIXED
canal.instance.binlog.format = ROW,STATEMENT,MIXED
canal.instance.binlog.image = FULL,MINIMAL,NOBLOB
# binlog ddl isolation
canal.instance.get.ddl.isolation = false
# parallel parser config
canal.instance.parser.parallel = true
## concurrent thread number, default 60% available processors, suggest not to exceed Runtime.getRuntime().availableProcessors()
#canal.instance.parser.parallelThreadSize = 16
## disruptor ringbuffer size, must be power of 2
canal.instance.parser.parallelBufferSize = 256
# table meta tsdb info #關於時間序列版本
canal.instance.tsdb.enable = true
canal.instance.tsdb.dir = ${canal.file.data.dir:../conf}/${canal.instance.destination:}
canal.instance.tsdb.url = jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
canal.instance.tsdb.dbUsername = canal
canal.instance.tsdb.dbPassword = canal
# dump snapshot interval, default 24 hour
canal.instance.tsdb.snapshot.interval = 24
# purge snapshot expire , default 360 hour(15 days)
canal.instance.tsdb.snapshot.expire = 360
#################################################
######### destinations #############
#################################################
canal.destinations =
# conf root dir
canal.conf.dir = ../conf
# auto scan instance dir add/remove and start/stop instance
# 如果配置為true,canal.conf.dir目錄下的instance配置變化會自動觸發
# a. instance目錄新增: 觸發instance配置載入,lazy為true時則自動啟動
# b. instance目錄刪除:解除安裝對應instance配置,如已啟動則進行關閉
# c. instance.properties檔案變化:reload instance配置,如已啟動自動進行重啟操作
canal.auto.scan = true #開啟instance自動掃描
canal.auto.scan.interval = 5 #instance自動掃描的間隔時間,單位秒
# set this value to 'true' means that when binlog pos not found, skip to latest.
# WARN: pls keep 'false' in production env, or if you know what you want.
canal.auto.reset.latest.pos.mode = false
canal.instance.tsdb.spring.xml = classpath:spring/tsdb/h2-tsdb.xml
#canal.instance.tsdb.spring.xml = classpath:spring/tsdb/mysql-tsdb.xml
canal.instance.global.mode = manager
canal.instance.global.lazy = false #全域性lazy模式
canal.instance.global.manager.address = ${canal.admin.manager}
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
#canal.instance.global.spring.xml = classpath:spring/file-instance.xml
canal.instance.global.spring.xml = classpath:spring/default-instance.xml #此處建議設定為classpath:spring/default-instance.xml
##################################################
######### MQ Properties #############
##################################################
# aliyun ak/sk , support rds/mq
canal.aliyun.accessKey =
canal.aliyun.secretKey =
canal.aliyun.uid=
canal.mq.flatMessage = true
canal.mq.canalBatchSize = 50
canal.mq.canalGetTimeout = 100
# Set this value to "cloud", if you want open message trace feature in aliyun.
canal.mq.accessChannel = local
canal.mq.database.hash = true
canal.mq.send.thread.size = 30
canal.mq.build.thread.size = 8
##################################################
######### Kafka #############
##################################################
kafka.bootstrap.servers = x-x-datacenter03:6667,x-x-datacenter04:6667,x-x-datacenter05:6667 # kafka bootstrap 資訊,可以不填所有的
kafka.acks = all
kafka.compression.type = none
kafka.batch.size = 16384
kafka.linger.ms = 50 #建議設為50
kafka.max.request.size = 1048576
kafka.buffer.memory = 33554432
kafka.max.in.flight.requests.per.connection = 1
kafka.retries = 0
kafka.kerberos.enable = false
kafka.kerberos.krb5.file = "../conf/kerberos/krb5.conf"
kafka.kerberos.jaas.file = "../conf/kerberos/jaas.conf"
##################################################
######### RocketMQ #############
##################################################
rocketmq.producer.group = test
rocketmq.enable.message.trace = false
rocketmq.customized.trace.topic =
rocketmq.namespace =
rocketmq.namesrv.addr = 127.0.0.1:9876
rocketmq.retry.times.when.send.failed = 0
rocketmq.vip.channel.enabled = false
rocketmq.tag =
##################################################
######### RabbitMQ #############
##################################################
rabbitmq.host =
rabbitmq.virtual.host =
rabbitmq.exchange =
rabbitmq.username =
rabbitmq.password =
rabbitmq.deliveryMode =
完成後點選儲存配置
配置canal.deployer
在 x-x-DATACENTER04、x-x-DATACENTER07安裝canal.deployer-1.1.5
這裡我們準備使用叢集模式,只需要關注canal_local.properties。
cd /home/canal-deployer/conf
- 修改配置
vim canal_local.properties
- 配置內容
########################加入以下內容#######################
# register ip
canal.register.ip = xx-xx-DATACENTER04 # 你主機的ip,避免多網路卡帶來的問題
# canal admin config
# canal admin的ip:port
canal.admin.manager = xx-xx-DATACENTER04:8189 # 與admin安裝資訊保持一致
canal.admin.port = 11110
canal.admin.user = admin
canal.admin.passwd = 6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9
# admin auto register
canal.admin.register.auto = true
# 填入之前在admin中建立叢集的叢集名
canal.admin.register.cluster = wt_test # 填我們剛剛在web ui中新建的叢集
canal.admin.register.name =
############################################################
啟動canal.deployer
/home/canal-deployer/bin
sh startup.sh local
# 檢視日誌
cd /home/canal-deployer/logs/canal
cat canal.log
上述步驟在部署canal.deployer的伺服器上執行(
x-x-DATACENTER04、x-x-DATACENTER07
)
出現下面的日誌即表示啟動成功
admin介面檢視會發現多了兩個服務
說明:
server: 代表一個canal執行例項,對應於一個jvm
instance: 對應於一個資料佇列 (1個server對應1..n個instance)
instance模組:
eventParser (資料來源接入,模擬slave協議和master進行互動,協議解析)
eventSink (Parser和Store連結器,進行資料過濾,加工,分發的工作)
eventStore (資料儲存)
metaManager (增量訂閱&消費資訊管理器)
安裝到這一步,安裝的部分已經完成,下面就是如何通過web ui 配置一個同步任務。
建立例項
資料來源mysql配置
前面的原理介紹說過,canal同步的原理是偽裝自己為 MySQL slave ,向 MySQL master 傳送dump 協議
MySQL master 收到 dump 請求,開始推送 binary log 給 slave
canal 再解析 binary log 物件(原始為 byte 流)
所以我們的資料來源也必須開啟了binlog並設定為主庫才行。
資料來源庫開啟binlog
mysql binlog引數配置應該如下
mysql> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON |
+---------------+-------+
若不是該配置則需要開啟binlog
# 編輯/etc/my.cnf
vim /etc/my.cnf
# 加入下面三行
log-bin=mysql-bin # 開啟 binlog
binlog-format=ROW # 選擇 ROW 模式
server_id=1
# 重啟mysql
/usr/mysql/support-files/mysql.server stop
/usr/mysql/support-files/mysql.server start
# 檢視binlog開啟情況
mysql> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON |
+---------------+-------+
建立例項
配置完資料來源,我們就可以開始例項的配置了
- instance 管理 -> 新建instance
- 例項名稱->選擇叢集->載入模板
- 進行如下配置
#################################################
## mysql serverId , v1.0.26+ will autoGen
canal.instance.mysql.slaveId=1 #slaveId 注意不要與資料來源庫的id 一樣
# enable gtid use true/false
canal.instance.gtidon=false
# position info
canal.instance.master.address=172.x.x.x:3306 # 資料來源mysql的ip:port
canal.instance.master.journal.name=
canal.instance.master.position=
canal.instance.master.timestamp=
canal.instance.master.gtid=
# rds oss binlog
canal.instance.rds.accesskey=
canal.instance.rds.secretkey=
canal.instance.rds.instanceId=
# table meta tsdb info
canal.instance.tsdb.enable=true
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/wt_pre
#canal.instance.tsdb.dbUsername=canal
#canal.instance.tsdb.dbPassword=canal
#canal.instance.standby.address =
#canal.instance.standby.journal.name =
#canal.instance.standby.position =
#canal.instance.standby.timestamp =
#canal.instance.standby.gtid=
# username/password
canal.instance.dbUsername=canal # 資料來源mysql的使用者,根據實際設定
canal.instance.dbPassword=canal # 資料來源mysql的密碼,根據實際設定
canal.instance.connectionCharset = UTF-8
canal.instance.defaultDatabaseName = test_db # 預設資料庫,但是好像設定了沒啥用
# enable druid Decrypt database password
canal.instance.enableDruid=false
#canal.instance.pwdPublicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALK4BUxdDltRRE5/zXpVEVPUgunvscYFtEip3pmLlhrWpacX7y7GCMo2/JM6LeHmiiNdH1FWgGCpUfircSwlWKUCAwEAAQ==
# table regex
canal.instance.filter.regex = test_db\\..* # 需要同步的表,這裡配置的意思是test_db下的所有表。
# 但是如果之前配置canal.instance.filter.query.dcl、canal.instance.filter.query.dml、canal.instance.filter.query.ddl 三個引數不設定為true 這個過濾可能失效
# 關於這點後面再稍微解釋下
# table black regex
canal.instance.filter.black.regex= # 同上,只不過這裡是黑名單,不同步的表
# table field filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)
#canal.instance.filter.field=test1.t_product:id/subject/keywords,test2.t_company:id/name/contact/ch # 需要同步的欄位
# table field black filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)
#canal.instance.filter.black.field=test1.t_product:subject/product_image,test2.t_company:id/name/contact/ch # 不需要同步的欄位
# mq config
canal.mq.topic=tpc_all # 預設資料同步的主題,所有未被canal.instance.filter.regex匹配的表都會同步到主題tpc_all中
# dynamic topic route by schema or table regex
canal.mq.dynamicTopic=tpc_test:test_db\\..* #自己指定主題,test_db庫中的所有表都會同步到tpc_test中
canal.mq.partition=0
# hash partition config
canal.mq.partitionsNum=3
#canal.mq.partitionHash=test.table:id^name,.*\\..*
#################################################
儲存配置,返回上一頁面
啟動例項
返回instance管理頁面可以看到新建的例項。
- 操作->啟動
- 檢視日誌
此時消費tpc_test即可看到test_db庫各個表的變化情況
至此配置instance完成
遇到的問題
其實按照上面的步驟應該不會有什麼問題了,畢竟該踩的坑我都踩了。
但是這裡還有一個問題想再做下說明。
關於表過濾失敗的問題
按照官網,我們過濾表只需要進行表的黑白名單配置即可
canal.instance.filter.regex = test_db\\..*
canal.instance.filter.black.regex=
但是實際上在測試過程中發現,黑白名單過濾都沒有生效。
網上一搜很多都說是API覆蓋了這裡的配置,但是實際上我們直接通過WEB UI配置壓根不存在覆蓋的問題。
關於這個問題官方FAQ中有兩個問題與此相關
為什麼INSERT/UPDATE/DELETE被解析為查詢或DDL?
出現這類情況主要原因為收到的binlog就為Query事件,比如:
a. binlog格式為非row模式,通過show variables like 'binlog_format'可以檢視. 針對statement/mixed模式,DML語句都會是以SQL語句存在
b. mysql5.6+之後,在binlog為row模式下,針對DML語句通過一個開關(binlog-rows-query-log-events=true, show variables裡也可以看到該變數),記錄DML的原始SQL,對應binlog事件為RowsQueryLogEvent,同時也有對應的row記錄.
ps. canal可以通過properties設定來過濾:canal.instance.filter.query.dml=true
我為表設定了過濾器,但它不起作用。
官方給了四個排查步驟
- 首先看文件AdminGuide,瞭解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 (逗號分隔)
- 檢查binlog格式,過濾條件只針對row模式的資料有效(ps. mixed/statement因為不解析sql,所以無法準確提取tableName進行過濾)
- 檢查下CanalConnector是否呼叫subscribe(filter)方法;有的話,filter需要和instance.properties的canal.instance.filter.regex一致,否則subscribe的filter會覆蓋instance的配置,如果subscribe的filter是...,那麼相當於你消費了所有的更新資料 【特別注意】
- canal 1.1.3+版本之後,會在日誌裡記錄最後使用的filter條件,可以對比使用的filter看看是否和自己期望的是一致,如果不一致檢查一下第3步
c.a.o.canal.parse.inbound.mysql.dbsync.LogEventConvert - --> init table filter : ^.*\..*$
c.a.o.canal.parse.inbound.mysql.dbsync.LogEventConvert - --> init table black filter :
- 檢查一下歷史的issue列表,很有可能你的提問別人已經遇到過並解決了,比如表示式不對,特別是雙斜槓的問題
好傢伙還真在歷史的issue找到解決辦法,多久的問題了,官方心真大
按上面的說法走一遍流程,好像也沒問題呀,官方拉垮
解決辦法
後面看了一個issue才發現問題。
實際上我們過濾過濾生效了。
根據上面第一個問題,我們知道INSERT/UPDATE/DELETE被解析為Query或DDL語句,發生一條變更會傳送兩個entry過來。
一個entry是query ,一個是具體的更新(insert/update/delete)。
你沒過濾的表會發兩個entry過來 ,但是你過濾的表只會發一個entrytype是query的資訊。
對於query型別好像無法準確提取tableName進行過濾,所以我們配置的黑白名單就失效了
。
猜測是這樣,沒深入研究,畢竟我即使何明.\.. 作為黑名單都失效了
最後是在把cannal.properties中的引數修改一下解決:
canal.instance.filter.transaction.entry = true
canal.instance.filter.query.dcl = true
canal.instance.filter.query.dml = true
canal.instance.filter.query.ddl = true
canal.instance.filter.table.error = true
參考資料
Canal配置檔案詳解
關於canal.instance.filter.regex 設定的問題
FAQ
更多內容請關注【兔八哥雜談】