Flink1.14新版KafkaSource和KafkaSink实践使用(自定义反序列化器、Topic选择器、序列化器、分区器)

Flink新API:KafkaSource与KafkaSink的自定义实现
文章介绍了ApacheFlink中即将弃用旧版API后,如何使用新APIKafkaSource和KafkaSink进行数据处理。作者提供了自定义反序列化器、Topic选择器、序列化器和分区器的示例代码,以适应不同业务需求。

前言

在官方文档的描述中,API FlinkKafkaConsumer和FlinkKafkaProducer将在后续版本陆续弃用、移除,所以在未来生产中有版本升级的情况下,新API KafkaSource和KafkaSink还是有必要学会使用的。下面介绍下基于新API的一些自定义类以及主程序的简单实践。

官方案例

官方文档地址:
https://nightlies.apache.org/flink/flink-docs-release-1.15/zh/docs/connectors/datastream/kafka/

KafkaSource的自定义类

自定义反序列化器

自定义反序列化器可以以指定的格式取到来源Kafka消息中我们想要的元素。该类需要继承 KafkaDeserializationSchema ,这里简单将来源Kafka的topic、key、value以Tuple3[String, String, String]的格式取出来。

MyKafkaDeserializationSchemaTuple3.scala

import org.apache.flink.api.common.typeinfo.TypeInformation
import org.apache.flink.streaming.connectors.kafka.KafkaDeserializationSchema
import org.apache.kafka.clients.consumer.ConsumerRecord

import java.nio.charset.StandardCharsets

/**
 * @author hushhhh
 */
class MyKafkaDeserializationSchemaTuple3 extends KafkaDeserializationSchema[(String, String, String)] {
  override def deserialize(record: ConsumerRecord[Array[Byte], Array[Byte]]): (String, String, String) = {
    new Tuple3[String, String, String](
      record.topic(),
      new String(record.key(), StandardCharsets.UTF_8),
      new String(record.value(), StandardCharsets.UTF_8))
  }

  override def isEndOfStream(nextElement: (String, String, String)): Boolean = false

  override def getProducedType: TypeInformation[(String, String, String)] = {
    TypeInformation.of(classOf[(String, String, String)])
  }
}

KafkaSink的自定义类

自定义Topic选择器

自定义一个 TopicSelector 可以将流中多个topic里的数据根据一定逻辑分发到不同的目标topic里。该类需要继承 TopicSelector ,这里简单根据来源Kafka的topic名拼接下。

MyTopicSelector.scala

import org.apache.flink.connector.kafka.sink.TopicSelector

/**
 * @author hushhhh
 */
class MyTopicSelector extends TopicSelector[(String, String, String)] {
  override def apply(t: (String, String, String)): String = {
    // t: 来源kafka的topic、key、value
    "TOPIC_" + t._1.toUpperCase()
  }
}

自定义序列化器

自定义序列化器可以将数据根据自己的业务格式写到目标Kafka的key和value里,这里将来源Kafka里的key和value直接写出去,这两个类都需要继承 SerializationSchema 。

ProducerRecord Key的序列化器

MyKeySerializationSchema.scala

import org.apache.flink.api.common.serialization.SerializationSchema

/**
 * @author hushhhh
 */
class MyKeySerializationSchema extends SerializationSchema[(String, String, String)] {
  override def serialize(element: (String, String, String)): Array[Byte] = {
    // element: 来源kafka的topic、key、value
    element._2.getBytes()
  }
}

ProducerRecord Value的序列化器

MyValueSerializationSchema.scala

import org.apache.flink.api.common.serialization.SerializationSchema

/**
 * @author hushhhh
 */
class MyValueSerializationSchema extends SerializationSchema[(String, String, String)] {
  override def serialize(element: (String, String, String)): Array[Byte] = {
    // element: 来源kafka的topic、key、value
    element._3.getBytes()
  }
}

自定义分区器

自定义分区器可以根据具体逻辑对要写到目标Kafka 里的数据进行partition分配。该类需要继承 FlinkKafkaPartitioner ,这里根据key的hash分配到不同的partition里(如果目标topic有多个partition的话)。

MyPartitioner.scala

import org.apache.flink.streaming.connectors.kafka.partitioner.FlinkKafkaPartitioner

/**
 * @author hushhhh
 */
class MyPartitioner extends FlinkKafkaPartitioner[(String, String, String)] {
  override def partition(record: (String, String, String), key: Array[Byte], value: Array[Byte], targetTopic: String, partitions: Array[Int]): Int = {
    // record: 来源kafka的topic、key、value
    Math.abs(new String(record._2).hashCode % partitions.length)
  }
}

主类

Main.scala

import format.{MyKafkaDeserializationSchemaTuple3, MyKeySerializationSchema, MyPartitioner, MyTopicSelector, MyValueSerializationSchema}
import org.apache.flink.api.common.eventtime.WatermarkStrategy
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.api.scala._
import org.apache.flink.connector.base.DeliveryGuarantee
import org.apache.flink.connector.kafka.sink.{KafkaRecordSerializationSchema, KafkaSink}
import org.apache.flink.connector.kafka.source.KafkaSource
import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer
import org.apache.flink.connector.kafka.source.reader.deserializer.KafkaRecordDeserializationSchema
import org.apache.kafka.clients.consumer.OffsetResetStrategy

import java.util.Properties
import scala.collection.JavaConverters._

/**
 * @author hushhhh
 */
object Main {
  def main(args: Array[String]): Unit = {
    /**
     * env
     */
    // stream环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    /**
     * source
     */
    // 定义 KafkaSource
    lazy val kafkaSource: KafkaSource[(String, String, String)] = KafkaSource.builder()
      // Kafka消费者的各种配置文件,此处省略配置
      .setProperties(new Properties())
      // 配置消费的一个或多个topic
      .setTopics("sourceTopic1,sourceTopic2,...".split(",", -1).toList.asJava)
      // 开始消费位置,从已提交的offset开始消费,没有的话从最新的消息开始消费
      .setStartingOffsets(OffsetsInitializer.committedOffsets(OffsetResetStrategy.LATEST))
      // 反序列化,使用之前我们自定义的反序列化器
      .setDeserializer(KafkaRecordDeserializationSchema.of(new MyKafkaDeserializationSchemaTuple3))
      .build()
    // 添加 kafka source
    val inputDS: DataStream[(String, String, String)] = env.fromSource(
      kafkaSource,
      WatermarkStrategy.noWatermarks(),
      "MyKafkaSource")
      .setParallelism(1)

    /**
     * transformation
     */
    // 数据加工处理,此处省略

    /**
     * sink
     */
    // 定义 KafkaSink
    lazy val kafkaSink: KafkaSink[(String, String, String)] =
      KafkaSink.builder[(String, String, String)]()
        // 目标集群地址
        .setBootstrapServers("bootstrap.servers")
        // Kafka生产者的各种配置文件,此处省略配置
        .setKafkaProducerConfig(new Properties())
        // 定义消息的序列化模式
        .setRecordSerializer(KafkaRecordSerializationSchema.builder()
          // Topic选择器,使用之前我们自定义的Topic选择器
          .setTopicSelector(new MyTopicSelector)
          // Key的序列化器,使用之前我们自定义的Key序列化器
          .setKeySerializationSchema(new MyKeySerializationSchema)
          // Value的序列化器,使用之前我们自定义的Value序列化器
          .setValueSerializationSchema(new MyValueSerializationSchema)
          // 自定义分区器,使用之前我们自定义的自定义分区器
          .setPartitioner(new MyPartitioner)
          .build()
        )
        // 语义保证,保证至少一次
        .setDeliverGuarantee(DeliveryGuarantee.AT_LEAST_ONCE)
        .build()

    // 添加 kafka sink
    inputDS.sinkTo(kafkaSink)
      .name("MyKafkaSink")
      .setParallelism(1)

    /**
     * execute
     */
    env.execute("myJob")
  }

}

以上就是KafkaSource和KafkaSink API的简单使用。大佬们感觉有用的话点个赞吧~😉

<think>我们在使用FlinkKafkaSource时,通常需要自定义反序列化器来读取Kafka消息,并提取其中的分区、偏移量、时间戳消息体等信息。在Flink 1.20.1Kafka-client 3.4.0版本下,我们可以通过实现`KafkaRecordDeserializationSchema`接口来完成。 具体步骤如下: 1. **创建自定义反序列化类**:实现`KafkaRecordDeserializationSchema`接口,并指定输出的类型(例如,一个包含分区、偏移量、时间戳消息体的元组)。 2. **重写`deserialize`方法**:在这个方法中,我们可以从`ConsumerRecord`对象中提取所需的信息。 3. **指定输出类型**:通过实现`getProducedType`方法,指定输出结果的类型信息。 下面是一个示例代码,假设我们期望输出的类型为`Tuple4<Integer, Long, Long, String>`,分别代表分区、偏移量、时间戳消息体(假设消息体是字符串)。 ```java import org.apache.flink.api.common.serialization.DeserializationSchema; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.api.java.tuple.Tuple4; import org.apache.flink.connector.kafka.source.reader.deserializer.KafkaRecordDeserializationSchema; import org.apache.flink.util.Collector; import org.apache.kafka.clients.consumer.ConsumerRecord; public class CustomKafkaDeserializer implements KafkaRecordDeserializationSchema<Tuple4<Integer, Long, Long, String>> { @Override public void deserialize(ConsumerRecord<byte[], byte[]> record, Collector<Tuple4<Integer, Long, Long, String>> out) throws Exception { // 提取分区 int partition = record.partition(); // 提取偏移量 long offset = record.offset(); // 提取时间戳(这里使用消息自带的时间戳,也可以根据需要选择其他时间戳类型) long timestamp = record.timestamp(); // 提取消息体,这里假设消息体是字符串,使用UTF-8解码 String value = new String(record.value(), "UTF-8"); // 输出元组 out.collect(new Tuple4<>(partition, offset, timestamp, value)); } @Override public TypeInformation<Tuple4<Integer, Long, Long, String>> getProducedType() { // 返回元组的类型信息 return TypeInformation.of(new TypeHint<Tuple4<Integer, Long, Long, String>>() {}); } } ``` 然后,在创建KafkaSource使用这个自定义反序列化器: ```java import org.apache.flink.connector.kafka.source.KafkaSource; import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer; KafkaSource<Tuple4<Integer, Long, Long, String>> source = KafkaSource.<Tuple4<Integer, Long, Long, String>>builder() .setBootstrapServers("kafka-broker:9092") .setTopics("topic-name") .setGroupId("group-id") .setStartingOffsets(OffsetsInitializer.earliest()) .setDeserializer(new CustomKafkaDeserializer()) // 使用自定义反序列化器 .build(); ``` 这样,我们就可以在Flink程序中使用这个Source,每条记录都包含了分区、偏移量、时间戳消息体。 需要注意的是: - 如果Kafka消息的key也需要用到,可以修改元组结构,增加key字段。 - 时间戳的类型:在Kafka消息中,时间戳可以是消息创建时间(CreateTime)或者日志追加时间(LogAppendTime),这取决于Kafka Broker的配置。在`ConsumerRecord`中,`timestamp()`方法返回的就是这个时间戳,类型可以通过`timestampType()`方法获取。 - 如果消息体不是字符串,需要根据实际格式进行反序列化(比如JSON、Avro等)。 引用说明:[^1]中提到从旧版 FlinkKafkaConsumer 迁移到新 KafkaSource,本文示例正是使用新版KafkaSource API。 §§相关问题§§ 1. Flink中如何同时获取Kafka消息的keyvalue? 2.Flink中处理Kafka消息时如何设置水位线(Watermark)? 3. 如何确保Flink消费Kafka的Exactly-Once语义? </think>### 在 Flink 1.20.1Kafka-client 3.4.0 中获取 Kafka 元数据的方法 在 Flink 1.20.1 使用 KafkaSource API 时,可通过实现自定义反序列化器获取分区、偏移量、时间戳消息体。以下是完整解决方案: #### 1. **自定义反序列化类实现** ```java import org.apache.flink.api.common.serialization.DeserializationSchema; import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.connector.kafka.source.reader.deserializer.KafkaRecordDeserializationSchema; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.flink.util.Collector; public class KafkaMetadataDeserializer implements KafkaRecordDeserializationSchema<Tuple4<Integer, Long, Long, String>> { @Override public void deserialize(ConsumerRecord<byte[], byte[]> record, Collector<Tuple4<Integer, Long, Long, String>> out) { // 提取元数据 int partition = record.partition(); long offset = record.offset(); long timestamp = record.timestamp(); String messageBody = new String(record.value(), StandardCharsets.UTF_8); // 假设文本格式 // 输出四元组 (分区, 偏移量, 时间戳, 消息体) out.collect(Tuple4.of(partition, offset, timestamp, messageBody)); } @Override public TypeInformation<Tuple4<Integer, Long, Long, String>> getProducedType() { return TypeInformation.of(new TypeHint<Tuple4<Integer, Long, Long, String>>() {}); } } ``` #### 2. **在 KafkaSource 中配置使用** ```java KafkaSource<Tuple4<Integer, Long, Long, String>> source = KafkaSource .<Tuple4<Integer, Long, Long, String>>builder() .setBootstrapServers("kafka-brokers:9092") .setTopics("your-topic") .setGroupId("flink-group") .setDeserializer(new KafkaMetadataDeserializer()) // 关键配置 .setStartingOffsets(OffsetsInitializer.earliest()) .build(); ``` #### 3. **流处理应用示例** ```java DataStream<Tuple4<Integer, Long, Long, String>> kafkaStream = env.fromSource( source, WatermarkStrategy.noWatermarks(), "Kafka Source" ); // 示例:打印元数据 kafkaStream.map(tuple -> String.format("分区:%d 偏移量:%d 时间戳:%d 消息:%s", tuple.f0, tuple.f1, tuple.f2, tuple.f3)) .print(); ``` #### 关键说明 1. **元数据类型**: - `partition`:消息所在分区(int) - `offset`:消息在分区内的唯一偏移量(long) - `timestamp`:消息时间戳(long,毫秒) - `messageBody`:消息内容(可替换为其他反序列化逻辑) 2. **消息体处理**: - 文本消息:使用 `new String(record.value())` - 二进制数据:直接使用 `record.value()` 返回 `byte[]` - 复杂格式:结合 `DeserializationSchema` 解析 Protobuf/Avro 3. **Exactly-once 保障**: 启用 Checkpoint 确保状态一致性: ```java env.enableCheckpointing(5000); // 每5秒触发一次Checkpoint ``` > **注意**:新版 `KafkaSource`(Flink 1.14+)直接通过 `ConsumerRecord` 暴露元数据,无需旧版 `FlinkKafkaConsumer` 的 `AbstractPartitionDiscoverer` [^1]。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Hushhhh1o24

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

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

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

打赏作者

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

抵扣说明:

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

余额充值