2. Flink 实时计算基础

在构建 PSI 漂移告警系统之前,必须掌握 Flink 的核心运行原理与编程模型。本章将围绕 Flink 的架构设计、DataStream API 的使用方式以及时间语义与 Watermark 机制展开,为后续实现实时特征分布对比打下坚实基础。

2.1 Flink 架构概览

Flink 采用经典的 主从(Master-Worker) 架构,各组件职责清晰,协同完成作业的调度、执行与容错。下图展示了 Flink 运行时的主要角色及其交互关系。

Flink 运行时架构 JobManager 调度器 Checkpoint 恢复协调 资源管理 TaskManager TaskManager Slot Slot Slot Slot JobManager 负责作业调度与 Checkpoint 协调,TaskManager 提供 Slot 执行并行子任务
关键角色说明:
  • JobManager:集群的大脑,负责接收作业、生成执行计划、协调 Checkpoint 以及故障恢复。
  • TaskManager:工作节点,以 Slot 为最小资源单位执行具体的算子任务。
  • Slot:TaskManager 内部的资源隔离单元,每个 Slot 可运行一个子任务(Subtask)。

2.2 DataStream API

DataStream API 是 Flink 实时计算的核心编程接口,它提供了一套声明式的算子来构建数据流处理管道。在 PSI 漂移告警场景中,我们主要使用以下算子来读取、转换和聚合特征数据。

算子类别 常用算子 PSI 场景用途
Source KafkaSource, FileSource 读取实时特征日志或离线参考分布
Transformation map, flatMap, filter, keyBy 特征提取、分箱映射、按特征维度分组
Window SlidingEventTimeWindows, TumblingEventTimeWindows 按时间窗口统计特征频次,计算分布
Sink KafkaSink, JDBCSink 输出 PSI 值到告警通道或存储
编程模型要点: DataStream 程序通常遵循 Source → Transformation → Sink 的三段式结构。所有算子都是惰性求值的,必须调用 env.execute() 才会触发实际计算。

以下是一个简化的特征读取与分组代码示例,展示了如何从 Kafka 消费特征数据并按特征名称分组:

// 创建执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

// 从 Kafka 读取特征日志
DataStream<String> sourceStream = env.fromSource(
    KafkaSource.<String>builder()
        .setBootstrapServers("localhost:9092")
        .setTopics("feature-log")
        .setGroupId("psi-group")
        .setStartingOffsets(OffsetsInitializer.latest())
        .build(),
    WatermarkStrategy.noWatermarks(),
    "Kafka Source"
);

// 解析为特征事件并分组
DataStream<FeatureEvent> featureStream = sourceStream
    .map(line -> parseFeatureEvent(line))
    .keyBy(FeatureEvent::getFeatureName);

2.3 时间语义与 Watermark 机制

在实时 PSI 计算中,我们关心的是特征在 真实业务时间 上的分布变化,而非数据到达 Flink 的系统时间。因此,必须使用 事件时间(Event Time) 语义,并配合 Watermark 机制来处理乱序数据。

2.3.1 三种时间语义对比

时间语义 定义 PSI 场景适用性
事件时间(Event Time) 事件实际发生的时间戳,嵌入在数据中 ✅ 必须使用,保证分布计算与业务时间对齐
摄入时间(Ingestion Time) 数据进入 Flink 源算子的时间 ❌ 不推荐,无法反映真实业务时序
处理时间(Processing Time) 算子所在机器的本地时钟时间 ❌ 不推荐,结果受机器负载和网络延迟影响

2.3.2 Watermark 机制

Watermark 是 Flink 中衡量事件时间进度的核心机制,它本质上是一个 带时间戳的全局标记,表示“在此时间戳之前的数据已经全部到达”。当 Watermark 超过窗口结束时间时,该窗口被触发计算。

Watermark 与乱序数据处理示意 时间 10:00 10:01 10:02 10:03 10:04 A B C D E F 乱序 Watermark = 10:02 Watermark 推进到 10:02 时,[10:00, 10:02) 窗口触发计算,乱序事件 F(时间戳 10:01)被丢弃或侧输出
PSI 实践注意: 在计算特征分布时,如果 Watermark 设置过小(允许乱序时间短),会导致部分晚到数据被丢弃,造成分布统计偏差;如果设置过大,则会增加窗口延迟,影响告警时效性。建议根据特征日志的最大延迟时间,设置合理的 maxOutOfOrderness 参数。

2.3.3 在代码中配置事件时间与 Watermark

// 配置 Watermark 策略:允许最大 30 秒乱序
WatermarkStrategy<FeatureEvent> watermarkStrategy = WatermarkStrategy
    .<FeatureEvent>forBoundedOutOfOrderness(Duration.ofSeconds(30))
    .withTimestampAssigner((event, timestamp) -> event.getEventTime());

// 应用 Watermark 策略到数据流
DataStream<FeatureEvent> streamWithWatermark = featureStream
    .assignTimestampsAndWatermarks(watermarkStrategy);

// 定义 5 分钟的滑动窗口,每 1 分钟滑动一次
DataStream<FeatureDistribution> distributionStream = streamWithWatermark
    .windowAll(SlidingEventTimeWindows.of(Time.minutes(5), Time.minutes(1)))
    .aggregate(new DistributionAggregator());
核心要点总结:
  • Flink 的 主从架构 提供了高可用与弹性扩缩容能力,适合长时间运行的 PSI 监控作业。
  • DataStream API 的 keyBy + window 组合是计算特征分布的基础模式。
  • 事件时间 + Watermark 是保证 PSI 计算结果准确性的前提,必须根据业务数据延迟特征精细调参。