使用debezium-connector-jdbc组件完成数据同步(io.debezium.connector.jdbc.JdbcSinkConnector)

1.情景展示

在网络上几乎找不到关于debezium-connector-jdbc插件的博客文章,基本上都在吹io.confluent.connect.jdbc.JdbcSinkConnector,由于一开始对数据同步插件并不了解,导致自己走了不少弯路。

生产数据组件:debezium-connector-mysql、debezium-connector-oracle等数据库组件,通过Source Connector完成了将表数据至kafka的推送工作。

消费数据组件:confluentinc-kafka-connect-jdbc、debezium-connector-jdbc等jdbc组件,通过Sink Connector拉取kafka数据推送到数据库当中。

如果你用的是debezium的官方组件来捕获表数据的变更记录的话,千万不要使用confluentinc-kafka-connect-jdbc插件,而应该使用debezium-connector-jdbc插件。

debezium-connector-jdbc插件可以和debezium提供的debezium-connector-mysql、debezium-connector-oracle-2.5.0等数据库组件,几乎实现了数据的无缝对接

前面我们已经实现了将表数据到kafka的推送,下面来说如何将这些数据从kafka读出来并推送到数据库当中。

2.准备工作

插件下载

https://debezium.io/releases/

这个页面会展示当前debezium的最新版本,一般情况下,我们直接采用最新版就可以了。

以2.5版本为例,进行举例说明:

我们点击“More info”按钮,会跳转到此版本详情页:https://debezium.io/releases/2.5/

首先,映入眼帘的是:运行此插件所需的java版本,kafka版本,以及其所支持的数据库类型、版本号和驱动版本。

往下走,看到的是:Documentation

也就是说明文档,点击“Documentation”按钮,将会跳转到当前版本对应的说明文档页:https://debezium.io/documentation/reference/2.5/

然后找到:Getting Started-->点击"Installation",会跳转到插件安装界面:https://debezium.io/documentation/reference/2.5/install.html

debezium插件列表如下:

mysql插件:https://repo1.maven.org/maven2/io/debezium/debezium-connector-mysql/2.5.0.Final/debezium-connector-mysql-2.5.0.Final-plugin.tar.gz

oracle插件:https://repo1.maven.org/maven2/io/debezium/debezium-connector-oracle/2.5.0.Final/debezium-connector-oracle-2.5.0.Final-plugin.tar.gz

SQLserver插件:https://repo1.maven.org/maven2/io/debezium/debezium-connector-sqlserver/2.5.0.Final/debezium-connector-sqlserver-2.5.0.Final-plugin.tar.gz

jdbc插件:https://repo1.maven.org/maven2/io/debezium/debezium-connector-jdbc/$2.5.0.Final/debezium-connector-jdbc-2.5.0.Final-plugin.tar.gz

插件下载说明:

当你发现插件下载失败的时候,需要检查下载地址当中是否存在$,如果存在将其删掉,才是正确的地址。

如上面的jdbc插件,由于多了一个$,导致下载失败,我们把它去掉再下载就可以了:https://repo1.maven.org/maven2/io/debezium/debezium-connector-jdbc/2.5.0.Final/debezium-connector-jdbc-2.5.0.Final-plugin.tar.gz

插件用法参数说明

点击不同的数据库,将会跳转到对应的参数说明页。

mysql:https://debezium.io/documentation/reference/2.5/connectors/mysql.html

oracle:https://debezium.io/documentation/reference/2.5/connectors/oracle.html

jdbc:https://debezium.io/documentation/reference/2.5/connectors/jdbc.html

如何下载历史插件版本?

在说明文档页,我们点击切换说明文档的版本号,就能看到历史版本信息。

以2.0进行举例说明

如何下载2.0版的插件呢?

我们点击“2.0”,将会切换到2.0版的说明页:https://debezium.io/documentation/reference/2.0/index.html

我们点击"Mysql Connector plugin archive",将会自动下载debezium-mysql-2.0.1.Final,下载地址为:https://repo1.maven.org/maven2/io/debezium/debezium-connector-mysql/2.0.1.Final/debezium-connector-mysql-2.0.1.Final-plugin.tar.gz

插件安装

下载成功后,进行解压。

来到KAFKA_HOME目录下,创建一个plugins目录。

并将刚才解压的插件移到plugins目录下。

最好把版本号也加上。

另外的话,jdbc插件的版本号最好和其余数据库插件的版本号保持一致。

参数说明

说明文档:https://debezium.io/documentation/reference/2.5/connectors/jdbc.html

2.5.0版本常用参数说明

具体的数据结构,下面有。 

name属性:代表的是连接器的名称,该名称具有唯一性!(名字随便起,但必须唯一)。

名字最好能让人望文生义,如:debezium-connector-sink-mysql-63-sourceTableName,这一看就知道:

创建的是Sink Connector,用的插件是:debezium-connector-jdbc,源库是mysql以及源表表名。

tasks.max属性:此连接器创建的最大任务数,默认值为1(MySQL 连接器始终使用单个任务,因此不使用此值),数据类型:int。

connector.class属性:Sink Connector的实现类,在这里我们需要填:io.debezium.connector.jdbc.JdbcSinkConnector(它是debezium-connector-jdbc的sink连接器)。

connection.url属性:数据库连接地址。

mysql形如:jdbc:mysql://192.168.0.1:3306/scott?useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true&useTimezone=true&serverTimezone=Asia/Shanghai。

oceanbase形如:与上面保持一致。

oracle形如:jdbc:oracle:thin:@192.168.0.1:1521/orcl。

sqlserver形如:jdbc:sqlserver://192.168.0.1:1433;databaseName=cdc_test_20240524;encrypt=false。

说明:如果目标库是sqlserver,不声明encrypt=false的话,默认会以加密的方式进行jdbc连接。

connection.username属性:数据库用户名,如:scott。

connection.password属性:数据库密码,如:123456。 

topics或topics.regex属性:将要发布的主题的名称前缀,该值具有唯一性(kafka会根据此主题前缀来生成主题名称。消费者需要根据topic名称来订阅数据)。

table.name.format属性:待接收数据的表名(目标表表名,忽略大小写)。

field.include.list属性:待同步的表字段。格式:topics.regex:fieldName,多个使用逗号隔开。

说明1:可以设置只同步部分字段(指定几个字段,就同步几个字段),如果不带此参数,将默认自动同步全部字段。

说明2:目标表不存在,如果需要其自动创建的话,必须设置此属性。

说明3:表字段名称区分大小写(必须和源表字段名称一模一样)。

insert.mode属性:插入模式,默认值:insert,可选值:[insert, upsert, update],这里我们需要设为:upsert。

upsert代表的含义是:如果主键不存在,则连接器执行 INSERT 操作;如果主键存在,则连接器执行 UPDATE 操作。

当使用upsert模式时,必须指定primary.key.mode和primary.key.fields。

primary.key.mode属性:主键模式,默认值:none,可选值:[none,kafka,record_key,record_value],这里我们需要设为:record_key。

当其值设为kafka时,属性schema.evolution的值不能为basic。

delete.enabled属性:是否将null记录值视为删除,默认值:false。当为true时,primary.key.mode的值必须指定为:record_key。

这里,我们需要将其设为:true。

primary.key.fields属性:主键字段,多个字段使用逗号分割(也就是:支撑联合主键)。

说明1:主键字段名称区分大小写(必须和源表字段名称一模一样)。

说明2:源表与目标表的主键必须保持一致。

说明3:它的值依赖于属性primary.key.mode(根据源表变更记录捕获的数据存到kafka当中,所以从kafka取数据的时候,它是根据源表记录进行查找的)。

truncate.enabled属性:当出现truncate操作时,是否进行同步(清空表数据),默认值:false。

schema.evolution属性:是否同步表结构更新(当目标表不存在时,会自动创建),默认值:none,可选值:[none, basic]。

none:仅支持DML(insert、update和delete操作);basic:DML+DDL。

说明:如果需要该插件进行自动建表操作,该值必须设为:basic。

errors.log.enable属性:是否显示错误日志,默认值:false。

errors.log.include.messages属性:错误日志是否包含错误信息,默认值:false。

dialect.sqlserver.identity.insert属性:是否允许为SQLSERVER表中的标识列插入显式值,默认值:false。

3.运行

准备工作

启动服务

启动zookeeper,启动kafka,启动kafka connect。

查看所有参数配置(可忽略)

http://localhost:8083/connector-plugins/io.debezium.connector.jdbc.JdbcSinkConnector/config

订阅主题

接口地址:

http://localhost:8083/connectors

请求数据:(oracle订阅mysql数据)

{
	"name": "debezium-connector-sink-oracle-124-tb_project",
	"config": {
		"connector.class": "io.debezium.connector.jdbc.JdbcSinkConnector",
		"table.name.format": "TB_PROJECT",
		"errors.log.include.messages": true,
		"connection.password": "mardon456",
		"primary.key.mode": "record_key",
		"tasks.max": 1,
		"truncate.enabled": true,
		"connection.username": "marydon",
		"topics.regex": "topic-test-124.test.tb_project",
		"delete.enabled": true,
		"primary.key.fields": "id",
		"connection.url": "jdbc:oracle:thin:@192.168.0.1:1521/orcl",
		"insert.mode": "upsert",
		"errors.log.enable": true
	}
}

响应数据:

{
	"name": "debezium-connector-sink-oracle-124-tb_project",
	"type": "sink",
	"config": {
		"connector.class": "io.debezium.connector.jdbc.JdbcSinkConnector",
		"table.name.format": "TB_PROJECT",
		"errors.log.include.messages": "true",
		"connection.password": "mardon456",
		"primary.key.mode": "record_key",
		"tasks.max": "1",
		"truncate.enabled": "true",
		"connection.username": "marydon",
		"topics.regex": "topic-test-124.test.tb_project",
		"delete.enabled": "true",
		"primary.key.fields": "id",
		"connection.url": "jdbc:oracle:thin:@192.168.0.1:1521/orcl",
		"insert.mode": "upsert",
		"errors.log.enable": "true",
		"name": "debezium-connector-sink-oracle-124-tb_project"
	},
	"tasks": [{
		"connector": "debezium-connector-sink-oracle-124-tb_project",
		"task": 0
	}]
}

由于在创建Source Connector时,我们设置的是存量+全量更新,所以,在Sink Connector成功创建后,为了测试数据是否可以正常同步。

我们需要:对源表进行操作(增、删、改)都可以。

初次同步耗时较长,需要我们耐心等待

新增操作

当我们对源表进行新增操作后,会发现Kafka Connect窗口输出了:Committing offsets for 1 acknowledged messages。

这就表示Source Connector已经将此表的这个新增数据推送到了kafka的topic-test-124.test.tb_project主题中。

当Kafka Connect窗口出现如下字样时,就表示Sink Connector已经从kafka的topic-test-124.test.tb_project主题中拿到最新数据并且同步到了目标表的目标表中。

 

修改操作

删除操作

6.拓展

关于主题的补充说明

关于主题的生成条件,我在前两篇文章已经说过了。

也就是说:在Source Connector创建之后,鉴于其参数snapshot.mode的设置以及表数据的变化,还有启动所需时间的长短问题,经常会造成:

主题没有创建的情况。

这个时候,Sink Connector的主题该怎么确定呢?

由前面两篇文章,我们已经知道了mysql和Oracle主题的生成规则,分别是:

mysql:topic.prefix.databaseName.tableName;

Oracle:topic.prefix.userName.tableName。

由于往往拿不到主题(主题尚未被创建),所以我们在创建Sink Connector时,需要提前指定要订阅的topic

那么问题来了,主题先被订阅后被创建,能不能Sink Connector能不能从Kafka中读取数据呢?

只要你创建的Source Connector和Sink Connector没有报错,在运行过程中也没有报错,并且保证订阅的主题名称和Source Connector生成的主题名称完全一致,

事实上是可以正常接收数据的。

只不过,首次订阅数据完成同步的过程比较慢(通常需要10-30分钟),耐心等待就可以了。

当目标库目标表不存在时,自动建表

前提条件:

需要指定参数schema.evolution,并将其值设为basic;

需要指定参数field.include.list,格式为:topicName:fieldName。

源库:mysql&目标库:mysql

(mysql-->mysql自动建表)

{
	"name": "debezium-connector-sink-mysql-122-tb_project",
	"config": {
		"connector.class": "io.debezium.connector.jdbc.JdbcSinkConnector",
		"table.name.format": "TB_PROJECT",
		"connection.password": "marydon123",
		"primary.key.mode": "record_key",
		"tasks.max": 1,
		"truncate.enabled": true,
		"connection.username": "marydon",
		"topics.regex": "topic-test-122.test.tb_project",
		"delete.enabled": true,
		"field.include.list": "topic-test-122.test.tb_project:create_by,topic-test-122.test.tb_project:create_time,topic-test-122.test.tb_project:id,topic-test-122.test.tb_project:project_begin_time,topic-test-122.test.tb_project:project_code,topic-test-122.test.tb_project:project_company,topic-test-122.test.tb_project:project_end_time,topic-test-122.test.tb_project:project_manager,topic-test-122.test.tb_project:project_name,topic-test-122.test.tb_project:project_submit_time,topic-test-122.test.tb_project:update_by,topic-test-122.test.tb_project:update_time",
		"schema.evolution": "basic",
		"primary.key.fields": "id",
		"connection.url": "jdbc:mysql://192.168.0.1:3306/test2?useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true&useTimezone=true&serverTimezone=Asia/Shanghai",
		"insert.mode": "upsert"
	}
}

由于Source Connector设置的同步类型为存量+增量,所以,在Source Connector和Sink Connector创建成功后,主题并不会立即生成。

我们还需要在源表当中插入一条数据,来触发Source Connector捕获数据并创建主题。

说明:如果是全量+增量的同步类型的话,只要确保源表存在数据,后续耐心等待就可以了,大概需要半个小时的时间。

如上图所示,我在源表当中增加了一条数据。

大概过了半个小时后,目标表被创建了出来,并且插入了一条数据。

双方表结构对比

源表表结构:tb_project

被自动创建的表结构:tb_project

我们会发现自动创建的目标表与源表的字段类型会发生变化。

这一点是我们需要注意的。

新增示例

新增id=9那列

删除示例

把id=8的那列删掉

修改示例

把id=9那列数据进行修改

另外,我们可以发现:

在被自动创建的TB_PROJECT表名被mysql数据库被转换成了小写。

源库:oracle&目标库:oracle

(oracle-->oracle自动建表)

源表:T_PATIENT_ZS

被自动创建的表:T_PATIENT_TEST

目标表字段的数据类型同样无法和源表保持一致。

源库:mysql&目标库:oracle

(mysql-->oracle自动建表)

源表:oauth2_access_token

查看代码
 {
	"name": "debezium-connector-source-mysql-123",
	"config": {
		"connector.class": "io.debezium.connector.mysql.MySqlConnector",
		"errors.log.include.messages": true,
		"database.user": "marydon",
		"database.server.id": 123,
		"schema.history.internal.kafka.bootstrap.servers": "localhost:9092",
		"event.processing.failure.handling.mode": "warn",
		"column.include.list": "test.oauth2_access_token.access_token,test.oauth2_access_token.client_id,test.oauth2_access_token.create_time,test.oauth2_access_token.expire_time,test.oauth2_access_token.grant_type,test.oauth2_access_token.id,test.oauth2_access_token.scope,test.oauth2_access_token.update_time,test.oauth2_access_token.user_id,test.oauth2_access_token.user_nickname",
		"database.port": "3306",
		"schema.history.internal.store.only.captured.tables.ddl": true,
		"schema.history.internal.store.only.captured.databases.ddl": true,
		"topic.prefix": "topic-test-123",
		"schema.history.internal.kafka.topic": "schema-history-test-123",
		"database.hostname": "192.168.0.1",
		"database.connectionTimeZone": "GMT+8",
		"database.password": "marydon@db",
		"table.include.list": "test.oauth2_access_token",
		"skipped.operations": "none",
		"errors.log.enable": true,
		"database.include.list": "test",
		"snapshot.mode": "initial"
	}
}

被自动创建的表:OAUTH2_ACCESS_TOKEN

查看代码
 {
	"name": "debezium-connector-sink-oracle-123-oauth2_access_token",
	"config": {
		"connector.class": "io.debezium.connector.jdbc.JdbcSinkConnector",
		"table.name.format": "OAUTH2_ACCESS_TOKEN",
		"errors.log.include.messages": true,
		"connection.password": "marydon456",
		"primary.key.mode": "record_key",
		"tasks.max": 1,
		"truncate.enabled": true,
		"connection.username": "mardon",
		"topics.regex": "topic-test-123.test.oauth2_access_token",
		"delete.enabled": true,
		"field.include.list": "topic-test-123.test.oauth2_access_token:access_token,topic-test-123.test.oauth2_access_token:client_id,topic-test-123.test.oauth2_access_token:create_time,topic-test-123.test.oauth2_access_token:expire_time,topic-test-123.test.oauth2_access_token:grant_type,topic-test-123.test.oauth2_access_token:id,topic-test-123.test.oauth2_access_token:scope,topic-test-123.test.oauth2_access_token:update_time,topic-test-123.test.oauth2_access_token:user_id,topic-test-123.test.oauth2_access_token:user_nickname",
		"schema.evolution": "basic",
		"primary.key.fields": "id",
		"connection.url": "jdbc:oracle:thin:@192.168.0.1:1521/orcl",
		"insert.mode": "upsert",
		"errors.log.enable": true
	}
}
源库:oracle&目标库:mysql

(oracle-->mysql自动建表)

源表:BASE_JOB_FRCODE

查看代码
 {
	"name": "debezium-connector-source-oracle-125",
	"config": {
		"connector.class": "io.debezium.connector.oracle.OracleConnector",
		"errors.log.include.messages": true,
		"database.user": "marydon",
		"database.dbname": "orcl",
		"database.server.id": 125,
		"tasks.max": 1,
		"database.url": "jdbc:oracle:thin:@192.168.57.111:1521/orcl",
		"schema.history.internal.kafka.bootstrap.servers": "localhost:9092",
		"event.processing.failure.handling.mode": "warn",
		"column.include.list": "MARYDON.BASE_JOB_FRCODE.CITY_CODE,MARYDON.BASE_JOB_FRCODE.FAREAVER,MARYDON.BASE_JOB_FRCODE.FDATE,MARYDON.BASE_JOB_FRCODE.FRCODE,MARYDON.BASE_JOB_FRCODE.FYEAR,MARYDON.BASE_JOB_FRCODE.STATUS",
		"log.mining.strategy": "online_catalog",
		"database.port": "1521",
		"schema.history.internal.store.only.captured.tables.ddl": true,
		"schema.history.internal.store.only.captured.databases.ddl": true,
		"topic.prefix": "topic-orcl-125",
		"schema.history.internal.kafka.topic": "schema-history-orcl-125",
		"database.hostname": "192.168.0.1",
		"database.password": "marydon456",
		"table.include.list": "MARYDON.BASE_JOB_FRCODE",
		"skipped.operations": "none",
		"errors.log.enable": true,
		"snapshot.mode": "initial"
	}
}

被自动创建的表:base_job_frcode

查看代码
 {
	"name": "debezium-connector-sink-mysql-125-BASE_JOB_FRCODE",
	"config": {
		"connector.class": "io.debezium.connector.jdbc.JdbcSinkConnector",
		"table.name.format": "BASE_JOB_FRCODE",
		"errors.log.include.messages": true,
		"connection.password": "marydon@db",
		"primary.key.mode": "record_key",
		"tasks.max": 1,
		"truncate.enabled": true,
		"connection.username": "marydon",
		"topics.regex": "topic-orcl-125.MARYDON.BASE_JOB_FRCODE",
		"delete.enabled": true,
		"field.include.list": "topic-orcl-125.MARYDON.BASE_JOB_FRCODE:CITY_CODE,topic-orcl-125.MARYDON.BASE_JOB_FRCODE:FAREAVER,topic-orcl-125.MARYDON.BASE_JOB_FRCODE:FDATE,topic-orcl-125.MARYDON.BASE_JOB_FRCODE:FRCODE,topic-orcl-125.MARYDON.BASE_JOB_FRCODE:FYEAR,topic-orcl-125.MARYDON.BASE_JOB_FRCODE:STATUS",
		"schema.evolution": "basic",
		"primary.key.fields": "FRCODE",
		"connection.url": "jdbc:mysql://192.168.0.1:3306/test?useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true&useTimezone=true&serverTimezone=Asia/Shanghai",
		"insert.mode": "upsert",
		"errors.log.enable": true
	}
}

其它补充说明

源表表名和目标表的表名必须保持一致(忽略大小写),否则无法完成数据同步。

源表表字段名称和目标表字段名称必须保持一致(忽略大小写),否则无法完成数据同步。

源表的主键名称和目标表的主键名称必须完全保持一致(大小写也必须保持一致),否则会报错:xx字段不存在(通常指的就是目标表的主键名称和源表的主键名称不一致)。

JdbcSinkConnector支持同步的数据类型有:

当然,官方的jdbc插件也不是万能的,例如:

源表表名与目标表表名不一致;

目标表字段B需要对应源表表字段A,而不是A-->A;

在进行数据同步时,目标表需要增加时间戳字段或者实现假删除等等个性化需求,官方插件就不能用了。

我们只能自己开发kafka connect组件了,具体见文末推荐。

 

posted @ 2024-01-26 19:14  Marydon  阅读(1050)  评论(0编辑  收藏  举报