由前几篇文章基本已了解canal的同步机制以及简单的使用,往往在实际业务中,都不是客户端直连服务端,而是通过消息队列来消费,所以本文主要记录使用Spring boot Kafka配合canal监听master数据库数据的变化

canal的配置

1、修改canal 配置文件

canal.zkServers =192.168.111.131:2181,192.168.111.130:2181,192.168.111.128:2181
# 可选项: tcp(默认), kafka, RocketMQ
canal.serverMode = kafka
# ...
# kafka/rocketmq 集群配置: 192.168.1.117:9092,192.168.1.118:9092,192.168.1.119:9092 
canal.mq.servers = 192.168.111.128:9092,192.168.111.130:9092,192.168.111.131:9092
canal.mq.retries = 0
# flagMessage模式下可以调大该值, 但不要超过MQ消息体大小上限
canal.mq.batchSize = 16384
canal.mq.maxRequestSize = 1048576
# flatMessage模式下请将该值改大, 建议50-200
canal.mq.lingerMs = 1
canal.mq.bufferMemory = 33554432
# Canal的batch size, 默认50K, 由于kafka最大消息体限制请勿超过1M(900K以下)
canal.mq.canalBatchSize = 50
# Canal get数据的超时时间, 单位: 毫秒, 空为不限超时
canal.mq.canalGetTimeout = 100
# 是否为flat json格式对象
canal.mq.flatMessage = false
canal.mq.compressionType = none
canal.mq.acks = all
# kafka消息投递是否使用事务
canal.mq.transaction = false

2、修改instance 配置文件

#  按需修改成自己的数据库信息
#################################################
...
canal.instance.master.address=192.168.111.130:3306
# username/password,数据库的用户名和密码
...
canal.instance.dbUsername = root
canal.instance.dbPassword = 123456
#监听所有的库
canal.instance.filter.regex=.*\\..*
...
# mq config
canal.mq.topic=canalKafkaTopic
# dynamic topic route by schema or table regex
canal.mq.dynamicTopic=canalKafkaTopic001:canal
canal.mq.partition=0

# hash partition config
#canal.mq.partitionsNum=3
#库名.表名: 唯一主键,多个表之间用逗号分隔
#canal.mq.partitionHash=mytest.person:id,mytest.role:id

以上配置表明监听所有的表,canal库下所有的表数据发送到canalKafkaTopic001 Topic上,其他库发送到默认的canalKafkaTopic上

启动后,去kafka-eagle上查看topic如下图所示

 

kafka 消费消息 ack_kafka 消费消息 ack

mq相关参数说明

参数名

参数说明

默认值

canal.mq.servers

kafka为bootstrap.servers

rocketMQ中为nameserver列表

127.0.0.1:6667

canal.mq.retries

发送失败重试次数

0

canal.mq.batchSize

kafka为ProducerConfig.BATCH_SIZE_CONFIG rocketMQ无意义

16384

canal.mq.maxRequestSize

kafka为ProducerConfig.MAX_REQUEST_SIZE_CONFIG rocketMQ无意义

1048576

canal.mq.lingerMs

kafka为ProducerConfig.LINGER_MS_CONFIG , 如果是flatMessage格式建议将该值调大, 如: 200

rocketMQ无意义

1

canal.mq.bufferMemory

kafka为ProducerConfig.BUFFER_MEMORY_CONFIG rocketMQ无意义

33554432

canal.mq.acks

kafka为ProducerConfig.ACKS_CONFIG rocketMQ无意义

all

canal.mq.kafka.kerberos.enable

kafka为ProducerConfig.ACKS_CONFIG rocketMQ无意义

false

canal.mq.kafka.kerberos.krb5FilePath

kafka kerberos认证

rocketMQ无意义

../conf/kerberos/krb5.conf

canal.mq.kafka.kerberos.jaasFilePath

kafka kerberos认证

rocketMQ无意义

../conf/kerberos/jaas.conf

canal.mq.producerGroup

kafka无意义

rocketMQ为ProducerGroup名

Canal-Producer

canal.mq.accessChannel

kafka无意义

rocketMQ为channel模式,如果为aliyun则配置为cloud

local

---

---

---

canal.mq.vhost=

rabbitMQ配置


canal.mq.exchange=

rabbitMQ配置


canal.mq.username=

rabbitMQ配置


canal.mq.password=

rabbitMQ配置


canal.mq.aliyunuid=

rabbitMQ配置


---

---

---

canal.mq.canalBatchSize

获取canal数据的批次大小

50

canal.mq.canalGetTimeout

获取canal数据的超时时间

100

canal.mq.parallelThreadSize

mq数据转换并行处理的并发度

8

canal.mq.flatMessage

是否为json格式

如果设置为false,对应MQ收到的消息为protobuf格式

需要通过CanalMessageDeserializer进行解码

false

---

---

---

canal.mq.topic

mq里的topic名


canal.mq.dynamicTopic

mq里的动态topic规则, 1.1.3版本支持


canal.mq.partition

单队列模式的分区下标,

1

canal.mq.partitionsNum

# 以下两个参数配置与canal.mq.partition互斥。配置以下两个参数可以使数据发送至云消息队列 Kafka 版Topic的不同分区


canal.mq.partitionHash

散列规则定义

库名.表名 : 唯一主键,比如mytest.person: id

1.1.3版本支持新语法,见下文


canal.mq.dynamicTopic 表达式说明

canal 1.1.3版本之后, 支持配置格式:schema 或 schema.table,多个配置之间使用逗号或分号分隔

  • 例子1:test\\.test 指定匹配的单表,发送到以test_test为名字的topic上
  • 例子2:.*\\..* 匹配所有表,则每个表都会发送到各自表名的topic上
  • 例子3:test 指定匹配对应的库,一个库的所有表都会发送到库名的topic上
  • 例子4:test\\..* 指定匹配的表达式,针对匹配的表会发送到各自表名的topic上
  • 例子5:test,test1\\.test1,指定多个表达式,会将test库的表都发送到test的topic上,test1\\.test1的表发送到对应的test1_test1 topic上,其余的表发送到默认的canal.mq.topic值

为满足更大的灵活性,允许对匹配条件的规则指定发送的topic名字,配置格式:topicName:schema 或 topicName:schema.table

  • 例子1: test:test\\.test 指定匹配的单表,发送到以test为名字的topic上
  • 例子2: test:.*\\..* 匹配所有表,因为有指定topic,则每个表都会发送到test的topic下
  • 例子3: test:test 指定匹配对应的库,一个库的所有表都会发送到test的topic下
  • 例子4:testA:test\\..* 指定匹配的表达式,针对匹配的表会发送到testA的topic下
  • 例子5:test0:test,test1:test1\\.test1,指定多个表达式,会将test库的表都发送到test0的topic下,test1\\.test1的表发送到对应的test1的topic下,其余的表发送到默认的canal.mq.topic值

大家可以结合自己的业务需求,设置匹配规则,建议MQ开启自动创建topic的能力

canal.mq.partitionHash 表达式说明

canal 1.1.3版本之后, 支持配置格式:schema.table:pk1^pk2,多个配置之间使用逗号分隔

  • 例子1:test\\.test:pk1^pk2 指定匹配的单表,对应的hash字段为pk1 + pk2
  • 例子2:.*\\..*:id 正则匹配,指定所有正则匹配的表对应的hash字段为id
  • 例子3:.*\\..*:$pk$ 正则匹配,指定所有正则匹配的表对应的hash字段为表主键(自动查找)
  • 例子4: 匹配规则啥都不写,则默认发到0这个partition上
  • 例子5:.*\\..* ,不指定pk信息的正则匹配,将所有正则匹配的表,对应的hash字段为表名
  • 按表hash: 一张表的所有数据可以发到同一个分区,不同表之间会做散列 (会有热点表分区过大问题)
  • 例子6: test\\.test:id,.\\..* , 针对test的表按照id散列,其余的表按照table散列

注意:大家可以结合自己的业务需求,设置匹配规则,多条匹配规则之间是按照顺序进行匹配(命中一条规则就返回)

mq顺序性问题

binlog本身是有序的,写入到mq之后如何保障顺序是很多人会比较关注,在issue里也有非常多人咨询了类似的问题,这里做一个统一的解答

  1. canal目前选择支持的kafka/rocketmq,本质上都是基于本地文件的方式来支持了分区级的顺序消息的能力,也就是binlog写入mq是可以有一些顺序性保障,这个取决于用户的一些参数选择
  2. canal支持MQ数据的几种路由方式:单topic单分区,单topic多分区、多topic单分区、多topic多分区
  • canal.mq.dynamicTopic,主要控制是否是单topic还是多topic,针对命中条件的表可以发到表名对应的topic、库名对应的topic、默认topic name
  • canal.mq.partitionsNum、canal.mq.partitionHash,主要控制是否多分区以及分区的partition的路由计算,针对命中条件的可以做到按表级做分区、pk级做分区等
  1. canal的消费顺序性,主要取决于描述2中的路由选择,举例说明:
  • 单topic单分区,可以严格保证和binlog一样的顺序性,缺点就是性能比较慢,单分区的性能写入大概在2~3k的TPS
  • 多topic单分区,可以保证表级别的顺序性,一张表或者一个库的所有数据都写入到一个topic的单分区中,可以保证有序性,针对热点表也存在写入分区的性能问题
  • 单topic、多topic的多分区,如果用户选择的是指定table的方式,那和第二部分一样,保障的是表级别的顺序性(存在热点表写入分区的性能问题),如果用户选择的是指定pk hash的方式,那只能保障的是一个pk的多次binlog顺序性 ** pk hash的方式需要业务权衡,这里性能会最好,但如果业务上有pk变更或者对多pk数据有顺序性依赖,就会产生业务处理错乱的情况. 如果有pk变更,pk变更前和变更后的值会落在不同的分区里,业务消费就会有先后顺序的问题,需要注意

Spring Boot+Kakfa消费

配置文件如下

kafka:
    bootstrap-servers: 192.168.111.128:9092,192.168.111.130:9092,192.168.111.131:9092
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      retries: 0
    consumer:
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      auto-offset-reset: latest
      enable-auto-commit: false
      batch:
        size: 2  #自定义 每次消费数据条数

消费者:我们写两个监听,分别监听上面配置的两个topic

kafka 消费消息 ack_kafka_02

测试:创建canal数据库,并插入数据

kafka 消费消息 ack_spring boot_03

创建canal01的数据库,并插入数据

kafka 消费消息 ack_kafka_04

能接受彼此的不完美,也许就是一种完美了

kafka 消费消息 ack_canal kafka_05