这是我参与「第四届青训营 」笔记创作活动的第5天
DataStream Connector
Kafka可以作为Flink的数据流连接器
Flink provides an Apache Kafka connector for reading data from and writing data to Kafka topics with exactly-once guarantees.
Flink 提供了一个 Kafka 连接器,用于从 Kafka 主题 读取数据 和 将数据写入到 Kafka 主题 中,并保证一致性语义。
-
1 依赖
-
-
Apache Flink ships with a universal Kafka connector which attempts to track the latest version of the Kafka client. The version of the client it uses may change between Flink releases. Modern Kafka clients are backwards compatible with broker versions 0.10.0 or later. For details on Kafka compatibility, please refer to the official Kafka documentation.
- Flink 附带了一个通用的 Kafka 连接器,它试图跟踪最新版本的 Kafka 客户端。它使用的客户端版本可能会在 Flink 版本之间发生变化。新式的 Kafka 客户端向后兼容代理版本 0.10.0 或更高版本。有关 Kafka 兼容性的详细信息,请参阅 Kafka 官方文档。
-
<dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-connector-kafka</artifactId> <version>1.15.1</version> </dependency> -
Flink’s streaming connectors are not part of the binary distribution. See how to link with them for cluster execution here.
-
Flink 的流连接器不是二进制分发的一部分。在此处查看如何与它们链接以进行集群执行。
2 Kafka Source
-
2.1 用法
-
-
Kafka source provides a builder class for constructing instance of KafkaSource. The code snippet below shows how to build a KafkaSource to consume messages from the earliest offset of topic “input-topic”, with consumer group “my-group” and deserialize only the value of message as string.
- Kafka Source 提供了一个构建器类,用于构建 KafkaSource 的实例。下面的代码片段展示了如何构建一个 KafkaSource 来消费主题“input-topic”的最早偏移量的消息,使用消费者组“my-group”并将消息的值反序列化为字符串。
-
KafkaSource < String > source = KafkaSource .< String > builder () . setBootstrapServers ( brokers ) . setTopics ( "input-topic" ) . setGroupId ( "my-group" ) . setStartingOffsets ( OffsetsInitializer . earliest ()) . setValueOnlyDeserializer ( new SimpleStringSchema ()) . build (); env . fromSource ( source , WatermarkStrategy . noWatermarks (), "Kafka Source" ); -
The following properties are required for building a KafkaSource:
- Bootstrap servers, configured by
setBootstrapServers(String)
- Topics / partitions to subscribe, see the following Topic-partition subscription for more details.
- Deserializer to parse Kafka messages, see the following Deserializer for more details.
- Bootstrap servers, configured by
- 构建 KafkaSource需要以下属性:
- 引导服务器,由
setBootstrapServers(String) - 要订阅的主题/分区,有关详细信息,请参阅以下 主题分区订阅。
- Deserializer 用于解析 Kafka 消息,有关详细信息,请参阅以下 Deserializer 。
-
-
2.2 主题-分区订阅
-
- Kafka源码提供了3种 主题-分区 订阅方式:
- 主题列表,订阅主题列表中所有分区的消息。例如:
-
KafkaSource . builder (). setTopics ( "topic-a" , "topic-b" ); - 主题模式,订阅来自名称与提供的正则表达式匹配的所有主题的消息。例如:
-
KafkaSource . builder (). setTopicPattern ( "topic.*" ); - 分区集,订阅提供的分区集合中的分区。例如:
-
final HashSet < TopicPartition > partitionSet = new HashSet <>( Arrays . asList ( new TopicPartition ( "topic-a" , 0 ), // Partition 0 of topic "topic-a" new TopicPartition ( "topic-b" , 5 ))); // Partition 5 of topic "topic-b" KafkaSource . builder (). setPartitions ( partitionSet );
2.3 反序列化
解析 Kafka 消息需要反序列化器。Deserializer(反序列化模式)可以通过 配置setDeserializer(KafkaRecordDeserializationSchema),其中 KafkaRecordDeserializationSchema定义了如何反序列化 Kafka ConsumerRecord。
如果只ConsumerRecord需要 Kafka 的值,您可以在构建器中使用setValueOnlyDeserializer(DeserializationSchema),其中 DeserializationSchema定义了如何反序列化 Kafka 消息值的二进制文件。
您还可以使用一个Kafka Deserializer来反序列化 Kafka 消息值。例如,StringDeserializer用于将 Kafka 消息值反序列化为字符串:
import org.apache.kafka.common.serialization.StringDeserializer ;
KafkaSource .< String > builder (). setDeserializer ( KafkaRecordDeserializationSchema . valueOnly ( StringDeserializer . class ));
2.4 起始偏移
Kafka 源可以通过指定从不同的偏移量开始消费消息 OffsetsInitializer。内置初始化器包括:
KafkaSource . builder () // Start from committed offset of the consuming group, without reset strategy . setStartingOffsets ( OffsetsInitializer . committedOffsets ()) // Start from committed offset, also use EARLIEST as reset strategy if committed offset doesn't exist . setStartingOffsets ( OffsetsInitializer . committedOffsets ( OffsetResetStrategy . EARLIEST )) // Start from the first record whose timestamp is greater than or equals a timestamp (milliseconds) . setStartingOffsets ( OffsetsInitializer . timestamp ( 1657256176000L )) // Start from earliest offset . setStartingOffsets ( OffsetsInitializer . earliest ()) // Start from latest offset . setStartingOffsets ( OffsetsInitializer . latest ());
如果上面的内置初始化程序无法满足您的要求,您还可以实现自定义偏移初始化程序。
如果未指定偏移初始化器,则默认使用OffsetsInitializer.earliest() 。
2.5 有界性
Kafka 源旨在支持流式和批处理运行模式。默认情况下,KafkaSource 设置为流式运行,因此在 Flink 作业失败或被取消之前永远不会停止。您可以使用 setBounded(OffsetsInitializer)指定停止偏移量并设置在批处理模式下运行的源。当所有分区都达到其停止偏移量时,源将退出。
您还可以将 KafkaSource 设置为以流模式运行,但仍使用setUnbounded(OffsetsInitializer). 当所有分区达到其指定的停止偏移量时,源将退出。
2.6 附加属性
除了上述属性之外,您还可以使用setProperties(Properties) 和 setProperty(String, String)为 KafkaSource 和 KafkaConsumer 设置任意属性。KafkaSource 有以下配置选项:
client.id.prefix定义用于 Kafka 消费者客户端 ID 的前缀
partition.discovery.interval.ms定义 Kafka 源发现新分区的时间间隔(毫秒)。有关更多详细信息,请参阅下面的动态分区发现 。
register.consumer.metrics指定是否在 Flink metric group 中注册 KafkaConsumer
commit.offsets.on.checkpoint指定是否在检查点向 Kafka 代理提交消费偏移量
有关 KafkaConsumer 的配置,您可以参考 Apache Kafka 文档 了解更多详细信息。
请注意,即使已配置,构建器也会覆盖以下键:
key.deserializer始终设置为ByteArrayDeserializer
value.deserializer始终设置为ByteArrayDeserializer
auto.offset.reset.strategy被OffsetsInitializer#getAutoOffsetResetStrategy()起始偏移量覆盖
partition.discovery.interval.mssetBounded(OffsetsInitializer)被调用时被覆盖为 -1
2.7 动态分区发现
为了在不重启 Flink 作业的情况下处理主题扩展或主题创建等场景,可以将 Kafka 源配置为在提供的主题-分区订阅模式下定期发现新分区。要启用分区发现,请为 property 设置一个非负值partition.discovery.interval.ms:
KafkaSource . builder (). setProperty ( "partition.discovery.interval.ms" , "10000" ); // discover new partitions per 10 seconds
默认情况下禁用 分区发现。您需要明确设置分区发现间隔才能启用此功能。
2.8 事件时间和水印
默认情况下,记录将使用嵌入在 Kafka 中的时间戳ConsumerRecord作为事件时间。您可以定义自己的WatermarkStrategy以从记录本身中提取事件时间,并在下游发出水印:
env . fromSource ( kafkaSource , new CustomWatermarkStrategy (), "Kafka Source With Custom Watermark Strategy" );
本文档描述了有关如何定义WatermarkStrategy.
2.9 空闲
如果并行度高于分区数,Kafka Source 不会自动进入空闲状态。您将需要降低并行度或向水印策略添加空闲超时。如果在这段时间内没有记录在流的分区中流动,则该分区被认为是“空闲的”,并且不会阻止下游操作符中水印的进度。
本文档 描述了有关如何定义WatermarkStrategy#withIdleness.
2.10 消费者抵消提交
Kafka source 在 checkpoint 完成时提交当前消耗的 offset,以确保 Flink 的 checkpoint 状态与 Kafka broker 上提交的 offset 之间的一致性。
如果未启用检查点,则 Kafka 源依赖于 Kafka 消费者内部的自动定期偏移提交逻辑,由 Kafka 消费者的属性配置enable.auto.commit并在其属性中配置auto.commit.interval.ms。
请注意,Kafka 源不依赖已提交的偏移量来实现容错。提交偏移量只是为了暴露消费者和消费组的进度以便监控。
2.11 监控
Kafka 源在各自的范围内公开了以下指标。
指标范围
| 范围 | 指标 | 用户变量 | 描述 | 类型 |
|---|---|---|---|---|
| 操作员 | currentEmitEventTimeLag | 不适用 | 从记录事件时间戳到源连接器发出记录的时间跨度¹:currentEmitEventTimeLag = EmitTime - EventTime. | 测量 |
| 水印滞后 | 不适用 | 水印滞后于挂钟时间的时间跨度:watermarkLag = CurrentTime - Watermark | 测量 | |
| 源空闲时间 | 不适用 | 源未处理任何记录的时间跨度:sourceIdleTime = CurrentTime - LastRecordProcessTime | 测量 | |
| 待定记录 | 不适用 | 源尚未获取的记录数。例如,Kafka 分区中消费者偏移后的可用记录。 | 测量 | |
| KafkaSourceReader.commitsSucceeded | 不适用 | 如果启用了偏移提交并启用了检查点,则成功向 Kafka 提交偏移的总数。 | 柜台 | |
| KafkaSourceReader.commitsFailed | 不适用 | 如果启用了偏移提交并启用了检查点,则偏移提交失败到 Kafka 的总数。请注意,将偏移量提交回 Kafka 只是公开消费者进度的一种手段,因此提交失败不会影响 Flink 的检查点分区偏移量的完整性。 | 柜台 | |
| KafkaSourceReader.committedOffsets | 主题,分区 | 对于每个分区,最后一次成功提交到 Kafka 的偏移量。特定分区的指标可以由主题名称和分区 ID 指定。 | 测量 | |
| KafkaSourceReader.currentOffsets | 主题,分区 | 每个分区的消费者当前读取偏移量。特定分区的指标可以由主题名称和分区 ID 指定。 | 测量 |
¹ 此指标是为最后处理的记录记录的瞬时值。提供此指标是因为延迟直方图可能很昂贵。瞬时延迟值通常是延迟的足够好的指示。
Kafka消费者指标
Kafka 消费者的所有指标也都注册在 group 下KafkaSourceReader.KafkaConsumer。例如,Kafka 消费者指标“records-consumed-total”将在 metric: 中报告 <some_parent_groups>.operator.KafkaSourceReader.KafkaConsumer.records-consumed-total。
您可以通过配置 option 来配置是否注册 Kafka 消费者的指标 register.consumer.metrics。默认情况下,此选项将设置为 true。
有关 Kafka 消费者的指标,您可以参考 Apache Kafka 文档 了解更多详细信息。
如果您遇到包含 的堆栈跟踪的警告 javax.management.InstanceAlreadyExistsException: kafka.consumer:[...],您可能正在尝试KafkaConsumers使用相同的 client.id 注册多个。警告表明并非所有可用指标都正确转发到指标系统。您必须确保 client.id.prefix为每个KafkaSource配置了不同的,并且 KafkaConsumer您的工作中没有其他人使用相同的client.id.
2.12 安全
为了启用包括加密和身份验证在内的安全配置,您只需将安全配置设置为 Kafka 源的附加属性。下面的代码片段显示了配置 Kafka 源以使用 PLAIN 作为 SASL 机制并提供 JAAS 配置:
KafkaSource . builder (). setProperty ( "security.protocol" , "SASL_PLAINTEXT" ). setProperty ( "sasl.mechanism" , "PLAIN" ). setProperty ( "sasl.jaas.config" , "org.apache.kafka.common.security.plain.PlainLoginModule required username="username" password="password";" );
对于更复杂的示例,使用 SASL_SSL 作为安全协议并使用 SCRAM-SHA-256 作为 SASL 机制:
KafkaSource . builder (). setProperty ( "security.protocol" , "SASL_SSL" ) // SSL configurations // Configure the path of truststore (CA) provided by the server . setProperty ( "ssl.truststore.location" , "/path/to/kafka.client.truststore.jks" ). setProperty ( "ssl.truststore.password" , "test1234" ) // Configure the path of keystore (private key) if client authentication is required . setProperty ( "ssl.keystore.location" , "/path/to/kafka.client.keystore.jks" ). setProperty ( "ssl.keystore.password" , "test1234" ) // SASL configurations // Set SASL mechanism as SCRAM-SHA-256 . setProperty ( "sasl.mechanism" , "SCRAM-SHA-256" ) // Set JAAS configurations . setProperty ( "sasl.jaas.config" , "org.apache.kafka.common.security.scram.ScramLoginModule required username="username" password="password";" );
请注意,sasl.jaas.config如果您在作业 JAR 中重新定位 Kafka 客户端依赖项,登录模块的类路径可能会有所不同,因此您可能需要将其重写为 JAR 中模块的实际类路径。
有关安全配置的详细说明,请参阅 Apache Kafka 文档中的“安全”部分。
2.13 幕后
如果您对新数据源 API 的设计下 Kafka 源如何工作感兴趣,您可能希望阅读此部分作为参考。有关新数据源 API 的详细信息,数据源 文档和 FLIP-27 提供了更多描述性讨论。
在新的数据源 API 的抽象下,Kafka 源由以下组件组成:
源拆分
Kafka 源中的源拆分表示 Kafka 主题的一个分区。Kafka 源拆分包括:
TopicPartition分裂代表
- 分区的起始偏移量
- 分区的停止偏移,仅当源运行在有界模式时可用
Kafka source split的状态也存储了partition当前消费的offset,当Kafka source reader为snapshot时,状态会转换为immutable split,将当前offset赋值给immutable split的起始offset。
您可以查看课程KafkaPartitionSplit并KafkaPartitionSplitState了解更多详细信息。
拆分枚举器
Kafka 的拆分枚举器负责在提供的主题分区订阅模式下发现新的拆分(分区),并将拆分分配给阅读器,以循环方式均匀分布在子任务中。请注意,Kafka 源的拆分枚举器会急切地将拆分推送给源阅读器,因此它不需要处理来自源阅读器的拆分请求。
源阅读器
Kafka 源码的源码阅读器扩展了提供的SourceReaderBase,并使用单线程多路复用线程模型,读取多个分配的拆分(分区),一个 KafkaConsumer 由一个驱动SplitReader。消息在从 Kafka 中获取后立即被反序列化SplitReader。拆分状态或消息消费的当前进度由 更新KafkaRecordEmitter,它还负责在下游发出记录时分配事件时间。
\
3 Kafka Sink
KafkaSink允许将记录流写入一个或多个 Kafka 主题。
3.1 用法
Kafka sink 提供了一个构建器类来构造一个 KafkaSink 的实例。下面的代码片段展示了如何将字符串记录写入 Kafka 主题,并保证至少一次的交付。
DataStream < String > stream = ...;
KafkaSink < String > sink = KafkaSink .< String > builder (). setBootstrapServers ( brokers ). setRecordSerializer ( KafkaRecordSerializationSchema . builder (). setTopic ( "topic-name" ). setValueSerializationSchema ( new SimpleStringSchema ()). build ()). setDeliverGuarantee ( DeliveryGuarantee . AT_LEAST_ONCE ). build ();
stream . sinkTo ( sink );
构建 KafkaSink需要以下属性:
- 引导服务器,
setBootstrapServers(String)
- 记录序列化器,
setRecordSerializer(KafkaRecordSerializationSchema)
- 如果您配置交货保证,
DeliveryGuarantee.EXACTLY_ONCE您也可以使用setTransactionalIdPrefix(String)
3.2 序列化器
您始终需要提供一个KafkaRecordSerializationSchema将传入元素从数据流转换为 Kafka 生产者记录。Flink 提供了一个模式构建器来提供一些常见的构建块,即键/值序列化、主题选择、分区。您也可以自行实现接口以施加更多控制权。
KafkaRecordSerializationSchema . builder (). setTopicSelector (( element ) -> {< your-topic-selection-logic >}). setValueSerializationSchema ( new SimpleStringSchema ()). setKeySerializationSchema ( new SimpleStringSchema ()). setPartitioner ( new FlinkFixedPartitioner ()). build ();
需要始终设置值序列化方法和主题(选择方法) 。 此外,也可以使用 Kafka 序列化器代替 Flink 序列化器,使用 setKafkaKeySerializer(Serializer)or setKafkaValueSerializer(Serializer)。
3.3 容错
总体上KafkaSink支持三种不同DeliveryGuarantee的s。必须启用For DeliveryGuarantee.AT_LEAST_ONCE和Flink 的检查点。DeliveryGuarantee.EXACTLY_ONCE默认情况下KafkaSink使用DeliveryGuarantee.NONE. 您可以在下面找到不同保证的说明。
DeliveryGuarantee.NONE不提供任何保证:如果 Kafka 代理出现问题,消息可能会丢失,如果 Flink 失败,消息可能会重复。
DeliveryGuarantee.AT_LEAST_ONCE:接收器将等待 Kafka 缓冲区中的所有未完成记录在检查点上被 Kafka 生产者确认。如果 Kafka 代理出现任何问题,不会丢失任何消息,但是当 Flink 重新启动时,消息可能会重复,因为 Flink 会重新处理旧的输入记录。
DeliveryGuarantee.EXACTLY_ONCE:在这种模式下,KafkaSink 会将所有消息写入 Kafka 事务中,这些消息将在检查点上提交给 Kafka。因此,如果消费者只读取已提交的数据(请参阅 Kafka 消费者配置隔离级别),则在 Flink 重启的情况下不会看到重复数据。但是,这会有效地延迟记录可见性,直到写入检查点,因此请相应地调整检查点持续时间。请确保您在同一个 Kafka 集群上运行的应用程序中使用唯一的 transactionalIdPrefix,这样多个正在运行的作业不会干扰它们的事务!此外,强烈建议调整 Kafka 事务超时(请参阅 Kafka 生产者 transaction.timeout.ms)»最大检查点持续时间 + 最大重启持续时间,否则当 Kafka 使未提交的事务过期时可能会发生数据丢失。
3.4 监控
Kafka sink 在各自的范围内公开了以下指标。
| 范围 | 指标 | 用户变量 | 描述 | 类型 |
|---|---|---|---|---|
| 操作员 | 当前发送时间 | 不适用 | 发送最后一条记录所需的时间。该指标是为最后处理的记录记录的瞬时值。 | 测量 |
4 Kafka Connector Metrics
Flink 的 Kafka 连接器通过 Flink 的Metrics 系统提供了一些指标来分析连接器的行为。生产者和消费者通过 Flink 的度量系统为所有支持的版本导出 Kafka 的内部度量。Kafka 文档在其文档中列出了所有导出的指标。
也可以register.consumer.metrics 通过本节概述的 KafkaSource 配置来禁用 Kafka 指标的转发,或者在使用 KafkaSink 时,您可以register.producer.metrics通过生产者属性将配置设置为 false。
5 启用 Kerberos 身份验证
Flink 通过 Kafka 连接器提供一流的支持,以对为 Kerberos 配置的 Kafka 安装进行身份验证。只需配置 Flinkflink-conf.yaml即可为 Kafka 启用 Kerberos 身份验证,如下所示:
- 通过设置以下内容来配置 Kerberos 凭据 -
security.kerberos.login.use-ticket-cache:默认情况下,这是trueFlink 将尝试在由kinit. 请注意,在部署在 YARN 上的 Flink 作业中使用 Kafka 连接器时,使用票证缓存的 Kerberos 授权将不起作用。
security.kerberos.login.keytab和security.kerberos.login.principal:要改用 Kerberos 密钥表,请为这两个属性设置值。
- Append
KafkaClienttosecurity.kerberos.login.contexts:这告诉 Flink 将配置的 Kerberos 凭据提供给 Kafka 登录上下文以用于 Kafka 身份验证。
启用基于 Kerberos 的 Flink 安全性后,您可以使用 Flink Kafka Consumer 或 Producer 对 Kafka 进行身份验证,只需在传递给内部 Kafka 客户端的提供的属性配置中包含以下两个设置:
- 设置
security.protocol为SASL_PLAINTEXT(默认NONE):用于与 Kafka 代理通信的协议。使用独立 Flink 部署时,您还可以使用SASL_SSL; 请在此处查看如何为 SSL 配置 Kafka 客户端。
- 设置
sasl.kerberos.service.name为kafka(默认kafka):此值应与sasl.kerberos.service.name用于 Kafka 代理配置的值匹配。客户端和服务器配置之间的服务名称不匹配将导致身份验证失败。
有关 Kerberos 安全性的 Flink 配置的更多信息,请参阅此处。您还可以在此处找到有关 Flink 如何在内部设置基于 Kerberos 的安全性的更多详细信息。
6 升级到最新的连接器版本
升级作业和 Flink 版本指南中概述了通用升级步骤。对于 Kafka,您还需要执行以下步骤:
- 不要同时升级 Flink 和 Kafka Connector 版本。
- 确保您
group.id为您的消费者配置了一个。
- 在消费者上设置
setCommitOffsetsOnCheckpoints(true),以便将读取偏移量提交给 Kafka。在停止和获取保存点之前执行此操作很重要。您可能必须在旧连接器版本上执行停止/重新启动循环才能启用此设置。
- 在消费者上设置
setStartFromGroupOffsets(true),以便我们从 Kafka 获取读取偏移量。这只有在 Flink 状态下没有读偏移的情况下才会生效,这也是为什么下一步非常重要的原因。
- 更改分配
uid的源/接收器。这确保新的源/接收器不会从旧的源/接收器操作符中读取状态。
- 开始新作业,
--allow-non-restored-state因为我们在保存点中仍然有先前连接器版本的状态。
7 故障排除
如果您在使用 Flink 时遇到 Kafka 问题,请记住 Flink 仅包装 了 KafkaConsumer或 KafkaProducer ,您的问题可能与 Flink 无关,有时可以通过升级 Kafka 代理、重新配置 Kafka 代理或在 Flink 中重新配置KafkaConsumer或KafkaProducer来解决。下面列出了一些常见问题的示例。
数据丢失
根据您的 Kafka 配置,即使在 Kafka 确认写入后,您仍然可能会遇到数据丢失。特别要记住 Kafka 配置中的以下属性:
acks
log.flush.interval.messages
log.flush.interval.ms
log.flush.*
上述选项的默认值很容易导致数据丢失。更多解释请参考 Kafka 文档。
未知的主题或分区异常
此错误的一个可能原因是正在进行新的领导者选举时,例如在重新启动 Kafka 代理之后或期间。这是一个可重试的异常,因此 Flink 作业应该能够重新启动并恢复正常运行。它也可以通过更改retries生产者设置中的属性来规避。但是,这可能会导致消息重新排序,如果不希望出现这种情况,可以通过设置max.in.flight.requests.per.connection为 1 来规避。
生产者FencedException
此异常的原因很可能是代理端的事务超时。随着 KAFKA-6119 的实现,(producerId, epoch)在事务超时后将被隔离,并且其所有未决事务都被中止(每个transactional.id都映射到一个producerId; 这在下面的博客文章中有更详细的描述)。