实时数仓|以upsert的方式读写Kafka数据——以Flink1.12为例

本文详细介绍了Flink 1.12新增的Upsert Kafka连接器,该连接器支持以upsert模式读写Kafka数据。内容包括连接器的使用方式、配置参数、主键约束、一致性保障以及使用案例,旨在帮助读者掌握如何在实时数仓中利用此功能进行数据处理。

在某些场景中,比如GROUP BY聚合之后的结果,需要去更新之前的结果值。这个时候,需要将 Kafka 消息记录的 key 当成主键处理,用来确定一条数据是应该作为插入、删除还是更新记录来处理。在Flink1.11中,可以通过 flink-cdc-connectors 项目提供的 **changelog-json format **来实现该功能。关于该功能的使用,见之前的分享Flink1.11中的CDC Connectors操作实践

在Flink1.12版本中, 新增了一个 upsert connector(upsert-kafka),该 connector 扩展自现有的 Kafka connector,工作在 upsert 模式(FLIP-149)下。新的 upsert-kafka connector 既可以作为 source 使用,也可以作为 sink 使用,并且提供了与现有的 kafka connector 相同的基本功能和持久性保证,因为两者之间复用了大部分代码。本文将以Flink1.12为例,介绍该功能的基本使用步骤,以下是全文,希望对你有所帮助。

公众号『大数据技术与数仓』,回复『资料』领取大数据资料包
【大数据技术与数仓】社群提供给大家一个开放的交流平台,扫描下方二维码加好友入技术交流群

Upsert Kafka connector简介

Upsert Kafka Connector允许用户以upsert的方式从Kafka主题读取数据或将数据写入Kafka主题。

当作为数据源时,upsert-kafka Connector会生产一个changelog流,其中每条数据记录都表示一个更新或删除事件。更准确地说,如果不存在对应的key,则视为INSERT操作。如果已经存在了相对应的key,则该key对应的value值为最后一次更新的值。

用表来类比,changelog 流中的数据记录被解释为 UPSERT,也称为 INSERT/UPDATE,因为任何具有相同 key 的现有行都被覆盖。另外,value 为空的消息将会被视作为 DELETE 消息。

当作为数据汇时,upsert-kafka Connector会消费一个changelog流。它将INSERT / UPDATE_AFTER数据作为正常的Kafka消息值写入(即INSERT和UPDATE操作,都会进行正常写入,如果是更新,则同一个key会存储多条数据,但在读取该表数据时,只保留最后一次更新的值),并将 DELETE 数据以 value 为空的 Kafka 消息写入(key被打上墓碑标记,表示对应 key 的消息被删除)。Flink 将根据主键列的值对数据进行分区,从而保证主键上的消息有序,因此同一主键上的更新/删除消息将落在同一分区中

依赖

为了使用Upsert Kafka连接器,需要添加下面的依赖

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-kafka_2.12</artifactId>
    <version>1.12.0</version>
</dependency>

如果使用SQL Client,需要下载flink-sql-connector-kafka_2.11-1.12.0.jar,并将其放置在Flink安装目录的lib文件夹下。

使用方式

使用样例

-- 创建一张kafka表,用户存储sink的数据
CREATE TABLE pageviews_per_region (
  user_region STRING,
  pv BIGINT,
  uv BIGINT,
  PRIMARY KEY (user_region) NOT ENFORCED
) WITH (
  'connector' = 'upsert-kafka',
  'topic' = 'pageviews_per_region',
  'properties.bootstrap.servers' = 'kms-2:9092,kms-3:9092,kms-4:9092',
  'key.format' = 'avro',
  'value.format' = 'avro'
);

尖叫提示:

要使用 upsert-kafka connector,必须在创建表时使用PRIMARY KEY定义主键,并为键(key.format)和值(value.format)指定序列化反序列化格式。

upsert-kafka connector参数

  • connector

必选。指定要使用的连接器,Upsert Kafka 连接器使用:'upsert-kafka'

  • topic

必选。用于读取和写入的 Kafka topic 名称。

  • properties.bootstrap.servers

必选。以逗号分隔的 Kafka brokers 列表。

  • key.format

必选。用于对 Kafka 消息中 key 部分序列化和反序列化的格式。key 字段由 PRIMARY KEY 语法指定。支持的格式包括 'csv''json''avro'

  • value.format

必选。用于对 Kafka 消息中 value 部分序列化和反序列化的格式。支持的格式包括 'csv''json''avro'

  • *properties. **

可选。 该选项可以传递任意的 Kafka 参数。选项的后缀名必须匹配定义在 Kafka 参数文档中的参数名。 Flink 会自动移除 选项名中的 “properties.” 前缀,并将转换后的键名以及值传入 KafkaClient。 例如,你可以通过 'properties.allow.auto.create.topics' = 'false' 来禁止自动创建 topic。 但是,某些选项,例如'key.deserializer''value.deserializer' 是不允许通过该方式传递参数,因为 Flink 会重写这些参数的值。

  • value.fields-include

可选,默认为ALL。控制key字段是否出现在 value 中。当取ALL时,表示消息的 value 部分将包含 schema 中所有的字段,包括定义为主键的字段。当取EXCEPT_KEY时,表示记录的 value 部分包含 schema 的所有字段,定义为主键的字段除外。

  • key.fields-prefix

可选。为了避免与value字段命名冲突,为key字段添加一个自定义前缀。默认前缀为空。一旦指定了key字段的前缀,必须在DDL中指明前缀的名称,但是在构建key的序列化数据类型时,将移除该前缀。见下面的示例。在需要注意的是:使用该配置属性,value.fields-include的值必须为EXCEPT_KEY

-- 创建一张upsert表,当指定了qwe前缀,涉及的key必须指定qwe前缀
CREATE TABLE result_total_pvuv_min_prefix (
    qwedo_date     STRING,     -- 统计日期,必须包含qwe前缀
    qwedo_min      STRING,      -- 统计分钟,必须包含qwe前缀
    pv          BIGINT,     -- 点击量
    uv          BIGINT,     -- 一天内同个访客多次访问仅计算一个UV
    currenttime TIMESTAMP,  -- 当前时间
    PRIMARY KEY (qwedo_date, qwedo_min) NOT ENFORCED -- 必须包含qwe前缀
) WITH (
  'connector' = 'upsert-kafka',
  'topic' = 'result_total_pvuv_min_prefix',
  'properties.bootstrap.servers' = 'kms-2:9092,kms-3:9092,kms-4:9092',
  'key.json.ignore-parse-errors' = 'true',
  'value.json.fail-on-missing-field' = 'false',
  'key.format' = 'json',
  'value.format' = 'json',
  'key.fields-prefix'='qwe', -- 指定前缀qwe
  'value.fields-include' = 'EXCEPT_KEY' -- key不出现kafka消息的value中
);
-- 向该表中写入数据
INSERT INTO result_total_pvuv_min_prefix
SELECT
  do_date,    --  时间分区
  cast(DATE_FORMAT (access_time,'HH:mm') AS STRING) AS do_min,-- 分钟级别的时间
  pv,
  uv,
  CURRENT_TIMESTAMP AS currenttime -- 当前时间
from
  view_total_pvuv_min;

尖叫提示:

如果指定了key字段前缀,但在DDL中并没有添加该前缀字符串,那么在向该表写入数时,会抛出下面异常:

[ERROR] Could not execute SQL statement. Reason:
org.apache.flink.table.api.ValidationException: All fields in ‘key.fields’ must be prefixed with ‘qwe’ when option ‘key.fields-prefix’ is set but field ‘do_date’ is not prefixed.

<think>嗯,用户的问题是关于使用Kafka读取数据并通过Upsert方式写入MySQL的代码示数据。我需要先理解用户的需求。他们可能是在构建一个实时数据处理的系统,比如实时数仓,需要将Kafka中的数据变化(包括插入、更新、删除)同步到MySQL中,保持数据的一致性。 首先,我应该回顾一下相关的技术组件。用户提到了KafkaUpsert和MySQLFlinkUpsert Kafka Connector应该是一个关键点,因为它允许以upsert方式处理数据。根据引用[4],Flink 1.12支持以UPSERT方式读写Kafka,这会将数据视为changelog流,处理INSERT/UPDATE和DELETE操作。 接下来,用户需要代码示数据。我需要确保代码结构正确,使用Flink SQL或者DataStream API。考虑到用户可能更熟悉SQL,用Flink SQL来演示可能更直观。同时,需要配置Kafka的连接器和MySQL的Sink Connector,特别是JDBC连接器,并处理upsert操作,这通常需要主键数据方面,应该展示包含操作类型(如+I表示插入,-U表示更新前,+U表示更新后,-D表示删除)的消息,这样用户可以看到不同操作如何被处理。如,用户发送到Kafka的消息需要包含主键和操作类型,Flink处理后将相应的SQL操作应用到MySQL。 还要注意Flink的JDBC Sink是否支持upsert,这通常是通过ON DUPLICATE KEY UPDATE实现的。需要检查连接器的文档,确保语法正确。此外,MySQL表必须有主键才能进行upsert操作,这点需要提醒用户。 最后,用户可能遇到的问题包括配置错误、主键未设置或格式不正确,需要在注意事项里提到。同时,根据用户提供的引用,特别是引用[1]和[4],确保代码示符合这些引用中的流程,比如数据Kafka到目标数据库的流动,以及upsert的处理方式。</think>### 使用Kafka读取数据并通过Upsert写入MySQL的代码示 #### 1. 场景说明 假设需要将Kafka中存储的用户行为数据(如用户ID、操作类型、时间戳)以**Upsert模式**写入MySQL表。当数据主键(如用户ID)冲突时,更新操作时间和状态字段。 #### 2. 数据 **Kafka Topic数据格式(JSON)**: ```json { "user_id": 1001, "operation": "login", "event_time": "2023-10-01 12:00:00" } ``` **目标MySQL表结构**: ```sql CREATE TABLE user_behavior ( user_id INT PRIMARY KEY, last_operation VARCHAR(20), last_event_time TIMESTAMP ); ``` #### 3. Flink SQL代码示 ```sql -- 创建Kafka源表(使用Upsert模式) CREATE TABLE kafka_upsert_source ( user_id INT, operation STRING, event_time TIMESTAMP(3), WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND ) WITH ( 'connector' = 'upsert-kafka', 'topic' = 'user_behavior', 'properties.bootstrap.servers' = 'localhost:9092', 'key.format' = 'json', 'value.format' = 'json', 'key.json.ignore-parse-errors' = 'true', 'value.json.fail-on-missing-field' = 'false' ); -- 创建MySQL Sink表 CREATE TABLE mysql_upsert_sink ( user_id INT PRIMARY KEY, last_operation VARCHAR(20), last_event_time TIMESTAMP ) WITH ( 'connector' = 'jdbc', 'url' = 'jdbc:mysql://localhost:3306/test', 'table-name' = 'user_behavior', 'username' = 'root', 'password' = '123456', 'sink.buffer-flush.max-rows' = '1', -- 实时写入 'sink.upsert-materialize' = 'NONE' ); -- 写入逻辑 INSERT INTO mysql_upsert_sink SELECT user_id, operation AS last_operation, event_time AS last_event_time FROM kafka_upsert_source; ``` #### 4. 关键配置说明 - **Upsert-Kafka Connector**:通过`key.format`定义主键字段,自动处理INSERT/UPDATE/DELETE语义[^4] - **JDBC Sink**:需声明主键,通过`sink.upsert-materialize`控制是否物化变更日志[^3] - **Watermark**:定义了事件时间窗口处理机制(可选) #### 5. 操作示 **输入Kafka数据**: ```json {"user_id":1001,"operation":"login","event_time":"2023-10-01 12:00:00"} -- 插入新记录 {"user_id":1001,"operation":"logout","event_time":"2023-10-01 12:05:00"} -- 更新操作时间 ``` **MySQL结果**: ``` | user_id | last_operation | last_event_time | |---------|----------------|-----------------------| | 1001 | logout | 2023-10-01 12:05:00 | ``` #### 注意事项 1. MySQL表**必须定义主键**才能支持Upsert 2. Kafka消息需包含主键字段(`user_id`作为`key`) 3. 更新和删除操作需要对应的消息格式: - 删除操作需发送`value`为`null`的消息[^4] - 更新操作需包含完整字段
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值