Debezium JDBC 连接器是 Kafka Connect 接收器连接器,因此需要 Kafka Connect 运行时。连接器定期轮询其订阅的 Kafka 主题,使用这些主题中的事件,然后将事件写入配置的关系数据库。连接器通过使用 upsert 语义和基本模式演化来支持幂等写入操作。
Debezium JDBC 连接器提供以下功能:
默认情况下,Debezium 源连接器会生成复杂的、分层的更改事件。当 Debezium 连接器与其他 JDBC 接收器连接器实现一起使用时,可能需要应用 ExtractNewRecordState 单消息转换 (SMT) 来展平更改事件的负载,以便接收器实现可以使用它们。如果运行 Debezium JDBC 接收器连接器,则无需部署 SMT,因为 Debezium 接收器连接器可以直接使用本机 Debezium 更改事件,而无需使用转换。
当 JDBC 接收器连接器使用来自 Debezium 源连接器的复杂更改事件时,它会从原始插入或更新事件的后续部分中提取值。当接收器连接器使用删除事件时,不会查阅事件有效负载的任何部分。
重要的:
Debezium JDBC 接收器连接器并非设计用于读取架构更改主题。如果您的源连接器配置为捕获架构更改,请在 JDBC 连接器配置中设置主题或topics.regex 属性,以便连接器不会使用架构更改主题。
Debezium JDBC 接收器连接器保证从 Kafka 主题消耗的事件至少被处理一次。
可以跨多个 Kafka Connect 任务运行 Debezium JDBC 接收器连接器。要跨多个任务运行连接器,请将tasks.max 配置属性设置为希望连接器使用的任务数。 Kafka Connect 运行时启动指定数量的任务,并为每个任务运行一个连接器实例。多个任务可以通过并行读取和处理多个源主题的更改来提高性能。
为了使 Debezium JDBC 接收器连接器能够将数据类型从入站消息字段正确映射到出站消息字段,连接器需要有关源事件中存在的每个字段的数据类型的信息。该连接器支持跨不同数据库方言的各种列类型映射。为了从事件字段中的类型元数据正确转换目标列类型,连接器应用为源数据库定义的数据类型映射。可以通过在源连接器配置中设置 column.propagate.source.type 或 datatype.propagate.source.type 选项来增强连接器解析列数据类型的方式。当启用这些选项时,Debezium 会包含额外的参数元数据,这有助于 JDBC 接收器连接器更准确地解析目标列的数据类型。
为了让 Debezium JDBC 接收器连接器处理来自 Kafka 主题的事件,Kafka 主题消息键(如果存在)必须是原始数据类型或 Struct。此外,源消息的有效负载必须是一个 Struct,该结构要么具有没有嵌套结构类型的扁平结构,要么具有符合 Debezium 复杂的分层结构的嵌套结构布局。
如果 Kafka 主题中的事件结构不遵守这些规则,则必须实现自定义单消息转换,以将源事件的结构转换为可用的格式。
默认情况下,Debezium JDBC 接收器连接器不会将源事件中的任何字段转换为事件的主键。不幸的是,缺乏稳定的主键可能会使事件处理复杂化,具体取决于业务需求,或者接收器连接器使用 upsert 语义时。要定义一致的主键,可以将连接器配置为使用下表中描述的主键模式之一:
模式 | 描述 |
---|---|
none | 创建表时未指定主键字段。 |
kafka | 主键由以下三列组成:__连接主题,__连接分区,__connect_offset这些列的值源自 Kafka 事件的坐标。 |
record_key | 主键由 Kafka 事件的键组成。如果主键是基本类型,请通过设置primary.key.fields属性指定要使用的列的名称。如果主键是结构体类型,则结构体中的字段将映射为主键的列。可以使用primary.key.fields 属性将主键限制为列的子集。 |
record_value | 主键由 Kafka 事件的值组成。由于Kafka事件的值始终是一个Struct,因此默认情况下,值中的所有字段都成为主键的列。要使用主键中的字段子集,请设置primary.key.fields 属性以指定要从中派生主键列的值中以逗号分隔的字段列表。 |
record_header | 主键由 Kafka 事件的标头组成。Kafka 事件的标头可以包含多个标头,每个标头可以是 Struct 或基元数据类型,连接器将这些标头组成一个 Struct。因此,该结构中的所有字段都成为主键的列。要使用主键中的字段子集,请设置primary.key.fields 属性以指定要从中派生主键列的值中以逗号分隔的字段列表。 |
重要:
如果将 Primary.key.mode 设置为 kafka 并将 schema.evolution 设置为 basic,某些数据库方言可能会引发异常。当方言将 STRING 数据类型映射到可变长度字符串数据类型(例如 TEXT 或 CLOB),并且该方言不允许主键列具有无限长度时,会发生此异常。要避免此问题,请在您的环境中应用以下设置:
当使用 DELETE 或逻辑删除事件时,Debezium JDBC 接收器连接器可以删除目标数据库中的行。默认情况下,JDBC 接收器连接器不启用删除模式。
如果希望连接器删除行,则必须在连接器配置中显式设置delete.enabled=true。要使用此模式,还必须将primary.key.fields设置为none以外的值。上述配置是必要的,因为删除是根据主键映射执行的,所以如果目标表没有主键映射,连接器将无法删除行。
Debezium JDBC 接收器连接器可以执行幂等写入,使其能够重复重放相同的记录,并且不会更改最终的数据库状态。
要使连接器能够执行幂等写入,必须显式将连接器的 insert.mode 设置为 upsert。 upsert 操作应用为更新或插入,具体取决于指定的主键是否已存在。
如果主键值已存在,则该操作会更新该行中的值。如果指定的主键值不存在,则插入会添加新行。
每种数据库方言处理幂等写入的方式都不同,因为没有针对 upsert 操作的 SQL 标准。下表显示了 Debezium 支持的数据库方言的 upsert DML 语法:
方言 | 更新语法 |
---|---|
Db2 | MERGE … |
MySQL | INSERT … ON DUPLICATE KEY UPDATE … |
Oracle | MERGE … |
PostgreSQL | INSERT … ON CONFLICT … DO UPDATE SET … |
SQL Server | MERGE … |
可以将以下模式演化模式与 Debezium JDBC 接收器连接器结合使用:
模式 | 描述 |
---|---|
none | 连接器不执行任何 DDL 架构演变。 |
basic | 连接器自动检测事件负载中但目标表中不存在的字段。连接器更改目标表以添加新字段。 |
当 schema.evolution 设置为 basic 时,连接器会根据传入事件的结构自动创建或更改目标数据库表。
当第一次从主题接收事件并且目标表尚不存在时,Debezium JDBC 接收器连接器使用事件的键或记录的模式结构来解析表的列结构。如果启用了架构演化,连接器会在将 DML 事件应用到目标表之前准备并执行 CREATE TABLE SQL 语句。
当 Debezium JDBC 连接器从主题接收事件时,如果记录的模式结构与目标表的模式结构不同,连接器将使用事件的键或其模式结构来识别哪些列是新的,并且必须是新的。添加到数据库表中。如果启用了架构演化,连接器会在将 DML 事件应用到目标表之前准备并执行 ALTER TABLE SQL 语句。由于更改列数据类型、删除列和调整主键可能被视为危险操作,因此禁止连接器执行这些操作。
每个字段的架构决定列是 NULL 还是 NOT NULL。该架构还定义了每列的默认值。如果连接器尝试创建具有可为空性设置或不需要的默认值的表,则必须提前手动创建该表,或者在接收器连接器处理事件之前调整关联字段的架构。要调整为空性设置或默认值,可以引入自定义单消息转换,该转换应用管道中的更改,或修改源数据库中定义的列状态。
字段的数据类型是根据一组预定义的映射来解析的。
重要的:
当向目标数据库中已存在的表的事件结构引入新字段时,必须将新字段定义为可选,或者这些字段必须具有在数据库架构中指定的默认值。如果希望从目标表中删除字段,请使用以下选项之一:
Debezium JDBC 接收器连接器通过构建在目标数据库上执行的 DDL(架构更改)或 DML(数据更改)SQL 语句来使用 Kafka 消息。默认情况下,连接器使用源主题和事件字段的名称作为目标表中的表名称和列名称的基础。构造的 SQL 不会自动用引号分隔标识符以保留原始字符串的大小写。因此,默认情况下,目标数据库中表名或列名的文本大小写完全取决于数据库在未指定大小写时处理名称字符串的方式。
例如,如果目标数据库方言是 Oracle 并且事件的主题是订单,则目标表将创建为 ORDERS,因为当名称不带引号时,Oracle 默认使用大写名称。同样,如果目标数据库方言是 PostgreSQL 并且事件的主题是 ORDERS,则目标表将创建为 order,因为当名称不带引号时 PostgreSQL 默认为小写名称。
要显式保留 Kafka 事件中存在的表名称和字段名称的大小写,请在连接器配置中将 quote.identifiers 属性的值设置为 true。设置此选项后,当传入事件针对名为orders 的主题且目标数据库方言为Oracle 时,连接器将创建一个名为orders 的表,因为构造的SQL 将表的名称定义为“orders”。当连接器创建列名称时,启用引号会导致相同的行为。
Debezium JDBC 接收器连接器通过使用逻辑或原始类型映射系统来解析列的数据类型。基本类型包括整数、浮点数、布尔值、字符串和字节等值。通常,这些类型仅用特定的 Kafka Connect 架构类型代码表示。逻辑数据类型通常是复杂类型,包括具有一组固定字段名称和架构的基于结构的类型等值,或用特定编码表示的值,例如自纪元以来的天数。
以下示例显示了原始数据类型和逻辑数据类型的代表性结构:
原始字段模式
{
"schema": {
"type": "INT64"
}
}
逻辑字段模式
[
"schema": {
"type": "INT64",
"name": "org.apache.kafka.connect.data.Date"
}
]
Kafka Connect 并不是这些复杂逻辑类型的唯一来源。事实上,Debezium 源连接器生成的更改事件具有类似逻辑类型的字段来表示各种不同的数据类型,包括但不限于时间戳、日期甚至 JSON 数据。
Debezium JDBC 接收器连接器使用这些基元和逻辑类型将列的类型解析为 JDBC SQL 代码,该代码表示列的类型。然后,底层 Hibernate 持久性框架使用这些 JDBC SQL 代码将列的类型解析为所使用方言的逻辑数据类型。下表说明了 Kafka Connect 和 JDBC SQL 类型之间以及 Debezium 和 JDBC SQL 类型之间的原始映射和逻辑映射。实际的最终列类型因每种数据库类型而异。
表 1. Kafka Connect 原语和列数据类型之间的映射
原始类型 | JDBC SQL类型 |
---|---|
INT8 | Types.TINYINT |
INT16 | Types.SMALLINT |
INT32 | Types.INTEGER |
INT64 | Types.BIGINT |
FLOAT32 | Types.FLOAT |
FLOAT64 | Types.DOUBLE |
BOOLEAN | Types.BOOLEAN |
STRING | Types.CHAR, Types.NCHAR, Types.VARCHAR, Types.NVARCHAR |
BYTES | Types.VARBINARY |
表 2. Kafka Connect 逻辑类型和列数据类型之间的映射
逻辑类型 | JDBC SQL类型 |
---|---|
org.apache.kafka.connect.data.Decimal | Types.DECIMAL |
org.apache.kafka.connect.data.Date | Types.DATE |
org.apache.kafka.connect.data.Time | Types.TIMESTAMP |
org.apache.kafka.connect.data.Timestamp | Types.TIMESTAMP |
表 3. Debezium 逻辑类型和列数据类型之间的映射
逻辑类型 | JDBC SQL类型 |
---|---|
io.debezium.time.Date | Types.DATE |
io.debezium.time.Time | Types.TIMESTAMP |
io.debezium.time.MicroTime | Types.TIMESTAMP |
io.debezium.time.NanoTime | Types.TIMESTAMP |
io.debezium.time.ZonedTime | Types.TIME_WITH_TIMEZONE |
io.debezium.time.Timestamp | Types.TIMESTAMP |
io.debezium.time.MicroTimestamp | Types.TIMESTAMP |
io.debezium.time.NanoTimestamp | Types.TIMESTAMP |
io.debezium.time.ZonedTimestamp | Types.TIMESTAMP_WITH_TIMEZONE |
io.debezium.data.VariableScaleDecimal | Types.DOUBLE |
重要:如果数据库不支持带时区的时间或时间戳,则映射将解析为不带时区的等效值。
逻辑类型 | MySQL SQL类型 | PostgreSQL SQL类型 | SQL Server SQL类型 |
---|---|---|---|
io.debezium.data.Bits | bit(n) | bit(n) or bit varying | varbinary(n) |
io.debezium.data.Enum | enum | Types.VARCHAR | n/a |
io.debezium.data.Json | json | json | n/a |
io.debezium.data.EnumSet | set | n/a | n/a |
io.debezium.time.Year | year(n) | n/a | n/a |
io.debezium.time.MicroDuration | n/a | interval | n/a |
io.debezium.data.Ltree | n/a | ltree | n/a |
io.debezium.data.Uuid | n/a | uuid | n/a |
io.debezium.data.Xml | n/a | xml | xml |
除了上面的原语和逻辑映射之外,如果更改事件的源是 Debezium 源连接器,则可以通过启用列或数据类型进一步影响列类型的分辨率及其长度、精度和小数位数传播。要强制传播,必须在源连接器配置中设置以下属性之一:
Debezium JDBC 接收器连接器应用具有较高优先级的值。
例如,假设更改事件中包含以下字段架构:
Debezium 更改事件字段架构并启用列或数据类型传播
{
"schema": {
"type": "INT8",
"parameters": {
"__debezium.source.column.type": "TINYINT",
"__debezium.source.column.length": "1"
}
}
}
在前面的示例中,如果未设置架构参数,则 Debezium JDBC 接收器连接器会将此字段映射到 Types.SMALLINT 列类型。 Types.SMALLINT 可以具有不同的逻辑数据库类型,具体取决于数据库方言。对于 MySQL,示例中的列类型转换为没有指定长度的 TINYINT 列类型。如果为源连接器启用了列或数据类型传播,则 Debezium JDBC 接收器连接器将使用映射信息来优化数据类型映射过程并创建类型为 TINYINT(1) 的列。
注意:通常,当源数据库和接收器数据库使用相同类型的数据库时,使用列或数据类型传播的效果要大得多。我们不断寻找改进跨异构数据库映射的方法,当前的类型系统使我们能够根据反馈继续完善这些映射。
安装步骤:
安装详细步骤请参考博主下面这篇技术博客:
通常,可以通过提交指定连接器配置属性的 JSON 请求来注册 Debezium JDBC 连接器。以下示例显示了用于注册 Debezium JDBC 接收器连接器实例的 JSON 请求,该连接器使用最常见的配置设置使用来自名为 order 的主题的事件:
示例:Debezium JDBC 连接器配置
{
"name": "jdbc-connector", 1
"config": {
"connector.class": "io.debezium.connector.jdbc.JdbcSinkConnector", 2
"tasks.max": "1", 3
"connection.url": "jdbc:postgresql://localhost/db", 4
"connection.username": "pguser", 5
"connection.password": "pgpassword", 6
"insert.mode": "upsert", 7
"delete.enabled": "true", 8
"primary.key.mode": "record_key", 9
"schema.evolution": "basic", 10
"database.time_zone": "UTC", 11
"topics": "orders" 12
}
}
有关可以为 Debezium JDBC 连接器设置的配置属性的完整列表。
可以使用 POST 命令将此配置发送到正在运行的 Kafka Connect 服务。该服务记录配置并启动执行以下操作的接收器连接器任务:
Debezium JDBC 接收器连接器具有多个配置属性,可以使用它们来实现满足您需求的连接器行为。许多属性都有默认值。有关属性的信息组织如下:
表 5. 通用属性
属性 | 默认值 | 描述 |
---|---|---|
name | No default | 连接器的唯一名称。如果您在注册连接器时尝试重复使用此名称,则会导致失败。所有 Kafka Connect 连接器都需要此属性。 |
connector.class | No default | 连接器的 Java 类的名称。对于 Debezium JDBC 连接器,指定值 io.debezium.connector.jdbc.JdbcSinkConnector。 |
tasks.max | 1 | 用于此连接器的最大任务数。 |
topics | No default | 要使用的主题列表,以逗号分隔。请勿将此属性与 topic.regex 属性结合使用。 |
topics.regex | No default | 指定要使用的主题的正则表达式。在内部,正则表达式被编译为 java.util.regex.Pattern。请勿将此属性与主题属性结合使用。 |
表 6. JDBC 连接器连接属性
Property | Default | Description |
---|---|---|
connection.url | No default | 用于连接数据库的 JDBC 连接 URL。 |
connection.username | No default | 连接器用于连接到数据库的数据库用户帐户的名称。 |
connection.password | No default | 连接器用于连接数据库的密码。 |
connection.pool.min_size | 5 | 指定池中的最小连接数。 |
connection.pool.max_size | 32 | 指定池维护的最大并发连接数。 |
connection.pool.acquire_increment | 32 | 指定当连接池超过其最大大小时连接器尝试获取的连接数。 |
connection.pool.timeout | 1800 | 指定未使用的连接在被丢弃之前保留的秒数。 |
表 7. JDBC 连接器运行时属性
属性 | 默认值 | 描述 |
---|---|---|
database.time_zone | UTC | 指定插入 JDBC 临时值时使用的时区。 |
delete.enabled | false | 指定连接器是否处理 DELETE 或逻辑删除事件并从数据库中删除相应的行。使用此选项需要将primary.key.mode 设置为record.key。 |
truncate.enabled | false | 指定连接器是否处理 TRUNCATE 事件并从数据库中截断相应的表。 |
insert.mode | insert | 指定用于将事件插入数据库的策略。可以使用以下选项:insert:指定所有事件都应构造基于 INSERT 的 SQL 语句。仅当未使用主键时,或者当您可以确定不会对具有现有主键值的行进行更新时,才使用此选项。update:指定所有事件都应构造基于 UPDATE 的 SQL 语句。仅当您可以确定连接器仅接收适用于现有行的事件时,才使用此选项。upsert:指定连接器使用 upsert 语义将事件添加到表中。也就是说,如果主键不存在,则连接器执行 INSERT 操作,如果主键存在,则连接器执行 UPDATE 操作。当需要幂等写入时,应将连接器配置为使用此选项。 |
primary.key.mode | none | 指定连接器如何解析事件中的主键列。none:指定不创建主键列。kafka:指定连接器使用 Kafka 坐标作为主键列。键坐标是根据事件的主题名称、分区和偏移量定义的,并映射到具有以下名称的列:__连接主题,__连接分区,__connect_offset,record_key:指定主键列源自事件的记录键。如果记录键是原始类型,则需要使用primary.key.fields属性来指定主键列的名称。如果记录键是结构类型,则primary.key.fields属性是可选的,并且可用于将事件键中的列子集指定为表的主键。record_value:指定主键列源自事件的值。您可以设置primary.key.fields属性来将主键定义为事件值的字段子集;否则默认使用所有字段。 |
primary.key.fields | No default | 主键列的名称或从中派生主键的逗号分隔字段列表。当primary.key.mode设置为record_key并且事件的键是原始类型时,预计该属性指定用于键的列名。当 Primary.key.mode 设置为具有非原始键的 record_key 或 record_value 时,预计此属性会指定键或值中的以逗号分隔的字段名称列表。如果primary.key.mode设置为具有非原始键的record_key或record_value,并且未指定此属性,则连接器从记录键或记录值的所有字段派生主键,具体取决于指定的模式。 |
quote.identifiers | false | 指定生成的 SQL 语句是否使用引号来分隔表名和列名。 |
schema.evolution | none | 指定连接器如何演变目标表架构。有关更多信息,请参阅架构演变。可以使用以下选项:none:指定连接器不发展目标架构。basic:指定发生基本进化。连接器通过将传入事件的记录架构与数据库表结构进行比较,将缺失的列添加到表中。 |
table.name.format | ${topic} | 指定一个字符串,该字符串根据事件的主题名称确定目标表名称的格式。占位符 ${topic} 将替换为主题名称。 |
dialect.postgres.postgis.schema | public | 指定安装 PostgreSQL PostGIS 扩展的架构名称。默认为public;但是,如果 PostGIS 扩展安装在另一个模式中,则应使用此属性来指定备用模式名称。 |
dialect.sqlserver.identity.insert | false | 指定连接器是否在对 SQL Server 表的标识列执行 INSERT 或 UPSERT 操作之前自动设置 IDENTITY_INSERT,然后在操作后立即取消设置。当默认设置 (false) 生效时,对表的 IDENTITY 列执行 INSERT 或 UPSERT 操作会导致 SQL 异常。 |
batch.size | 500 | 指定尝试将多少记录一起批处理到目标表中。请注意,如果您在 Connect Worker 属性中将 Consumer.max.poll.records 设置为低于batch.size的值,则批处理将受到consumer.max.poll.records的限制,并且将无法达到所需的batch.size 。您还可以在连接器配置中使用consumer.override.max.poll.records来配置连接器的底层消费者的max.poll.records。 |
field.include.list | empty string | 可选的、以逗号分隔的字段名称列表,与要包含在更改事件值中的字段的完全限定名称相匹配。字段的完全限定名称的格式为 fieldName 或 topicName:_fieldName_。如果您在配置中包含此属性,请不要设置 field.exclude.list 属性。 |
field.exclude.list | empty string | 任选的、以分隔的字段名称列表,与要从更改事件值中删除的字段的完全限定名称相匹配。字段的完全限定名称的格式为 fieldName 或 topicName:_fieldName_。如果您在配置中包含此属性,请不要设置 field.include.list 属性。 |
表 8. JDBC 连接器可扩展属性
属性 | 默认值 | 描述 |
---|---|---|
column.naming.strategy | i.d.c.j.n.DefaultColumnNamingStrategy | 指定连接器用来解析事件字段名称中的列名称的 ColumnNamingStrategy 实现的完全限定类名称。默认情况下,连接器使用字段名称作为列名称。 |
table.naming.strategy | i.d.c.j.n.DefaultTableNamingStrategy | 指定 TableNamingStrategy 实现的完全限定类名称,连接器使用该实现来解析传入事件主题名称中的表名称。默认行为是:将 table.name.format 配置属性中的 ${topic} 占位符替换为事件的主题。通过用下划线 (_) 替换点 (.) 来清理表名称。 |
是否需要 ExtractNewRecordState 单消息转换?
如果列的类型发生更改,或者列被重命名或删除,这是否由模式演化处理?
如果列的类型无法解析为我想要的类型,如何强制映射到不同的数据类型?
如何在不更改Kafka主题名称的情况下为表名称指定前缀或后缀?
即使未启用标识符引用,为什么某些列会自动引用?
更多Debezium实战应用可以参考博主Debezium专栏: