Flink消费kafka的debezium-json数据(包含增删改消息),将数据同步到starrocks

本文介绍了如何解决Flink CDC在处理Oracle数据时遇到的问题,转而采用高版本Debezium结合Kafka Connect将Oracle数据同步到Kafka。然后利用Flink SQL消费Kafka中的Debezium-JSON消息,实现数据实时同步到Starrocks。通过本地MySQL源测试,展示启动Zookeeper和Kafka,配置binlog到Kafka的流程,并强调Flink SQL源表需定义主键以处理删除消息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

业务上需要同步oracle的数据到starrocks,先开始调研使用了flinkCDC,运行一段时间后发现Oracle内存不足,查阅相关issues以及相关资料,最终确认是flinkCDC2.3版本中debezium版本太低导致的,具体issues参考: https://github.com/ververica/flink-cdc-connectors/issues/815

所以只能更换方案使用高版本debezium + kafka connect的方式来同步对应的数据到kafka中,后面使用flink sql消费对应的kafka消息,来达到实时同步的目的。

本地测试调研使用mysql source作为测试案例

启动本地zookeeper以及kafka,
我这里的版本是 zookeeper3.4.6、kafka2.1
采集mysql binlog数据发送kafka:

package debezium_cdc

import io.debezium.engine.DebeziumEngine.{ChangeConsumer, CompletionCallback}
import io.debezium.engine.format.Json
import io.debezium.engine.{ChangeEvent, DebeziumEngine}
import org.apache.kafka.clients.producer.{KafkaProducer, ProducerRecord}

import java.util
import java.util.Properties
import java.util.concurrent.{ExecutorService, Executors, TimeUnit}
import java.util.function.Consumer
import scala.collection.JavaConverters.asScalaBufferConverter

/**
 * 采集binlog日志发送kafka
 *
 * @author zhangyunhao
 */
object MysqlDebeziumEngine {


  def main(args: Array[String]): Unit = {

    val bootstrapList = "localhost:9092"
    val topicName = "test_zyh_kafka_cdc"

    // kafka配置
    val kafkaProps = new Properties()
    kafkaProps.put("bootstrap.servers", bootstrapList)
    kafkaProps.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer")
    kafkaProps.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer")

    val producer = new KafkaProducer[String,String](kafkaProps)

    // debezium配置

    val props: Properties = new Properties()
    // engine的参数设置
    props.setProperty("name", "engine")
    props.setProperty("offset.storage", "org.apache.kafka.connect.storage.FileOffsetBackingStore")
    props.setProperty("offset.storage.file.filename", "/Users/zhangyunhao/IdeaProjects/flink_explore/offsets.log")
    props.setProperty("offset.flush.interval.ms", "6000")
    props.setProperty("converter.schemas.enable", "true")
    // mysql connector的参数设置
    props.setProperty("connector.class", "io.debezium.connector.mysql.MySqlConnector")
    props.setProperty("database.hostname", "127.0.0.1")
    props.setProperty("database.port", "3306")
    props.setProperty("database.user", "root")
    props.setProperty("database.password", "123456")
    props.setProperty("database.server.id", "85744")   // 随便设置
    props.setProperty("database.server.name", "my-app-connector")   // 随便设置
    props.setProperty("database.include.list", "db_test")         // 同步库
    props.setProperty("table.include.list", "db_test.stu_test")   // 同步表
    props.setProperty("snapshot.mode", "schema_only")
    props.setProperty("decimal.handling.mode", "double")
    props.setProperty("database.history",
      "io.debezium.relational.history.FileDatabaseHistory")
    props.setProperty("database.history.file.filename",
      "/Users/zhangyunhao/IdeaProjects/flink_explore/dbhistory.log")

    try {
      // 创建engine。DebeziumEngine继承了Closeable,会自动关闭
      val engine: DebeziumEngine[ChangeEvent[String, String]] =
        DebeziumEngine.create(classOf[Json])
          .using(props)
          .notifying(new Consumer[ChangeEvent[String, String]] {
            override def accept(changeEvent: ChangeEvent[String, String]): Unit = {

              println("日志key" + changeEvent.key())
              println("日志value" + changeEvent.value())
            }
          })
          .notifying(
            new ChangeConsumer[ChangeEvent[String, String]] {
              override def handleBatch(list: util.List[ChangeEvent[String, String]], recordCommitter: DebeziumEngine.RecordCommitter[ChangeEvent[String, String]]): Unit = {
                for (changeEvent <- list.asScala) {
                  println("日志key" + changeEvent.key())
                  println("日志value" + changeEvent.value())

                  // 消息发送kafka
                  producer.send(new ProducerRecord[String,String](topicName, changeEvent.key(), changeEvent.value()))
                  producer.flush()

                  recordCommitter.markProcessed(changeEvent)
                }

                recordCommitter.markBatchFinished()
              }
            }
          )
          // 加上回调代码,查看错误信息
          .using(new CompletionCallback {
            override def handle(success: Boolean, message: String, error: Throwable): Unit = {
              if (!success && error != null) {
                System.out.println("----------error------")
                System.out.println(message)
                error.printStackTrace()
              }

            }
          })
          .build()

      // 异步执行engine
      val executor: ExecutorService = Executors.newSingleThreadExecutor()
      executor.execute(engine)

      // 优雅的关闭应用
      executor.shutdown() // 执行shutdown,等待已经提交的任务执行完毕
      // 持续监控任务是否完成,如果未完成,则继续等待
      while (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
        println("Waiting another 10 seconds for the embedded engine to shut down")
      }


    } catch {
      case e: InterruptedException => {
        Thread.currentThread().interrupt()
      }
    }


  }

}

使用flink sql消费对应的消息:

package debezium_cdc

import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.table.api.{EnvironmentSettings}
import org.apache.flink.table.api.bridge.scala.StreamTableEnvironment

object BinlogParseApp {

  def main(args: Array[String]): Unit = {

    val fsSettings: EnvironmentSettings = EnvironmentSettings.newInstance()
      .useBlinkPlanner()
      .inStreamingMode()
      .build()

    val fsEnv: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    // fsEnv.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    fsEnv.setParallelism(1)

    val tEnv: StreamTableEnvironment = StreamTableEnvironment.create(fsEnv,fsSettings)

    // 可以删除,注意这里要设置主键字段
    // 目前debezium-json格式支持的连接器,
    // 参考 https://nightlies.apache.org/flink/flink-docs-release-1.13/zh/docs/connectors/table/formats/overview/
    val sourceSql =
      """
        |CREATE TABLE topic_products (
        |  id BIGINT,
        |  name STRING,
        |  PRIMARY KEY (id) NOT ENFORCED
        |) WITH (
        | 'connector' = 'kafka',
        | 'topic' = 'test_zyh_kafka_cdc',
        | 'properties.bootstrap.servers' = 'localhost:9092',
        | 'properties.group.id' = 'testGroup_zyh',
        | 'format' = 'debezium-json',
        | 'debezium-json.schema-include' = 'true'
        |)
        |""".stripMargin

    tEnv.executeSql(sourceSql)

    // tEnv.executeSql("select * from topic_products").print()

    val sinkSql =
      """
        |CREATE TABLE user_sink_table (
        |  id BIGINT,
        |  name STRING,
        |  PRIMARY KEY (id) NOT ENFORCED
        |) WITH (
        |   'connector' = 'jdbc',
        |   'url' = 'jdbc:mysql://localhost:3306/db_test',
        |   'table-name' = 'stu_test2',
        |   'username' = 'root',
        |   'password' = '123456',
        |	  'sink.buffer-flush.interval' = '5s',
        |   'sink.buffer-flush.max-rows' = '100'
        |)
        |""".stripMargin

    tEnv.executeSql(sinkSql)

    val insertSql =
      """
        |insert into user_sink_table
        |select
        | id,
        | name
        |from
        |topic_products
        |""".stripMargin

    tEnv.executeSql(insertSql)

  }

}

在表 db_test.stu_test 上执行增删改操作,在对应的sink表 db_test.stu_test2 也会出现对应的操作。
⚠️: flink sql程序的source需要定义主键,否则无法处理删除消息

<think>我们正在讨论的是Flink物化表(Materialized Table)中`write-mode`设置为`changelog`时是否支持增删改操作。根据Flink的设计,物化表是用于存储物化视图结果的物理表,而`changelog`模式意味着该表会接收并处理变更日志(包括插入、更新和删除)。 关键点: 1. Flink物化表支持通过`WITH`子句设置存储连接器的参数,其中`write-mode`是一个重要的参数。 2. 当`write-mode`设置为`changelog`时,表示物化表将处理变更日志流(即INSERT、UPDATE、DELETE操作)。 因此,答案是:**支持增删改操作**。 详细解释如下: ### 1. 物化表与`changelog`模式 - 物化表在Flink中是一个物理存储的表,用于持续地存储物化视图的结果。 - `write-mode`参数决定了数据写入外部存储的方式。在`changelog`模式下,物化表会接收来自上游的变更日志(Changelog Stream),这些变更日志包括: - `+I`:插入(Insert) - `-U`:更新前镜像(Update-Before) - `+U`:更新后镜像(Update-After) - `-D`:删除(Delete) 因此,当物化表配置为`'write-mode'='changelog'`时,它能够处理这些变更操作,从而支持完整的增删改操作。 ### 2. 前提条件 要使物化表支持增删改操作,需要满足以下条件: - **定义主键**:在创建物化表时,必须定义主键(PRIMARY KEY),这样系统才能根据主键来更新或删除记录。例如: ```sql CREATE MATERIALIZED TABLE my_table ( id INT PRIMARY KEY NOT ENFORCED, name STRING, age INT ) ... ``` 主键用于唯一标识一行,以便执行更新和删除。 - **连接器支持**:底层的存储连接器必须支持更新和删除操作。不是所有的连接器都支持`changelog`模式。例如: - **支持更新/删除的连接器**:JDBC、Upsert Kafka、HBase等。 - **不支持更新/删除的连接器**:普通的Kafka(仅追加)、文件系统(如Parquet、ORC)等。 因此,在设置`write-mode='changelog'`时,必须确保使用的连接器支持更新和删除操作。 ### 3. 示例 以下是一个创建物化表的示例,它支持增删改操作: ```sql CREATE MATERIALIZED TABLE orders_materialized ( order_id STRING PRIMARY KEY NOT ENFORCED, order_time TIMESTAMP(3), amount DECIMAL(10,2) ) WITH ( 'connector' = 'jdbc', 'url' = 'jdbc:mysql://localhost:3306/mydb', 'table-name' = 'orders', 'write-mode' = 'changelog' -- 使用changelog模式 ) FRESHNESS = INTERVAL '30' SECOND REFRESH_MODE = CONTINUOUS AS SELECT order_id, order_time, amount FROM orders_source; ``` 在这个例子中: - 我们使用JDBC连接器,它支持更新和删除操作。 - 设置`write-mode='changelog'`,因此物化表会处理来自上游的变更(包括插入、更新、删除)。 - 主键`order_id`用于标识每一行,以便执行更新和删除。 ### 4. 注意事项 - **连接器的限制**:不同的连接器对更新和删除操作的支持程度不同。例如: - JDBC连接器:支持完整的增删改,但需要数据库表有主键。 - Upsert Kafka:支持插入和更新(通过主键),但删除操作需要特殊处理(通常发送一个value为null的消息表示删除)。 - **存储格式**:如果使用文件系统(如HDFS),通常不支持更新和删除,除非使用支持更新的文件格式(如Hudi、Iceberg),但这时通常不需要显式设置`write-mode`,因为这些表格式有自己的机制。 ### 5. 如何验证 可以通过查看物化表的数据变化来验证是否支持增删改。例如,在源表执行更新操作后,观察物化表中的相应记录是否更新。 ### 总结 当Flink物化表的`write-mode`设置为`changelog`时,**支持增删改操作**,但前提条件是: 1. 物化表必须定义主键。 2. 使用的连接器必须支持更新和删除操作。 如果不满足这些条件,物化表可能只支持追加(Append)模式,即只能插入,不能更新和删除。 相关问题: 1. 在Flink物化表中,哪些连接器支持`write-mode='changelog'`? 2. 如果物化表没有定义主键,设置`write-mode='changelog'`会怎样? 3. 如何监控Flink物化表的更新和删除操作是否成功? 4. 使用Upsert Kafka作为物化表存储时,删除操作是如何表示的? 5. 物化表的`write-mode`和`refresh-mode`有什么关系?</think>### Flink 物化表 `write-mode='changelog'` 对增删改操作的支持分析 **是的,当 Flink 物化表设置 `'write-mode'='changelog'` 时,完全支持增删改(CRUD)操作**。以下是关键机制和技术细节: #### 1. **核心原理** - **Changelog 模式本质**:通过记录数据变更事件(`+I`插入, `-U/+U`更新, `-D`删除)实现动态表同步[^2] - **主键约束要求**:必须显式定义 `PRIMARY KEY`(如 `policy_id STRING PRIMARY KEY NOT ENFORCED`),使系统能追踪行级变更 - **变更传播**:物化表会将源表的 DML 操作转换为变更日志流写入目标存储[^1] #### 2. **支持的操作类型 | 操作类型 | Changelog 表示 | 存储要求 | |----------|----------------|----------| | **INSERT** | `+I` 记录 | 所有存储均支持 | | **UPDATE** | `-U`(旧值) + `+U`(新值) | 需支持主键更新(如 JDBC, HBase) | | **DELETE** | `-D` 记录 | 需支持主键删除(如 JDBC, Cassandra) | #### 3. **连接器兼容性 | 连接器类型 | UPDATE支持 | DELETE支持 | 典型配置 | |------------|------------|------------|----------| | **JDBC** | ✅ | ✅ | `'connector'='jdbc'` + 主键 | | **Upsert-Kafka** | ✅ | ✅ | `'value.format'='debezium-json'` | | **HBase** | ✅ | ✅ | `'connector'='hbase'` | | **FileSystem** | ❌ | ❌ | 仅支持 Append 模式 | #### 4. **配置示例(支持增删改) ```sql CREATE MATERIALIZED TABLE user_actions ( user_id BIGINT PRIMARY KEY NOT ENFORCED, -- 必须定义主键 action_type STRING, last_update TIMESTAMP(3) ) WITH ( 'connector' = 'jdbc', 'url' = 'jdbc:mysql://dbserver:3306/log_db', 'table-name' = 'user_actions', 'write-mode' = 'changelog', -- 关键配置 'changelog-producer' = 'input' -- 启用变更日志捕获[^1] ) FRESHNESS = INTERVAL '5' SECOND AS SELECT user_id, action_type, event_time FROM kafka_source; ``` #### 5. **操作验证 ```java // Java 示例:验证删除操作 TableResult result = tableEnv.executeSql( "DELETE FROM user_actions WHERE user_id = 101" ); // 删除操作将生成 -D 变更日志 // 并在目标MySQL表删除对应行 ``` #### 6. **注意事项 1. **存储系统限制**:HDFS/Parquet 等文件存储不支持 `changelog` 模式(需用 Hudi/Iceberg) 2. **主键冲突处理**:需配置 `'sink.parallelism'` 避免并发写入冲突 3. **Exactly-Once 保证**:需开启检查点并配置连接器幂等性(如 JDBC 需开启事务) 4. **数据结构变更**:表结构修改需通过 `ALTER TABLE ... ADD COLUMN` 实现[^3] ### 典型应用场景 1. **用户画像实时更新** ```sql UPDATE user_profiles SET interest = 'AI' WHERE user_id = 205; -- 生成 +U/-U 日志 ``` 2. **订单状态流转** ```sql DELETE FROM pending_orders WHERE order_id = 'O1001'; -- 生成 -D 日志 ``` ### 相关问题 1. 如何为 JDBC 物化表配置事务保证以支持增删改? 2. `changelog` 模式与 `append` 模式在存储开销上有何差异? 3. 使用 Upsert-Kafka 时,`write-mode='changelog'` 的消息格式是怎样的? 4. 物化表删除操作在 HBase 连接器中如何实现? 5. 如何监控 Flink 物化表的变更日志生成速率? [^1]: 变更日志流转换机制 [^2]: 动态表存储原理 [^3]: 表结构修改语法
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雾岛与鲸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值