wdgcd logo wdgcd personal lab
全文 4,728 字 约 14 分钟

flink20问-2

bg

1、Flink 中的 Broadcast State 是什么?它在分布式计算中的作用是什么?

broadcast state 是 flink 中一种特殊的状态机制,用于将状态信息广播到作业的所有并行实例中,它的作用是保证在分布式环境下,每个并行任务都能一致地访问相同的数据,它允许在流数据处理过程中,对某些全局配置或匹配条件进行全局共享,以便所有的子任务使用相同的配置信息。

flink state 是用于管理和保存流作业中间结构的一种机制,除了 Broadcast state,flink 还提供了 keyed state,用于每一个 key 对应子任务独立维护状态信息;

broadcast state 使用场景:动态配置更新、实时规则匹配(在实时风控、告警系统中,根据业务需要实时调整规则)、数据处理逻辑切换(根据不同条件切换处理逻辑)

实现方式:

数据广播:在 flink 中,可以使用 BroadcastStream 类来实现数据广播,并将其融合到原始数据流中

访问广播状态:可以通过 broadcast() 方法将常量流转化为广播流,然后在 BroadcastProcessFunction 中对齐进行处理

DataStream<String> text = env.socketTextStream("localhost", 9999);
MapStateDescriptor<String, String> broadcastStateDescriptor = new MapStateDescriptor<>("broadcastState", BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO);
BroadcastStream<String> broadcastStream = text.broadcast(broadcastStateDescriptor);

2、Flink 的时间语义有哪几种?如何选择合适的时间语义?

三种:事件时间:事件在源头设备或系统上产生的时间,这种时间语义更贴近业务实际情况;常用于金融交易分析、实时物联网数据检测

处理时间:事件在执行 flink 操作时的系统时间,这种时间语义简单高效,适合延迟和准确性要求不高的应用;常用于日志分析、数据监控

摄取时间:事件进入 flink 的事件,兼备事件时间和处理时间的特性,一般适用于实时性要求比较高且事件时间不可控的场景,这种方案,可能用于社交媒体数据和点击流分析

3、在 Flink 中,如何优化作业的并行度?有哪些调优方法?

a、设置全局并行度:通过 ExecutionEnvironment 的 setParallelism 方法来设置整个 Flink 作业的并行度

b、单独设置算子并行度:对于特定算子,可以使用 setParallelism 方法来单独设置,比如 map 或 filter 算子的并行度

c、使用资源自适应调度:flink 允许根据资源的实际情况动态调整并行度,可以利用 flink 的 AdaptiveScheduler 实现这一功能;

d、提升 slot 共享和使用:通过配置 Flink's task manager 的 slot 配置来实现更好的资源利用率,确保多个任务按需共享 slot

flink 的 slot 可以理解为是任务执行资源的定位单元,一个 slot 被视为独立的分配 cpu 和内存,通过调整 slot 可以使任务对资源的利用更加高效,例如一个 taskmanager 有 4 个 slot,可以让多个任务共享这些 slot,从而提高资源使用率

4、Flink 的窗口聚合操作是如何实现的?如何优化窗口的计算性能?

flink 的窗口聚合操作是使用 window 机制来实现的,window 可以指定一个时间范围或基于事件的范围,对数据进行分组,在这些组内进行聚合计算,常见的窗口类型包括滚动窗口、滑动窗口、会话窗口;

flink 使用了一种 keyed streams 技术,根据用户定义的 key 对数据进行分组,每个 key 下面的记录会分配到不同的窗口中,每个窗口都会有对应的 trigger,当窗口满足触发条件时,窗口内的数据会被传递给窗口函数进行聚合计算;

要优化窗口的计算性能:选择合适的窗口类型和大小;减少状态的大小(flink 窗口有状态,会在内存中维护窗口的状态数据,可以使用 ttl 机制 自动清除过期的状态数据);合理配置并行度;优化数据的分区和分布;应用提前触发策略,减少计算延迟;

5、在 Flink 中,如何实现容错?Flink 的容错机制是如何设计的?

flink 通过分布式快照来实现容错,具体而言,flink 定期为数据流创建检查点,并将这些检查点保存在可靠的存储中,如果出现故障,flink 会基于这些检查点重启任务,从而保证状态的一致性和数据的不丢失;

flink 的容错机制包括:checkpoint、state backend、recovery mechanism

两阶段提交协议:保证了端到端的精确一次的处理语义,flink 使用了两阶段提交协议,在提交过程中:

第一阶段:pre-commit,所有任务都把它们的状态写到检查点存储中,并通知 JobManager;

第二阶段:commit,一旦所有的 pre-commit 都完成,jobmanager 会指示所有任务提交这些检查点;

状态后端定义了状态数据的存储位置:主要有内存状态后端和 rocksdb 状态后端

容错配置:

execution.checkpointing.interval:检查点的间隔

state.backend:选择的状态后端,比如 memory / rocksdb

state.savepoints.dir:保存检查点和保存点的位置

6、Flink 中的 StateBackend 是什么?常见的 StateBackend 实现有哪些?

statebackend 负责将作业中的状态数据存储起来,以便在任务失败或重启时可以恢复,在流式计算中,状态的管理非常重要,因为它影响到系统的容错性和一致性

MemoryStateBackend:状态保存到 taskmanager 的内存

FsStateBackend:状态保存到分布式文件系统,如 hdfs 中

RocksDBStateBackend:利用 rocksdb 来存储状态,能方便地做增量 checkpoint

7、在 Flink 中,如何通过动态调整 Watermark 来处理延迟数据?

可以使用 WatermarkStrategy API 自定义 Watermark 生成逻辑,通过设定合适的延时策略,flink 可以容忍适当范围内的延迟数据,例如可以根据当前数据的时间戳和系统时间动态调整 watermark 的生成,以减少由于网络波动等原因导致的延迟数据丢失;

import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.eventtime.WatermarkGenerator;
import org.apache.flink.api.common.eventtime.WatermarkGeneratorSupplier;
import org.apache.flink.api.common.eventtime.TimestampAssignerSupplier;
import org.apache.flink.api.common.eventtime.WatermarkOutput;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.datastream.DataStream;

import java.time.Duration;

public class DynamicWatermarkExample {
    public static void main(String[] args) throws Exception {
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStream<MyEvent> eventStream = // your data source here

        WatermarkStrategy<MyEvent> watermarkStrategy = WatermarkStrategy
            .<MyEvent>forBoundedOutOfOrderness(Duration.ofSeconds(20)) // 设置初始延迟时间
            .withTimestampAssigner((event, timestamp) -> event.getEventTime())
            .withDynamicWatermarkFunction(context -> new WatermarkGenerator<MyEvent>() {
                @Override
                public void onEvent(MyEvent event, long eventTimestamp, WatermarkOutput output) {
                    // your custom logic to dynamically adjust watermark
                    output.emitWatermark(new Watermark(eventTimestamp - determineAllowedLateness(event)));
                }

                @Override
                public void onPeriodicEmit(WatermarkOutput output) {
                    // no-op, as we already emit watermark on every event
                }

                private long determineAllowedLateness(MyEvent event) {
                    // 自定义逻辑,计算最大允许延迟时间
                    return 5000L; // 比如延迟5秒
                }
            });

        eventStream.assignTimestampsAndWatermarks(watermarkStrategy);

        env.execute("Dynamic Watermark Example");
    }
}

8、Flink 如何与 Kafka 集成?它们之间的集成方式是什么?

Flink 提供了 FlinkKafkaConsumer 和 FlinkKafkaProducer 两个类来实现这一功能;

flinkkafkaconsumer:用于从 kafka 读取数据需要提供 kafka topic 名、反序列化器和 kafka 配置如服务器地址;

flinkkafkaproducer:用于将数据写入 kafka,需要提供 kafka 主题名称、序列化器和 kafka 配置;

flink 支持与 kafka 完全集成的精确一次语义,通过 checkpoint 机制和 kafka 的事务性 producer 实现;

// 从 Kafka 中读取数据
FlinkKafkaConsumer<String> consumer = new FlinkKafkaConsumer<>(
    "input-topic",
    new SimpleStringSchema(),
    properties
);
DataStream<String> stream = env.addSource(consumer);

// 处理数据,例如转换为大写
DataStream<String> upperCaseStream = stream.map(String::toUpperCase);

// 将数据写入 Kafka
FlinkKafkaProducer<String> producer = new FlinkKafkaProducer<>(
    "output-topic",
    new SimpleStringSchema(),
    properties
);
upperCaseStream.addSink(producer);

9、Flink 的 Task Slot 是如何设计的?它在资源管理中起到什么作用?

每个 taskmanager 包含若干个 task slot,每个 task slot 被分配给一个或多个正在执行的任务,task slot 的设计主要出于这些考虑:

a、资源隔离:每个 task slot 代表一组资源,如 cpu、内存等,通过 task slot,flink 能够更精确地控制资源分配和隔离,防止任务间资源争用;

b、task slot 的数量直接影响任务的并行度,为每个任务分配足够的 task slot 可以提高任务的并行执行能力;

c、task slot 允许任务在不同的 taskmanager 之间迁移,从而实现任务的故障恢复和高可用性

在资源管理中,task slot 可以实现:

a、精确的资源控制:通过设定 task slot 的数量和大小,可以控制每个 task manager 的资源使用情况,确保任务获得所需的资源;

b、任务调度优化:在任务调度时,flink 会考虑 task slot 的可用性,确保任务可以在最短时间内获得资源并执行;

c、弹性伸缩:通过动态调整 task slot 的数量,flink 可以实现集群资源的弹性伸缩,适应不同的工作负载

slot sharing:通过 slot sharing 机制,flink 允许多个任务共享同一个 task slot,这可以提高资源利用率,减少资源浪费,任务之所以可以共享 slot,是因为它们可能在同一个作业中,或者有相似的资源需求和执行时间窗口;

flink 的执行模型将作业拆分为多个 task,这些 task 按照预定义的并行度分配给各个 task slot,task slot 的数量和分布会直接影响作业的整体执行性能;

10、Flink 的批流一体化架构是如何实现的?有哪些典型应用场景?

批流一体架构主要通过统一 API、统一执行引擎和统一存储来实现,flink 的 datastream api 和 dataset api 提供了统一的编程模型,开发者可以用同一套代码处理批量和流数据,同时 flink 的执行引擎能够动态地调度作业的执行策略,以适应不同的数据处理需求,flink 还支持从同一个存储系统读取数据,无论是批处理还是流处理;

典型应用场景:实时数据处理、批处理任务、混合处理任务(实时电商推荐系统既需要实时计算用户画像,有需要定时离线计算推荐模型);

统一 api:统一 datastream api(主要面向流处理)、dataset api(主要面向批处理),在 flink 1.12 后,这两个 api 逐渐合并

统一执行引擎:flink 的引擎可以根据任务的特性选择合适的执行策略,比如对于流处理flink 会使用流优先调度,批处理则是批处理优化策略;

容错:基于 chandy-lamport 算法的异步快照机制,可以保证在系统出现故障时不丢失数据

11、在 Flink 中,如何进行作业的监控和调优?有哪些常用的监控工具?

监控 flink 作业:

a、使用 flink 内置的 web ui,通过访问 flink job manager 提供了 web 页面,查看作业的执行情况、资源使用情况和历史任务记录等信息;

b、设置并使用 flink 的监控指标,这些 metrics 可以暴露给外部的监控系统,比如 prometheus 和 grafana 进行可视化监控;

c、利用日志,分析 flink 作业的日志信息,通过配置适当的日志级别可以捕捉和分析重要的运行时信息以及错误信息

d、监控 taskmanager、jobmanager 等组件的 cpu、内存和网络使用情况来判断集群健康状态和负载情况

调优 flink 作业:

a、优化并行度:通过调整算子的并行度,更好利用 cpu 和内存资源

b、调整 checkpoint 设置:合理设置 checkpoint 频率和保存时间,可以在保证作业稳定性的同时,减少对性能的影响

c、合理分配和调整 flink 作业的 task slots、内存配置等

d、尽可能减少 shuffle 操作,尽量避免操作状态存储的数据量过大等,优化数据处理过程中各个步骤的性能

e、使用 flink 的 autoscaler 组件,可以基于实时的负载情况自动调整作业的并行度,从而在动态环境中保持高效运行

12、Flink 的算子状态和键控状态有什么区别?如何选择使用?

算子状态 Operator State 和键控状态 Keyed State 都是用来管理和维护流计算任务中的状态的机制,它们在应用场景和使用方式上有所不同:

算子状态:它跟算子相关联,算子的每个并行任务都会有自己的状态,主要用于无键控流的情形,例如在 源算子中维护 kafka 连接的偏移量。在 FlapMapFunction 中应用 ListState 维护算子状态;

键控状态:它与键相关联,当数据流被某个键分区后,每个键会有一个状态,主要用于有键控流的情形,例如在键控的窗口操作和分布计算中维护每个键的累积计数;适用于比如窗口操作,大规模计数器等应用;在 KeyedProcessFunction 中使用 ValueState 或 ListState 维护键控状态;

13、在 Flink 中,如何进行 State 的清理?有哪些常见的状态过期策略?

a、ttl 机制:通过配置 state 的 ttl 来自动管理状态的过期和清理,当状态过期时间到达后,它会被标记为可回收状态,flink 会在合适的时间进行清理,配置 ttl 通常通过 statettlconfig 来完成

StateTtlConfig ttlConfig = StateTtlConfig
    .newBuilder(Time.minutes(10))
    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
    .cleanupFullSnapshot() // 说明这里可以指定不同的清理策略
    .build();

b、基于事件时间或处理时间的清理:

利用 event time 或者 process time 来决定何时应该清理状态,通过设定一个时间窗口,当时间窗口关闭时可以清理不再需要的状态,例如通过 timeservice 来设置一个定时器,定时器到期触发状态清理;

public void onTimer(long timestamp, OnTimerContext ctx, Collector<OUT> out) throws Exception {
    // 在这里进行状态的清理
    state.clear();
}

c、增量清理:只有在需要访问该状态时,才检查其是否已经过期,过期则清理,这种机制在设置高 ttl 的情况下,可以极大减少不必要的状态访问开销

d、savepoints 和 checkpoints:也可以通过定期输出 savepoint 和 checkpoints 来清理状态,在生成二者之后,旧的未引用的状态会被自动回收

14、Flink 中的 RocksDB StateBackend 是如何实现的?它的优缺点是什么?

通过 rocksdb 数据库将状态信息存储到本地磁盘上,在具体实现时,fluent 会将每个任务实例的状态数据碎片化存储到多个 rocksdb 实例中;

优点:

持久化强大、崩溃回复、高性能

缺点:

磁盘 IO 开销、复杂性高、本地存储依赖

15、Flink 中的异步 I/O 是如何实现的?如何提高异步操作的性能?

通过 AsyncFunction 接口实现,允许发起异步请求并在获取到结果时处理相应,从而避免了 IO 操作阻塞数据流的处理,AsyncFunction 主要依赖 CompletableFuture 或其他类型的 Future 来实现异步操作;

提高异步操作性能:调整并行度、优化线程池、批量请求、使用低延迟的IO技术、限流和超时

16、在 Flink 中,如何通过 Savepoint 进行任务的热重启?

a、创建 savepoint:在现有任务正在运行的状态下,手动触发 savepoint 创建,用于保存任务的状态;

b、停止原任务:停止当前正在运行的任务,同时确保 savepoint 创建成功;

c、热重启新任务:通过指定创建的 savepoint,重新启动或启动新的任务;

# 手动触发 savepoint:
bin/flink savepoint <job-id> <savepoint-dir>

# 停止原任务:
bin/flink cancel <job-id>

# 热重启新任务:
bin/flink run -s <savepoint-path> -c <main-class> <jar-path>

-s <savepoint-path> 指定了上一步骤中创建的 Savepoint 路径,-c <main-class> 是包含任务的主类入口,<job-path> 是打包好的任务 jar 文件;

17、Flink 中的分布式快照机制是如何工作的?如何优化快照的性能?

分布式快照机制主要通过 chandy-lamport 算法(通过引入 barrier 的概念,确保在不停止整个系统操作的前提下,拿到一个一致的全局状态)实现一致性状态快照,当执行快照时,flink 触发并保存但前的操作符状态和流的偏移量,具体工作分为:

a、触发快照:作业管理器定期向所有并行任务发送 checkpointing barrier,用于触发快照操作

b、传播 barrier:barrier 在数据流网络中传播,沿途标记数据流,它们确保所有任务的一致性状态;

c、保存状态:当 barrier 到达操作符时,操作符会暂停处理新的数据,将当前状态保存到检查点

d、全局一致性:所有的 barrier 被处理后,作业管理器会整合各个操作符的状态,形成全局一致的快照;

为了优化快照性能,通常采用:

a、调整快照间隔:根据作业的特性和压力情况调整快照间隔,避免用于频繁的快照导致性能下降;

b、增量快照:利用 rocksdb 等状态后端,启用增量快照,仅保存变化的部分,减少 IO 和存储开销;

c、异步快照:使用异步 state backend,可以在后台异步完成快照存储,不阻塞数据处理路径

18、在 Flink 中,如何优化数据的序列化和反序列化过程?

a、选择合适的序列化框架:java、kryo、avro

b、自定义序列化器:实现 flink 的 typeserializer 接口,自定义序列化器,可以针对具体的数据结构进行深度优化,例如对于一个业务对象可以直接读取和写入字节流而不依赖反射,提高性能

c、减少序列化操作

d、使用 pojo 类型数据结构

19、Flink 中的 Kafka Connector 是如何实现的?如何优化 Kafka 的消费性能?

用于从 kafka 读取数据或将数据写入 kafka,这个连接器实现了 flink 的 source 和 sink 接口,确保数据在分布式流处理系统中的准确传递和共享状态的一致性;

优化 kafka 的消费性能的主要方法包括:

a、增大并行度:通过提高 flink job 的并行度,更高效地消费 kafka 中的分区数据;

b、调整消费配置:适当调整 kafka 消费者的配置参数:例如 fetch.min.bytes(设置消费者可从 kafka 中拉去的数据最小字节数,当数据量达不到这个值时,kafka 会等待更多的数据,直到达到这个值或超时)、max.poll.records(控制每次拉取时的最大记录数,调高这个值可以减少拉取次数,提高吞吐量) 和 session.timeout.ms(表示消费者和 kafka 之间心跳检测的超时时间,适当延长可以减少消费者被错误认为死掉的可能性) 等,以最大化吞吐量和数据处理效率;

c、批量处理:尽可能在消费端和处理端应用批量处理技术,以减少通信和处理开销

d、使用本地存储:在消费数据量较大时,可考虑使用本地磁盘或告诉缓存在暂存数据

20、Flink 的 KeyedState 和 OperatorState 是如何配合使用的?它们在状态管理中的作用是什么?

keyedstate:基于键的聚合操作,如求和、求平均值、计数等;用于窗口操作,例如滚动窗口、滑动窗口的计算;实现复杂的事件模式匹配,需要为每个键保存状态;

operatorstate:在进行一些全局信息的保存时,如全局计数器、全部数据流历史等;用于维护算子的元数据,如偏移量、时间戳;在任务恢复时,需要重新初始化一些全局状态信息;