2. Flink 基础回顾:架构、DataStream API、时间语义与水位线
在搭建特征服务之前,必须牢固掌握 Flink 的核心基础。本章将系统回顾 Flink 的运行时架构、DataStream API 编程模型、时间语义以及水位线(Watermark)机制,为后续的因子计算与特征管道设计打下坚实的技术地基。
2.1 Flink 运行时架构
Flink 采用经典的 主从(Master-Worker) 架构,核心组件包括 JobManager 和 TaskManager。理解各角色的职责与协作方式,是进行集群部署与作业调优的前提。
| 组件 | 角色 | 核心职责 |
|---|---|---|
| JobManager | Master | 接收作业、生成执行计划、协调 Checkpoint、故障恢复 |
| TaskManager | Worker | 执行具体的 Task Slot,管理本地内存与网络缓冲 |
| Dispatcher | REST 入口 | 提供 Web UI 与 REST API,提交与管理作业 |
| ResourceManager | 资源调度 | 管理 TaskManager 的申请与释放(YARN/K8s) |
关键设计:Task Slot 是 TaskManager 中的最小资源单元,每个 Slot 可以运行一个 SubTask。Slot 数量决定了作业的并行度上限。
2.2 DataStream API 编程模型
DataStream API 是 Flink 流处理的核心抽象。一个典型的 Flink 流作业包含三个步骤:Source(数据源)、Transformation(转换算子)、Sink(数据输出)。
2.2.1 核心算子速览
| 算子类别 | 算子名称 | 用途说明 |
|---|---|---|
| Source | fromSource / addSource | 从 Kafka、文件、Socket 等读取数据流 |
| 单流转换 | map / flatMap / filter | 数据映射、扁平化、过滤 |
| 分组聚合 | keyBy / window / reduce | 按 Key 分组、开窗、增量聚合 |
| 多流合并 | union / connect / coMap | 合并多条流或连接不同类型流 |
| Sink | toSink / addSink | 输出到 Kafka、JDBC、HDFS 等 |
实战提示:在特征服务中,最常用的算子组合是
keyBy → window → aggregate,用于按股票代码或用户 ID 分组后计算滑动窗口内的统计特征。
2.2.2 典型作业骨架
// 1. 创建执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 定义数据源(Kafka)
DataStream<String> sourceStream = env.fromSource(
KafkaSource.<String>builder()
.setBootstrapServers("localhost:9092")
.setTopics("stock-tick")
.setGroupId("flink-feature")
.build(),
WatermarkStrategy.noWatermarks(),
"Kafka Source"
);
// 3. 转换逻辑:解析 JSON 并计算因子
DataStream<StockFeature> featureStream = sourceStream
.map(json -> parseStockTick(json))
.keyBy(StockTick::getSymbol)
.window(SlidingEventTimeWindows.of(Time.minutes(5), Time.minutes(1)))
.aggregate(new PriceFeatureAggregator());
// 4. 输出到特征存储
featureStream.sinkTo(
JdbcSink.sink(
"INSERT INTO stock_features VALUES (?, ?, ?, ?)",
new FeatureJdbcWriter()
)
);
// 5. 提交执行
env.execute("Stock Feature Pipeline");
2.3 时间语义
Flink 支持三种时间语义,在因子计算中必须根据业务场景正确选择:
| 时间语义 | 定义 | 特征服务典型场景 |
|---|---|---|
| Event Time | 事件实际发生的时间(嵌入在数据中) | 交易行情因子、用户行为序列特征 |
| Ingestion Time | 数据进入 Flink 算子的时间 | 对时间精度要求不高的监控指标 |
| Processing Time | 算子本地系统时间 | 实时报警、当前状态快照 |
避坑指南:在特征服务中,必须优先使用 Event Time。Processing Time 无法处理乱序数据,会导致因子计算结果在回测与实盘之间不一致。使用 Event Time 时,必须配合水位线机制处理延迟数据。
2.4 水位线(Watermark)机制
水位线是 Flink 中衡量 Event Time 进度的核心机制,用于触发窗口计算并处理乱序数据。其本质是一个带时间戳的流式信号,表示“当前时间之前的数据已经全部到达”。
2.4.1 水位线的工作原理
- 单调递增:水位线时间戳必须单调递增,不能回退。
- 触发窗口:当水位线超过窗口的结束时间时,触发该窗口的计算。
- 允许延迟:通过
allowedLateness参数,可以控制窗口在触发后继续等待迟到数据的时间。
2.4.2 水位线生成策略
Flink 提供了两种内置的水位线生成策略,开发者也可以自定义:
| 策略 | 类名 | 适用场景 |
|---|---|---|
| 周期性 | WatermarkStrategy.forBoundedOutOfOrderness |
已知最大乱序时间(如 5 秒),周期性生成水位线 |
| 标点式 | WatermarkStrategy.forGenerator |
数据流中自带标记事件,用于精确控制水位线 |
特征服务最佳实践:对于股票行情数据,推荐使用
forBoundedOutOfOrderness(Duration.ofSeconds(5)),并配合 allowedLateness(Duration.ofSeconds(10)) 处理网络抖动导致的少量迟到数据。对于迟到超过 15 秒的数据,建议通过侧输出流(Side Output)单独处理,避免影响主窗口的因子准确性。
2.4.3 代码示例:设置水位线
// 定义水位线策略:允许最大乱序 5 秒
WatermarkStrategy<StockTick> watermarkStrategy =
WatermarkStrategy.<StockTick>forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner((event, timestamp) -> event.getEventTime());
// 应用到数据流
DataStream<StockTick> streamWithWatermark = sourceStream
.assignTimestampsAndWatermarks(watermarkStrategy);
通过以上回顾,我们为后续章节的因子计算管道搭建了坚实的 Flink 基础。下一章将正式进入特征服务的需求分析与架构设计。