一、Canal介绍

canal可以用来监控数据库数据的变化,从而获得新增数据,或者修改的数据。

canal是应阿里巴巴存在杭州和美国的双机房部署,存在跨机房同步的业务需求而提出的。

阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务。

canal主要用途是基于 MySQL 数据库增量日志解析,并能提供增量数据订阅和消费,应用场景十分丰富。

目前canal主要支持mysql数据库。

github地址:https://github.com/alibaba/canal

版本下载地址:https://github.com/alibaba/canal/releases

文档地址:https://github.com/alibaba/canal/wiki/Docker-QuickStart

Canal应用场景

1)、电商场景下商品、用户实时更新同步到至Elasticsearch、solr等搜索引擎;
2)、价格、库存发生变更实时同步到redis;
3)、数据库异地备份、数据同步;
4)、代替使用轮询数据库方式来监控数据库变更,有效改善轮询耗费数据库资源。

MySQL主从复制原理

1)、MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)
2)、MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)
3)、MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据

Canal工作原理

1)、canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
2)、MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
3)、canal 解析 binary log 对象(原始为 byte 流)

二、环境部署

我的环境:mysql8.0.26,es6.2.1,canal-adapter 1.1.5,canal-deployer1.1.5

 1、mysql开启binlog模式

(1)查看当前mysql是否开启binlog模式。

SHOW VARIABLES LIKE '%log_bin%'

如果 log_bin的值为OFF是未开启,为ON是已开启。binlog开启之后,mysql才会对它内部的添加修改删除操作都记录到binlog日志中。

(2)修改/etc/my.cnf 需要开启binlog模式。

修改mysql配置文件,找到 MySQL 配置文件的位置

Linux: /etc/my.cn,如果/etc 目录下没有,可以通过 locate my.cnf 查找位置

[mysqld]
log-bin=mysql-bin
binlog-format=ROW
server_id=1

Windows: \my.ini,在 mysql 的配置文件下,修改配置

在[mysqld] 区块

log-bin=mysql-bin #添加这一行就ok
binlog-format=ROW #选择row模式
server_id=1 #配置mysql replaction需要定义,不能和canal的slaveId重复
binlog-do-db=canal_manager # 需要监控的数据库名称

注意:(1)、监控的数据库名称一定要正确。(2)、server_id配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复

查看值得配置是否开启

SHOW VARIABLES LIKE 'binlog_format'; -- 结果应该是ROW
SHOW VARIABLES LIKE 'log_bin'; -- 结果应该是 ON
SHOW VARIABLES LIKE '%log%'; -- 所有binlog信息

(1)这个表示binlog日志的前缀是 mysql-bin , 以后生成的日志文件就是mysql-bin.123456 的文件后面的数字按顺序生成,每次 mysql 重启或者到达单个文件大小的阈值时,新生一个文件,按顺序编号。

(2)mysql binlog 的格式有三种,

binlog_format= statement|mixed|row

◼ statement

 语句级,binlog 会记录每次一执行写操作的语句。相对 row 模式节省空间,但是可能产生不一致性,比如
update tt set create_date=now()

如果用 binlog 日志进行恢复,由于执行时间不同可能产生的数据不同。

优点: 节省空间

缺点: 有可能造成数据不一致。

◼ row

行级, binlog 会记录每次操作后每行记录的变化。

优点:保持数据的绝对一致性。因为不管 sql 是什么,引用了什么函数,只记录执行后的效果。

缺点:占用较大空间。

◼ mixed

statement 的升级版,一定程度上解决了,因为一些情况而造成的 statement 模式不一致问题.

默认还是 statement,在某些情况下譬如:当函数中包含 UUID() 时; 包含 AUTO_INCREMENT 字段的表被更新时; 执行 INSERT DELAYED 语句时;用 UDF 时;会按照 ROW 的方式进行处理。

优点:节省空间,同时兼顾了一定的一致性。

缺点:还有些极个别情况依旧会造成不一致,另外 statement 和 mixed 对于需要对 binlog 的监控的情况都不方便。

综合上面对比,Cannel 想做监控分析,选择 row 格式比较合适

(3)、重启mysql服务

(4)、创建账号 用于测试使用

进入mysql

mysql -h localhost -u root -p

使用root账号创建用户并授予权限

create user canal@'%' IDENTIFIED by 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%';
FLUSH PRIVILEGES;

结果:

三、Canal安装

参考文档:https://github.com/alibaba/canal/wiki/QuickStart

windows下载:https://github.com/alibaba/canal/releases/

canal1.1.6版本并不能兼容java8,只能用低版本的canal1.1.5

百度网盘下载地址:

链接:https://pan.baidu.com/s/1vRjd-Ra68cI8BmxDNUNi5w 
提取码:d01u 
--来自百度网盘超级会员V5的分享

建议下载:canal.deployer-1.1.5-SNAPSHOT.tar.gz和canal.adapter-1.1.5-SNAPSHOT.tar.gz

(1)、修改 exmaple下的实例配置

编辑instance.properties,

配置要监听的数据库服务地址和监听数据变化的数据库以及表,修改如下:

## mysql serverId , v1.0.26+ will autoGen
canal.instance.mysql.slaveId=8888

# enable gtid use true/false
canal.instance.gtidon=false

# position info
canal.instance.master.address=127.0.0.1:3306
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/canal_tsdb
#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
canal.instance.dbPassword=canal
canal.instance.connectionCharset = UTF-8
# enable druid Decrypt database password
canal.instance.enableDruid=false
#canal.instance.pwdPublicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALK4BUxdDltRRE5/zXpVEVPUgunvscYFtEip3pmLlhrWpacX7y7GCMo2/JM6LeHmiiNdH1FWgGCpUfircSwlWKUCAwEAAQ==

# table regex
canal.instance.filter.regex=canal_manager\\..*
# table black regex
canal.instance.filter.black.regex=mysql\\.slave_.*
# 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=example
# dynamic topic route by schema or table regex
#canal.mq.dynamicTopic=mytest1.user,mytest2\\..*,.*\\..*
canal.mq.partition=0

默认就是我们前面授权的 canal

指定监听数据库表的配置如下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 (逗号分隔)
注意:此过滤条件只针对row模式的数据有效(ps. mixed/statement因为不解析sql,所以无法准确提取tableName进行过滤)

(2)、配置canal,打开canal.properties文件

 将它的id属性修改成和mysql数据库中server-id不同的值,如下图:

canal.id = 3
# tcp bind ip
canal.ip = 127.0.0.1
# register ip to zookeeper
canal.register.ip =
canal.port = 11111
canal.metrics.pull.port = 11112

如果没有特殊需求的单机,可以不配置。这里可以配置端口号、集群(集群依赖zk,需要配置zk地址),如图。

canal.id = 3
# tcp bind ip
canal.ip = 127.0.0.1
# register ip to zookeeper
canal.register.ip =
canal.port = 11111
canal.metrics.pull.port = 11112

canal.id不要与mysql的server_id相同。

(3)、进入bin目录下,双击startup.bat即可启动

四、canal-1.1.5实时同步MySQL数据到Elasticsearch

(1)、创建数据库canal_manager和相关的表

https://github.com/alibaba/canal/blob/master/admin/admin-web/src/main/resources/canal_manager.sql

CREATE DATABASE /*!32312 IF NOT EXISTS*/ `canal_manager` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_bin */;

USE `canal_manager`;

SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for canal_adapter_config
-- ----------------------------
DROP TABLE IF EXISTS `canal_adapter_config`;
CREATE TABLE `canal_adapter_config` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `category` varchar(45) NOT NULL,
  `name` varchar(45) NOT NULL,
  `status` varchar(45) DEFAULT NULL,
  `content` text NOT NULL,
  `modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for canal_cluster
-- ----------------------------
DROP TABLE IF EXISTS `canal_cluster`;
CREATE TABLE `canal_cluster` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(63) NOT NULL,
  `zk_hosts` varchar(255) NOT NULL,
  `modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for canal_config
-- ----------------------------
DROP TABLE IF EXISTS `canal_config`;
CREATE TABLE `canal_config` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `cluster_id` bigint(20) DEFAULT NULL,
  `server_id` bigint(20) DEFAULT NULL,
  `name` varchar(45) NOT NULL,
  `status` varchar(45) DEFAULT NULL,
  `content` text NOT NULL,
  `content_md5` varchar(128) NOT NULL,
  `modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `sid_UNIQUE` (`server_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for canal_instance_config
-- ----------------------------
DROP TABLE IF EXISTS `canal_instance_config`;
CREATE TABLE `canal_instance_config` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `cluster_id` bigint(20) DEFAULT NULL,
  `server_id` bigint(20) DEFAULT NULL,
  `name` varchar(45) NOT NULL,
  `status` varchar(45) DEFAULT NULL,
  `content` text NOT NULL,
  `content_md5` varchar(128) DEFAULT NULL,
  `modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for canal_node_server
-- ----------------------------
DROP TABLE IF EXISTS `canal_node_server`;
CREATE TABLE `canal_node_server` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `cluster_id` bigint(20) DEFAULT NULL,
  `name` varchar(63) NOT NULL,
  `ip` varchar(63) NOT NULL,
  `admin_port` int(11) DEFAULT NULL,
  `tcp_port` int(11) DEFAULT NULL,
  `metric_port` int(11) DEFAULT NULL,
  `status` varchar(45) DEFAULT NULL,
  `modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for canal_user
-- ----------------------------
DROP TABLE IF EXISTS `canal_user`;
CREATE TABLE `canal_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(31) NOT NULL,
  `password` varchar(128) NOT NULL,
  `name` varchar(31) NOT NULL,
  `roles` varchar(31) NOT NULL,
  `introduction` varchar(255) DEFAULT NULL,
  `avatar` varchar(255) DEFAULT NULL,
  `creation_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;

-- ----------------------------
-- Records of canal_user
-- ----------------------------
BEGIN;
INSERT INTO `canal_user` VALUES (1, 'admin', '6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9', 'Canal Manager', 'admin', NULL, NULL, '2019-07-14 00:05:28');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

创建表es_test

CREATE TABLE `es_test`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

(2)、创建索引和映射

{
                "properties": {
                    "count": {
                        "type": "text"
                    },
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "text",
                        "store": true,
                        "analyzer": "ik_smart"
                    },
                    "color": {
                        "type": "text"
                    }
                }
            }

五、安装并启动Canal-adapter

要和上面的canal版本一致。下载后修改配置文件application.yml

(1)、解压并修改配置文件 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: -1
  timeout:
  accessKey:
  secretKey:
  consumerProperties:
    # canal tcp consumer
    canal.tcp.server.host: 127.0.0.1:11111
    canal.tcp.zookeeper.hosts:
    canal.tcp.batch.size: 500
    canal.tcp.username:
    canal.tcp.password:
    # kafka consumer
    kafka.bootstrap.servers: 127.0.0.1:9092
    kafka.enable.auto.commit: false
    kafka.auto.commit.interval.ms: 1000
    kafka.auto.offset.reset: latest
    kafka.request.timeout.ms: 40000
    kafka.session.timeout.ms: 30000
    kafka.isolation.level: read_committed
    kafka.max.poll.records: 1000
    # rocketMQ consumer
    rocketmq.namespace:
    rocketmq.namesrv.addr: 127.0.0.1:9876
    rocketmq.batch.size: 1000
    rocketmq.enable.message.trace: false
    rocketmq.customized.trace.topic:
    rocketmq.access.channel:
    rocketmq.subscribe.filter:
    # rabbitMQ consumer
    rabbitmq.host:
    rabbitmq.virtual.host:
    rabbitmq.username:
    rabbitmq.password:
    rabbitmq.resource.ownerId:

  srcDataSources:
    defaultDS:
      url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8
      username: canal
      password: canal
  canalAdapters:
  - instance: example # canal instance Name or mq topic name
    groups:
    - groupId: g1
      outerAdapters:
      - name: logger
#      - name: rdb
#        key: mysql1
#        properties:
#          jdbc.driverClassName: com.mysql.jdbc.Driver
#          jdbc.url: jdbc:mysql://127.0.0.1:3306/mytest2?useUnicode=true
#          jdbc.username: root
#          jdbc.password: 121212
#      - name: rdb
#        key: oracle1
#        properties:
#          jdbc.driverClassName: oracle.jdbc.OracleDriver
#          jdbc.url: jdbc:oracle:thin:@localhost:49161:XE
#          jdbc.username: mytest
#          jdbc.password: m121212
#      - name: rdb
#        key: postgres1
#        properties:
#          jdbc.driverClassName: org.postgresql.Driver
#          jdbc.url: jdbc:postgresql://localhost:5432/postgres
#          jdbc.username: postgres
#          jdbc.password: 121212
#          threads: 1
#          commitSize: 3000
#      - name: hbase
#        properties:
#          hbase.zookeeper.quorum: 127.0.0.1
#          hbase.zookeeper.property.clientPort: 2181
#          zookeeper.znode.parent: /hbase
      - name: es6
        hosts: http://127.0.0.1:9200 # 127.0.0.1:9200 for rest mode
        properties:
          mode: rest # or rest
          # security.auth: test:123456 #  only used for rest mode
          cluster.name: elasticsearch

注意:使用rest时,要加http://,端口为9200.name要选择es6,集群的名称要与es中的一致。

(2)、修改配置文件bootstrap.yml

canal:
  manager:
    jdbc:
      url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8
      username: canal
      password: canal

(3)、用于配置MySQL中的表与Elasticsearch中索引的映射关系;

其他三个没用的yml文件可以删除

dataSourceKey: defaultDS
destination: example
groupId: g1
esMapping:
  _index: es_test
  _type: _doc
  _id: _id
  upsert: true
#  relations:
#    customer_order:
#      name: order
#      parent: customer_id
  sql: "select a.id as _id, a.id, a.count, a.name, a.color from es_test a"
#  skips:
 #   - customer_id
  etlCondition: "where a.id>={}"
  commitBatch: 3000

(4)、启动服务

注意:如果下载的是canal.adapter-1.1.5-SNAPSHOT.tar.gz,则不会出现以下问题。

报错:class com.alibaba.druid.pool.DruidDataSource cannot be cast to class 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/es6x/target 下 将打包好的 client-adapter.es6x-1.1.5-jar-with-dependencies.jar 替换掉 canal-adapter/plugin 下原来的.

重启 Canal Adapter,发现日志不再报错,问题成功解决。

(5)、实时同步

向数据库中插入一条数据

insert into es_test(id,name,color,count) values(1,'钢铁是怎样炼成的','黄色','111');

canal 的 数据同步不是自动的,需要触发!!

触发:http://127.0.0.1:8081/etl/es6/canal_manager.yml?params=1

etl 固定的,es6 之前起的名字必须对应,后续配置文件也在这里面,canal_manager.yml 配置文件名称, "params=1" 同步数据的条件 1 入参

查看head插件

修改数据

update es_test set name = '简爱' where id = 1;

再次触发,查看head插件

删除数据

delete from es_test where id =1;

触发:

查看head插件

发现删除时,无法同步。

如果一个文档从 MySQL 中删除,该操作并不会同步到 ElasticSearch 中。

列举几个我们可以考虑的方案。

方案一:MySQL 中的记录可通过包含 is_deleted 字段以表明该条记录是否有效。一旦发生更新,is_deleted 也会同步更新到 ElasticSearch 中。如果通过这种方式,在执行 MySQL 或 ElasticSearch 查询时,重写查询语句来过滤掉 is_deleted 为 true 的记录。同时,可以通过一些后台进程将 MySQL 和 ElasticSearch 中的这些文档删除。

方案二:应用系统负责 MySQL 和 ElasticSearch 中数据的删除,即应用系统在删除 MySQL 中数据,同时负责也删除 ElasticSearch 中相应的文档。这个就会到程序层面的开发工作。

可以通过 mysql 的 binlog 实现数据库中的得事件(增加、更新、删除)同步,任何事件实时同步到 Elasticsearch。这个相对较复杂了。但好在已经有了开源组件可以用,即阿里的 canal。不过,我个人认为,项目不大,且实时性要求不高,还是用 logstash 比较简单省心。canal 在单表时,还比较简单,如果涉及多表,也不方便,根据需求会有不定量的的开发工作。

  最后,要依据场景选择合适的方案。把它们进行适时地结合:实时性要求高,用 canal 实现;一般场景,logstash 即可;即使是有题主提的删除,也不建议使用 canal,而是用前面提到的几个思路。有能力的话,其实可以针对这两种方案设计一个数据同步框架,以后只要加个配置即可完成

 

 

posted on 2023-01-15 16:00  周文豪  阅读(3854)  评论(0编辑  收藏  举报