Flink-Kafka 连接器和exactly-once 的一些理解

本文详细介绍了Apache Flink如何与Kafka集成,包括如何配置数据源与目标,设置起始offset,自动发现新增分区,以及如何通过DeserializationSchema进行反序列化。此外,还深入探讨了Flink的容错机制和如何确保端到端的exactly-once语义。

Flink Source & Sink

在 Flink 中,Source 代表从外部获取数据源,Transfromation 代表了对数据进行转换操作,Sink 代表将内部数据写到外部数据源
一个 Flink Job 一般由 Source,Transformation,Sink 组成
Flink 提供了 Kafka Connector 用于消费/生产 Apache Kafka Topic 的数据。
Flink 的 kafka consumer 集成了 checkpoint 机制以提供精确一次的处理语义
在具体的实现过程中,Flink 不依赖于 kafka 内置的消费组位移管理,而是在内部自行记录和维护 consumer 的位移。

基本使用方式

val env = StreamExecutionEnvironment.getExecutionEnvironment
  val prop:Properties = new Properties
  prop.put("bootstrap.servers", "192.168.17.26:9092,192.168.17.27:9092,192.168.17.28:9092")
  prop.put("group.id","Flink_kafka_test_5")
  prop.put("enable.auto.commit","true")
  prop.put("auto.commit.interval.ms","100")
  val consumer = new FlinkKafkaConsumer010[String]("flink-test",new SimpleStringSchema,prop)
  env.addSource(consumer)
    .print()
  env.execute("job_name")

设置起始 offset

默认情况,从 group offset 开始读,即从消费者组(group.id)提交到 kafka broker 上的位移开始读取分区数据(对于老版本而言,位移是提交到 zookeeper 上)。
如果未找到位移,使用 auto.offset.reset 属性值来决定位移。该属性默认是 largest,即从最新的消息位移处开始消费。

consumer.setStartFromGroupOffsets()

设置从最早的 offset 开始读

consumer.setStartFromEarliest()

设置从最新的 offset 开始读

consumer.setStartFromLatest()

设置从指定的 offset 开始读(注意,这里的位移记录的是下一条待消费消息的位移,而不是最新的已消费消息的位移)

val offsets = new util.HashMap[KafkaTopicPartition, java.lang.Long]()
 offsets.put(new KafkaTopicPartition("flink-test", 1),600L)
 offsets.put(new KafkaTopicPartition("flink-test", 2),700L)
 consumer.setStartFromSpecificOffsets(offsets)

当任务从失败中恢复,或者手动的从 savepoint 恢复时,上述的这些设置位移的方法是不生效的。在恢复时,每个 kafka 分区的起始位移都是由保存在 savepoint 或者 checkpoint 中的位移来决定的

DeserializationSchema 反序列化

如何将从 kafka 中获取的字节流转换为 java Object ?Flink 提供了 DeserializationSchema 接口允许用户自己自定义这个序列化的实现。

该接口的方法

T deserialize(byte[] message) throws IOException

会在收到每一条 kafka 消息的时候被调用

为了方便使用,Flink 提供了一些反序列化的默认实现:

(1)SimpleStringSchema,可以将消息反序列化成字符串,使用方法:

val consumer = new FlinkKafkaConsumer010[String]("flink-test",new SimpleStringSchema,prop)

(2)JSONKeyValueDeserializationSchema,使用 jackson 将消息反序列化成 ObjectNode,并且构造函数中可以指定需不需要返回 metadata,metadata 包括 topic,offset,partition 信息


val consumer = new FlinkKafkaConsumer010[ObjectNode]("flink-test", new JSONKeyValueDeserializationSchema(true), prop)
  env.addSource(consumer)
    .map(f => (f.get("value"), f.get("metadata")))
    .print()

(3)JsonNodeDeserializationSchema,使用 jackson 将消息反序列化成 ObjectNode

val consumer = new FlinkKafkaConsumer010[ObjectNode]("flink-test", new JsonNodeDeserializationSchema, prop)

自动发现 kafka 新增的分区

在上游数据量猛增的时候,可能会选择给 kafka 新增 partition 以增加吞吐量,那么 Flink 这段如果不配置的话,就会永远读取不到 kafka 新增的分区了

prop.put("flink.partition-discovery.interval-millis", "30000")

表示每30秒自动发现 kafka 新增的分区信息

Flink的容错机制

当 Flink 开启了 checkpoint 的时候,Flink 会一边消费 topic 的数据,一边定时的将 offset 和 其他 operator 的状态记录到 checkpoint 中。

如果遇到了 job 失败的情况,那么 Flink 将会重启 job,从最后一个 checkpoint 中来恢复 job 的所有状态,然后从 checkpoint 中记录的 offset 开始重新对 topic 进行消费。

Flink 如何保证端到端的 exacly-once 语义

Flink 基于异步轻量级的分布式快照技术提供 Checkpoint 容错机制。
Flink 分布式快照的核心概念之一就是数据栅栏(barrier)。
Barrier 在数据源端插入,和数据流一起向下流动,(Barrier不会干扰正常的数据,数据流严格有序)
当 snapshot n 的 barrier 插入后,系统会记录当前 snapshot 位置值 n (用 Sn 表示),在 apache kafka 中,这个变量表示某个分区最后一次消费的偏移量。
这个位置值 Sn 会被发送到一个称为 checkpoint coordinate 模块(即 Flink 的 JobManager)。
在这里插入图片描述
barrier 插入后,随着数据一起向下游流动,从一个 operator 到 另一个 operator。
当一个 operator 从其输入流接收到所有标识 snapshot n 的barrier 时,它会向其所有输出流继续插入一个 标识 snapshot n 的 barrier。
当 sink operator (DAG 流的终点)从其输入流接收到所有 barrier n 时,它向 checkpoint coordinate 确认 snapshot n 完成。当所有 sink 都确认了这个快照,快照就被标识为完成。

在这里插入图片描述
有一个特性是,某个operator 只要一接收到 某个输入流的 barrier n,它就不能继续处理此数据流后续的数据,后续的数据会被放入到接收缓存(input buffer)中(如上图红框标识的缓存区)。
只有当 operator 从最后一个流中提取到 barrier n 时,operator 才会继续发射出所有等待向后发送的数据,然后发送 snapshot n 所属的 barrier。

那么如何保证 exactly-once 语义的?

假设现在 barrier 现在在 source 和 map 之间,任务挂掉了。下一次 Flink 会自动的重启任务,从上一次的快照中恢复。
会从 kafka 的上一次消费的地方开始消费。由于上一次 sink 还未接收到 所有的 barrier 就挂掉了,上一次的数据都被缓存在 input buffer 中,还未到 sink 中处理,这一次重新消费的记录会被sink继续处理。也就是没有多消费一条记录,也没有少消费一条记录。

<think>我们正在处理一个关于在Java 17环境下FlinkKafka集成的配置问题。用户可能遇到了集成上的问题,需要解决方案。 根据引用,我们知道Flink可以与Kafka集成,其中涉及ProducerConsumer的使用。同时,引用[2]提到了关于主类配置运行环境的问题,引用[3]提到了ExecutionEnvironmentSourceFunction/SinkFunction。 因此,我们需要考虑以下几个方面: 1. FlinkKafka的依赖配置(特别是Java 17环境下) 2. 创建Flink环境并配置Kafka SourceSink 3. 可能遇到的兼容性问题(因为Java 17引入了新特性,可能有些库尚未完全支持) 步骤: 1. 添加正确的依赖:Flink提供了与Kafka集成的连接器,但需要注意版本兼容性。 2. 编写Flink程序,使用Kafka作为数据源(Source)/或数据汇(Sink)。 3. 处理可能出现的配置问题,如序列化、反序列化、版本不匹配等。 注意:Java 17相对于之前版本(如Java 8)有较大的变化,特别是模块化移除了一些内部API。因此,我们需要确保所有依赖(包括FlinkKafka客户端以及相关库)都支持Java 17。 常见问题: - 如果使用Flink旧版本,可能不支持Java 17。因此,建议使用较新的Flink版本(如Flink 1.14或更高版本)。 - Kafka客户端版本需要与Kafka集群版本兼容,同时也要与FlinkKafka连接器版本匹配。 下面我们将详细说明如何配置。</think>### Java 17 环境下 Flink-Kafka 集成配置指南 在 Java 17 环境下配置 Flink-Kafka 集成时,需特别注意以下关键配置项常见问题解决方案: #### 1. **依赖配置(Maven示例)** ```xml <properties> <flink.version>1.17.0</flink.version> <!-- 支持Java 17的最低版本 --> <scala.binary.version>2.12</scala.binary.version> </properties> <dependencies> <!-- Flink Java API --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-java</artifactId> <version>${flink.version}</version> </dependency> <!-- Kafka连接器 --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-connector-kafka</artifactId> <version>${flink.version}</version> </dependency> <!-- Java 17 必需依赖 --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-clients</artifactId> <version>${flink.version}</version> </dependency> </dependencies> ``` **关键点**:必须使用 Flink ≥1.17.0 版本,早期版本不支持 Java 17[^2] #### 2. **Kafka Source 配置示例** ```java Properties props = new Properties(); props.setProperty("bootstrap.servers", "kafka-server:9092"); props.setProperty("group.id", "flink-consumer"); KafkaSource<String> source = KafkaSource.<String>builder() .setBootstrapServers("kafka-server:9092") .setTopics("input-topic") .setValueOnlyDeserializer(new SimpleStringSchema()) .setProperties(props) .build(); DataStream<String> stream = env.fromSource( source, WatermarkStrategy.noWatermarks(), "Kafka Source" ); ``` #### 3. **Java 17 特有配置问题** - **模块访问权限问题**: ```bash WARNING: Illegal reflective access by org.apache.kafka.common.network.Selector ``` **解决方案**:在 JVM 启动参数添加: ```bash --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED ``` - **序列化兼容性问题**: 使用 `SimpleStringSchema` 时若遇到 `ClassCastException`,改用: ```java .setDeserializer(KafkaRecordDeserializationSchema.valueOnly(StringDeserializer.class)) ``` #### 4. **生产环境最佳实践** 1. **Checkpoint 配置**: ```java env.enableCheckpointing(5000); // 5秒间隔 env.getCheckpointConfig().setCheckpointStorage("hdfs:///checkpoints"); ``` 2. **Exactly-Once 语义**: ```java // Sink配置 KafkaSink<String> sink = KafkaSink.<String>builder() .setBootstrapServers("kafka-server:9092") .setRecordSerializer(KafkaRecordSerializationSchema.builder() .setTopic("output-topic") .setValueSerializationSchema(new SimpleStringSchema()) .build() ) .setDeliveryGuarantee(DeliveryGuarantee.EXACTLY_ONCE) .setTransactionalIdPrefix("flink-tx-") .build(); ``` 3. **资源调优**: ```yaml # flink-conf.yaml taskmanager.memory.process.size: 4096m taskmanager.numberOfTaskSlots: 4 parallelism.default: 8 ``` #### 5. **常见故障排查** - **类加载冲突**:使用 `child-first.classloading` 配置 ```yaml classloader.resolve-order: parent-first ``` - **版本不匹配错误**:确保所有组件的 Java 17 兼容版本: ``` Flink ≥1.17.0, Kafka ≥3.0.0, Zookeeper ≥3.6.3 ``` - **认证问题**:SASL/SSL 配置需在 properties 中声明: ```java props.setProperty("security.protocol", "SASL_SSL"); props.setProperty("sasl.jaas.config", "org.apache.kafka.common.security.plain.PlainLoginModule required username='user' password='pwd';"); ``` [^1]: Kafka 作为分布式消息队列系统,Producer Consumer 分别负责消息的发送接收 [^2]: Flink 从 1.17 版本开始提供完整的 Java 17 支持,需注意主类配置 [^3]: ExecutionEnvironment 负责管理 Flink 运行时的集群配置资源分配
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值