原理
canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
canal 解析 binary log 对象(原始为 byte 流)
当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x
安装
-
mysql canall用户赋予权限
GRANT ALL PRIVILEGES ON *.* TO canal@'%' IDENTIFIED BY 'canal';
使用root用户省略 -
创建/打开 /etc/my.cnf 写入一下配置开启binary log
[mysqld] server-id= 1 log-bin= mysql-bin binlog_format= row
-
重启mysql 生效配置
service mysql restart
-
登录mysql检查是否生效
show variables like 'log_%';
-
下载canal
wget https://github.com/alibaba/canal/releases/download/canal-1.1.2/canal.deployer-1.1.2.tar.gz
-
解压到安装目录
-
修改 conf/canal.properties 端口号等信息 默认11111
-
修改conf/example/instance.properties 针对要追踪的 mysql 的实例配置
#canal.instance.standby.address = ################################################# ## mysql serverId , v1.0.26+ will autoGen # slaveId 必须配置, 不能和 mysql 的 id 重复 canal.instance.mysql.slaveId=100 # enable gtid use true/false canal.instance.gtidon=false # position info # mysql 的位置信息 canal.instance.master.address=dw-node03:3306 # table regex # 要监控的表 正则表达式 表示gmallstream数据库下所有的表 canal.instance.filter.regex=gmallstream\\..* # mysql用户名密码 canal.instance.dbUsername=root canal.instance.dbPassword=123456 canal.instance.connectionCharset=utf-8
启动与关闭
启动: bin/starup.sh
会开启CanalLauncher jps进程
关闭: bin/stop.sh
案例:读取数据到kafka
Scala API 写入
package com.chen.gmall.canal
import java.net.InetSocketAddress
import java.util
import com.alibaba.fastjson.JSONObject
import com.alibaba.otter.canal.client.{CanalConnector, CanalConnectors}
import com.alibaba.otter.canal.protocol.CanalEntry.{EntryType, EventType, RowChange}
import com.alibaba.otter.canal.protocol.{CanalEntry, Message}
import com.google.protobuf.ByteString
import scala.collection.JavaConversions._
object CanalClient {
def main(args: Array[String]): Unit = {
val address: InetSocketAddress = new InetSocketAddress("ubuntu", 11111)
// 1. 链接到cannl example 对应conf/example/instance.properties中的实力目录
val connector: CanalConnector = CanalConnectors.newSingleConnector(address, "example", "", "")
// 连接
connector.connect()
// 订阅 gmallstream中所有的表
connector.subscribe("gmallstream.*")
// 读取数据
while (true) {
// 一次拉取最多100条sql语句生成的数据
val msg: Message = connector.get(100)
val entriesOption: Option[util.List[CanalEntry.Entry]] = if (msg.getEntries != null) Some(msg.getEntries) else None
if (entriesOption.isDefined && entriesOption.get.nonEmpty) {
val entries: util.List[CanalEntry.Entry] = entriesOption.get
for (entry <- entries) {
// 保证是ROWDATA类型
if (entry != null && entry.hasEntryType && entry.getEntryType == EntryType.ROWDATA) {
// 从每个entry中获取storeValue
val storeValue: ByteString = entry.getStoreValue
// 使用RowChange 解析
val rowChange: RowChange = RowChange.parseFrom(storeValue)
// 一个storeValue 中有多个rowdatas 每个rowdatas 表示一行数据的变化
val rowdatas: util.List[CanalEntry.RowData] = rowChange.getRowDatasList
// 解析rowdatas 中每行每列数据
handleData(entry.getHeader.getTableName, rowdatas, rowChange.getEventType)
}
}
} else {
println("未拉取到数据 2s之后重新拉去")
Thread.sleep(2000)
}
}
// 2. 读取数据
}
/**
* 解析rowdatas 中每行每列数据
*
*/
def handleData(tableName: String, rowDatas: util.List[CanalEntry.RowData], eventType: CanalEntry.EventType) = {
// 判断是否是要处理的表 只获取insert 数据
if ("order_info" == tableName && eventType == EventType.INSERT && rowDatas != null && rowDatas.nonEmpty) {
for (rowData <- rowDatas) {
val result = new JSONObject()
// 1. 获取变化后的列使用After between为变化前的列
val columeList: util.List[CanalEntry.Column] = rowData.getAfterColumnsList
// 2. 取出一行
for (colume <- columeList) {
val key: String = colume.getName //列名
val value: String = colume.getValue //列的值
result.put(key, value)
}
println(result.toJSONString)
}
}
}
}
配置文件直接写入
https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart