Flink Connectors 介绍与 Kafka Connector

时间秒杀一切 提交于 2020-02-15 08:45:32

1. Streaming Connectors

Connector 的作用就相当于一个连接器,连接 Flink 计算引擎跟外界存储系统。
目前常用的 Connector 有以下几种:

预定义的 source 和 sink

在这里插入图片描述

  • 基于文件的 source 和 sink
// 从文本文件中读取数据
env.readTextFile(path);
// 以文本的形式读取该文件中的内容
env.readFile(fileInputFormat, path);

// 将结果已文本格式写出到文件中
DataStream.writeAsText(path) 
// 将结果已 csv 格式写出到文件中
DataStream.writeAsCsv(path)
  • 基于 Socket 的 Source 和 Sink
// 根据 Socket 的 host name 及 port,创建基于 Socket 的 source
env.socketTextStream(hostname, port);

// 将结果写入到一个socket中
DataStream.writeToSocket(hostname, port)
  • 基于内存 Collections、Iterators 的 Source
// 直接基于内存中的集合或者迭代器创建source
env.fromCollection(conllectionA or iteratorB);
// or
env.fromElements(arrayC);

// 将结果直接标准输出
DataStream.print();
// 将结果直接表春错误输出
DataStream.printToErr();

Boundled connectors

Flink 里已经提供了一些绑定的 Connector,例如 kafka source 和 sink,Es sink等。
该部分是 Flink 项目源代码里的一部分,但是真正意义上不算作 Flink 引擎相关逻辑,并且该部分没有打包在二进制的发布包里面。在提交 Job 时候需要注意, job 代码 jar 包中一定要将相应的 connetor 相关类打包进去,否则在提交作业时就会失败,提示找不到相应的类,或初始化某些类异常。

Apache Bahir 中的连接器

Apache Bahir 最初是从 Apache Spark 中独立出来的项目,提供不限于 Spark 相关的扩展/插件、连接器和其他可插入组件的实现。通过提供多样化的流连接器(streaming connectors)和 SQL 数据源扩展分析平台的覆盖面。如有需要写到 flume、redis 的需求的话,可以使用该项目提供的 connector。

异步 IO

流计算中经常需要与外部存储系统交互,比如需要关联 MySQL 中的某个表。
一般来说,如果用同步 I/O 的方式,会造成系统中出现大的等待时间,影响吞吐和延迟。为了解决这个问题,异步 I/O 可以并发处理多个请求,提高吞吐,减少延迟。
在这里插入图片描述
Async 的原理可参考官方文档:
https://ci.apache.org/projects/flink/flink-docs-release-1.3/dev/stream/asyncio.html

2. Flink Kafka Connector

Kafka 是一个分布式的、分区的、多副本的、 支持高吞吐的、发布订阅消息系统。生产环境环境中 Flink 经常会跟 kafka 进行一些数据交换。

Flink 提供了现成的构造FlinkKafkaConsumer、Producer 的接口,可以直接使用。
在这里插入图片描述
Kafka 有多个版本,多个版本之间的接口协议会不同。Flink 针对不同版本的 kafka 有相应的版本的 Consumer 和 Producer。例如:针对 08、09、10、11 版本,Flink 对应的 consumer 分别是 FlinkKafkaConsumer 08、09、010、011,producer 也是。

2.1 Flink Kafka Consumer

1)反序列化

kafka 中数据都是以二进制 byte 形式存储的。读到 Flink 系统中之后,需要将二进制数据转化为具体的 java、scala 对象。所以需要实现一个 schema 类,定义如何序列化和反序列数据。
在这里插入图片描述

  • 反序列化
    反序列化时需要实现 DeserializationSchema 接口,并重写 deserialize(byte[] message) 函数。
    如果是反序列化 kafka 中 kv 的数据时,需要实现 KeyedDeserializationSchema 接口,并重写 deserialize(byte[] messageKey, byte[] message, String topic, int partition, long offset) 函数。

  • 常用的序列化反序列化的 schema 类
    SimpleStringSchema,按字符串方式进行序列化、反序列化。
    TypeInformationSerializationSchema,它可根据 Flink 的 TypeInformation 信息来推断出需要选择的 schema。
    JsonDeserializationSchema 使用 jackson 反序列化 json 格式消息,并返回 ObjectNode,可以使用 .get(“property”) 方法来访问相应字段。

2)消费起始位置设置

Flink 提供非常好的封装来设置作业从 kafka 消费数据最开始的起始位置。在构造好的 FlinkKafkaConsumer 类后面调用如下相应函数,即可设置合适的起始位置。

  • setStartFromGroupOffsets
    默认的策略,从 group offset 位置读取数据,group offset 指的是 kafka broker 端记录的某个 group 的最后一次的消费位置。但是 kafka broker 端没有该 group 信息,会根据 kafka 的参数”auto.offset.reset”的设置来决定从哪个位置开始消费。
  • setStartFromEarliest
    从 kafka 最早的位置开始读取。
  • setStartFromLatest
    从 kafka 最新的位置开始读取。
  • setStartFromTimestamp(long)
    从时间戳大于或等于指定时间戳的位置开始读取。Kafka 时戳,是指 kafka 为每条消息增加另一个时戳。该时戳可以表示消息在 proudcer 端生成时的时间、或进入到 kafka broker 时的时间。
  • setStartFromSpecificOffsets
    从指定分区的 offset 位置开始读取,如指定的 offsets 中不存某个分区,该分区从 group offset 位置开始读取。此时需要用户给定一个具体的分区、offset 的集合。
    具体的使用方法可以参考下图。
    在这里插入图片描述
    注意:因为 Flink 框架有容错机制,如果作业故障,如果作业开启 checkpoint,会从上一次 checkpoint 状态开始恢复。或者在停止作业的时候主动做 savepoint,启动作业时从 savepoint 开始恢复。这两种情况下恢复作业时,作业消费起始位置是从之前保存的状态中恢复,与上面提到跟 kafka 这些单独的配置无关。

3)topic 和 partition 动态发现

因为业务的需要,Kafka 可能会有 topic 的增加或 partition 的扩容,Flink 可以动态的监测到 topic 和 partition 的变化。
在这里插入图片描述

  • 开启动态发现
    在构建 FlinkKafkaConsumer 时的 properties 中设置 flink.partition-discovery.interval-millis 参数为非负值,表示开启动态发现的开关,以及设置的时间间隔。此时 FlinkKafkaConsumer 内部会启动一个单独的线程定期去 kafka 获取最新的 meta 信息。
  • 针对于topic的变化
    在构建 FlinkKafkaConsumer 时,topic 的描述可以传一个正则表达式描述的 pattern。每次获取最新 kafka meta 时获取正则匹配的最新 topic 列表。
  • 针对于partition的变化
    设置前面的动态发现参数,在定期获取 kafka 最新 meta 信息时会匹配新的 partition。新发现的 partition 从最早的位置开始读取。

4)commit offset 方式

Flink kafka consumer commit offset 方式需要区分是否开启了 checkpoint。
在这里插入图片描述

  • checkpoint 关闭时
    commit offset 要依赖于 kafka 客户端的 auto commit。需设置 enable.auto.commit,auto.commit.interval.ms 参数到 consumer properties,就会按固定的时间间隔定期 auto commit offset 到 kafka。
  • checkpoint 开启时
    此时作业消费的 offset 是 Flink 在 state 中自己管理和容错。此时提交 offset 到 kafka,一般都是作为外部进度的监控。想实时知道作业消费的位置和 lag 情况,需要 setCommitOffsetsOnCheckpoints 为 true 来设置当 checkpoint 成功时提交 offset 到 kafka。此时 commit offset 的间隔就取决于 checkpoint 的间隔,所以此时从 kafka 一侧看到的 lag 可能并非完全实时,如果 checkpoint 间隔比较长 lag 曲线可能会是一个锯齿状。

5)Timestamp Extraction/Watermark 生成

使用 EventTime 属性时,需要指定从消息中提取时戳和生成水位的函数。
FlinkKakfaConsumer 构造的 source 后直接调用 assignTimestampsAndWatermarks 函数设置水位生成器的好处是此时是每个 partition 一个 watermark assigner,如下图。source 生成的时戳为多个 partition 时戳对齐后的最小时戳。此时在一个 source 读取多个 partition,并且 partition 之间数据时戳有一定差距的情况下,因为在 source 端 watermark 在 partition 级别有对齐,不会导致数据读取较慢 partition 数据丢失。
在这里插入图片描述
所以对于 EventTime 的情况下,建议在 Source 后尽快设置 Timestamp 和 Watermark。

2.2 Flink Kafka Producer

1)Producer 写出时的 Partition 分区

使用 FlinkKafkaProducer 往 kafka 中写数据时,如果不单独设置 partition 策略,会默认使用 FlinkFixedPartitioner。

  • FlinkFixedPartitioner(默认)
    该 partitioner 分区的方式是 task 所在的并发 id 对 topic 总 partition 数取余:parallelInstanceId % partitions.length。
    此时如果 sink 为 4,paritition 为 1,则 4 个 task 往同一个 partition 中写数据。但当 sink task < partition 个数时会有部分 partition 没有数据写入,例如 sink task 为2,partition 总数为 4,则后面两个 partition 将没有数据写入。
  • partition 设置为 null
    如果构建 FlinkKafkaProducer 时,partition 设置为 null,此时会使用 kafka producer 默认分区方式,非 key 写入的情况下,使用 round-robin 的方式进行分区,每个 task 都会轮循的写下游的所有 partition。该方式下游的 partition 数据会比较均衡,但是缺点是 partition 个数过多的情况下需要维持过多的网络连接,即每个 task 都会维持跟所有 partition 所在 broker 的连接。
  • custom partitioner
    当然用户也可以自定义 FlinkKafkaProducer 写出时的分区逻辑。

2)Producer 容错

在这里插入图片描述
Flink kafka 09、010 版本下,通过 setLogFailuresOnly 为 false,setFlushOnCheckpoint 为 true,能达到 at-least-once 语义。
setLogFailuresOnly,默认为 false,是控制写 kafka 失败时,是否只打印失败的 log 不抛异常让作业停止。
setFlushOnCheckpoint,默认为 true,是控制是否在 checkpoint 时 fluse 数据到 kafka,保证数据已经写到 kafka。否则数据有可能还缓存在 kafka 客户端的 buffer 中,并没有真正写出到 kafka,此时作业挂掉数据即丢失,不能做到至少一次的语义。

Flink kafka 011 版本下,通过两阶段提交的 sink 结合 kafka 事务的功能,可以保证端到端精准一次。
详情参考:https://www.ververica.com/blog/end-to-end-exactly-once-processing-apache-flink-apache-kafka

3. Q&A

Q1: 在 Flink consumer 的并行度的设置:是对应 topic 的 partitions 个数吗?要是有多个主题数据源,并行度是设置成总体的 partitions 数吗?

A: 这个并不是绝对的,跟 topic 的数据量也有关,如果数据量不大,也可以设置小于 partitions 个数的并发数。但不要设置并发数大于 partitions 总数,因为这种情况下某些并发因为分配不到 partition 导致没有数据处理。

Q2: 如果 partitioner 传 null 的时候是 round-robin 发到每一个 partition?如果有 key 的时候行为是 kafka 那种按照 key 分布到具体分区的行为吗?

A: 如果在构造 FlinkKafkaProducer 时,如果没有设置单独的 partitioner,则默认使用 FlinkFixedPartitioner,此时无论是带 key 的数据,还是不带 key。如果主动设置 partitioner 为 null 时,不带 key 的数据会 round-robin 的方式写出,带 key 的数据会根据 key,相同 key 数据分区的相同的 partition,如果 key 为 null,再轮询写。不带 key 的数据会轮询写各 partition。

Q3: 如果 checkpoint 时间过长,offset 未提交到 kafka,此时节点宕机了,重启之后的重复消费如何保证呢?
A: 首先开启 checkpoint 时 offset 是 Flink 通过状态 state 管理和恢复的,并不是从 kafka 的 offset 位置恢复。在 checkpoint 机制下,作业从最近一次 checkpoint 恢复,本身是会回放部分历史数据,导致部分数据重复消费,Flink 引擎仅保证计算状态的精准一次,要想做到端到端精准一次需要依赖一些幂等的存储系统或者事务操作。

以上内容均来自对 https://www.bilibili.com/video/av56902622/ 的学习总结。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!