环境:
win11 x64
jdk1.8
docker
mysql:5.7.36(docker镜像安装)
es7(docker镜像安装)
canal-adapter1.1.5
canal-deployer1.1.6 (直接使用的docker镜像,版本也是1.1.6)
除canal相关的其它软件正常安装就行,下面直接介绍数据同步的相关操作
安装elasticsearch
docker pull elasticsearch:7.16.2
docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" --name='es7162' -d elasticsearch:7.16.2
docker启动es:
然后我们需要配置一下es的信息:
docker exec -ites es7162 /bin/bash
cd config
vi elasticsearch.yml
配置文件:
cluster.name: dailyhub-es
network.host: 0.0.0.0
node.name: node-1
http.port: 9200
http.cors.enabled: true
http.cors.allow-origin: "*"
node.master: true
node.data: true
docker restart es7162 重启es,注意千万别写错配置的信息,否则启动会失败,启动失败是后可以通过docker logs -f es7162查看原因,但也只能重新来了。
然后服务器访问:
// 查询es所有mapping
http://119.45.25.164:9200/_mapping?pretty=true
注意如果是云服务器的话,要在安全组中配置对应的端口开放、还有防火墙啥的,然后安全些的话,还需要给es配合账号密码啥的。
安装canal-server
拉取镜像并启动:
docker pull canal/canal-server:v1.1.6
docker run --name canal115 -p 11111:11111 --link mysql5736:mysql5736 -id canal/canal-server:v1.1.6
修改对应的配置:
docker exec -it canal115 /bin/bash
cd canal-server/conf/example/
vi instance.properties // 修改配置
# 把0改成10,只要不和mysql的id相同就行
canal.instance.mysql.slaveId=10
# 修改成mysql对应的账号密码,mysql5736就是mysql镜像的链接别名
canal.instance.master.address=mysql5736:3306
canal.instance.dbUsername=root
canal.instance.dbPassword=admin
验证配置是否成功:
#首先重启一下canal
docker restart canal115
docker exec -it canal115 /bin/bash
cd canal-server/logs/example/
tail -100f example.log // 查看日志
截图如下,说明已经链接上了mysql主机,此时mysql中的数据变化,都会在canal中有同步。
可以通过Java程序测试有没连接上mysql:
导入canal-client包
<!-- 为了测试canal-server是否连接mysql成功,1.1.5版本少包,所以用1.1.4版本 -->
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.4</version>
</dependency>
- com.markerhub.SimpleCanalClientExample
当mysql的数据更新时候效果如下:/** * 公众号:MarkerHub * * 说明:用于测试canal是否已经连接上了mysql */ public class SimpleCanalClientExample { public static void main(String args[]) { // 创建链接 CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("119.45.25.164", 11111), "example", "", ""); int batchSize = 1000; int emptyCount = 0; try { connector.connect(); connector.subscribe(".*\\..*"); connector.rollback(); int totalEmptyCount = 120; while (emptyCount < totalEmptyCount) { Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据 long batchId = message.getId(); int size = message.getEntries().size(); if (batchId == -1 || size == 0) { emptyCount++; System.out.println("empty count : " + emptyCount); try { Thread.sleep(1000); } catch (InterruptedException e) { } } else { emptyCount = 0; // System.out.printf("message[batchId=%s,size=%s] \n", batchId, size); printEntry(message.getEntries()); } connector.ack(batchId); // 提交确认 // connector.rollback(batchId); // 处理失败, 回滚数据 } System.out.println("empty too many times, exit"); } finally { connector.disconnect(); } } private static void printEntry(List<Entry> entrys) { for (Entry entry : entrys) { if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) { continue; } RowChange rowChage = null; try { rowChage = RowChange.parseFrom(entry.getStoreValue()); } catch (Exception e) { throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(), e); } EventType eventType = rowChage.getEventType(); System.out.println(String.format("================> binlog[%s:%s] , name[%s,%s] , eventType : %s", entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(), entry.getHeader().getSchemaName(), entry.getHeader().getTableName(), eventType)); for (RowData rowData : rowChage.getRowDatasList()) { if (eventType == EventType.DELETE) { printColumn(rowData.getBeforeColumnsList()); } else if (eventType == EventType.INSERT) { printColumn(rowData.getAfterColumnsList()); } else { System.out.println("-------> before"); printColumn(rowData.getBeforeColumnsList()); System.out.println("-------> after"); printColumn(rowData.getAfterColumnsList()); } } } } private static void printColumn(List<Column> columns) { for (Column column : columns) { System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated()); } } }
注意当后面canal-adapter也连接上canal-server后,程序就监听不到数据变化了。
安装canal-adapter 1.1.5
重点来了,这个比较坑,不同版本会出现不同的问题,我这里使用1.1.7和1.1.6都出现一些不同的BUG解决起来都比较麻烦,最终选择1.1.5这个版本
首先说一下,这个docker没有官方镜像,不过有第三方封装的镜像,我没有使用,这里提供一下拉取地址(版本号可根据情况进行选择)
docker pull slpcat/canal-adapter:v1.1.5
docker run --name adapter115 -p 8081:8081 --link mysql5736:mysql5736 --link canal115:canal115 --link es7162:es7162 -d slpcat/canal-adapter:v1.1.5
docker安装和客户端运行配置都是一样的,接下来开始爬坑。
客户端安装(重点)
首先在官网下载
地址:Release v1.1.5 · alibaba/canal (github.com)
下载好后解压到一个没有中文的路径下面(空格也不行)
注意:路径如果不对会影响后边操作
打开conf文件夹下的application.yml文件进行配置
配置修改如下,一些不需要的配置或者注释掉的配置可以删除掉:
server:
port: 8081
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null
canal.conf:
mode: tcp #tcp kafka rocketMQ rabbitMQ
flatMessage: true
zookeeperHosts:
syncBatchSize: 1000
retries: 0
timeout:
accessKey:
secretKey:
consumerProperties:
# canal tcp consumer
canal.tcp.server.host: canal115:11111
canal.tcp.zookeeper.hosts:
canal.tcp.batch.size: 500
canal.tcp.username:
canal.tcp.password:
srcDataSources:
defaultDS:
url: jdbc:mysql://mysql5736:3306/dailyhub?useUnicode=true
username: root
password: admin
canalAdapters:
- instance: example # canal instance Name or mq topic name
groups:
- groupId: g1
outerAdapters:
- name: logger
- name: es7
hosts: es7162:9200 # 127.0.0.1:9200 for rest mode
properties:
mode: rest
# security.auth: test:123456 # only used for rest mode
cluster.name: dailyhub-es
接下来打开conf/es7文件夹,拷贝一份mytest_user.yml重命名dailyhub_collect.yml
配置文件:
dataSourceKey: defaultDS
destination: example
groupId: g1
esMapping:
_index: dailyhub_collect
_id: _id
_type: _doc
upsert: true
# pk: id
sql: "
SELECT
c.id AS _id,
c.user_id AS userId,
c.title AS title,
c.url AS url,
c.note AS note,
c.collected AS collected,
c.created AS created,
c.personal AS personal,
u.username AS username,
u.avatar AS userAvatar
FROM
m_collect c
LEFT JOIN m_user u ON c.user_id = u.id
"
# objFields:
# _labels: array:;
# etlCondition: "where c.c_time>={}"
commitBatch: 3000
注意对于时间类型,在后端一定要使用LocalDateTime或者LocalDate类型,如果是Date类型,需要自己手动设置格式。
到这里基本就配置完成,接下来是遇到的问题,保证所有配置没问题的报错
java.lang.ClassCastException: com.alibaba.druid.pool.DruidDataSource cannot be cast to com.alibaba.druid.pool.DruidDataSource
原因是 druid 包冲突导致的,解决办法如下
下载源码包
wget https://github.com/alibaba/canal/archive/refs/tags/canal-1.1.5.tar.gz
解压后,使用IDEA打开,定位到 client-adapter.escore 模块的 pom.xml 的 druid 更新为
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <scope>provided</scope> </dependency>
更新后,在项目根目录下执行
mvn clean package
然后到 canal-canal-1.1.5/client-adapter/es7x/target 下 将打包好的 client-adapter.es7x-1.1.5-jar-with-dependencies.jar 替换掉 canal-adapter/plugin 下原来的
重启 Canal Adapter,发现日志不再报错,问题成功解决。