3、流处理引擎搭建:Apache Flink核心概念、Flink环境部署、DataStream API实战
好,咱们进入第三章。这一章我打算聊聊流处理引擎的搭建。你想想看,一个金融监控告警系统,核心就是「快」——数据来了就得马上处理,延迟一高,行情变了,风控就来不及了。Apache Flink 在这方面,我个人认为是目前最成熟的方案之一。
3.1 Flink 核心概念:先搞懂这几个东西
刚开始接触 Flink 的时候,我其实也被一堆术语搞晕过。什么 JobManager、TaskManager、Slot…… 说白了,Flink 就是一个「数据管道工厂」。你定义好怎么处理数据,它帮你把任务拆开、分发、容错。
3.1.1 有界流 vs 无界流
这是 Flink 里最基础的一个区分。
- 有界流:数据有头有尾,比如跑批任务,处理完就结束。
- 无界流:数据源源不断,比如实时行情、交易流水。金融监控里,99% 的场景都是无界流。
我在项目中遇到过一个问题:有人把无界流当有界流处理,结果窗口一直不触发,数据积压到 OOM。嗯,这里要注意——Flink 默认认为你处理的是无界流,所以必须显式定义窗口或时间语义。
3.1.2 时间语义
Flink 支持三种时间:
| 时间类型 | 说明 | 金融场景举例 |
|---|---|---|
| Event Time | 事件发生的时间 | 交易实际发生的时间戳 |
| Ingestion Time | 进入 Flink 的时间 | 数据到达 Kafka 的时间 |
| Processing Time | 算子处理的时间 | 当前机器时间 |
我个人习惯用 Event Time。为什么?因为金融数据经常有网络延迟、乱序。你用 Processing Time 算出来的指标,可能跟实际差好几秒。我曾经因为用了 Processing Time,导致一个风控告警延迟了 3 秒,差点出事故。
3.1.3 窗口机制
窗口就是把无界流切成一段一段的「小数据块」。常用的有:
- 滚动窗口:固定大小,不重叠。比如每 5 秒统计一次成交额。
- 滑动窗口:固定大小,可重叠。比如每 2 秒统计过去 5 秒的波动率。
- 会话窗口:根据不活动间隔切分。比如用户连续操作,超过 30 秒没动作就切一个窗口。
避坑指南:我曾经在滑动窗口上踩过坑——窗口大小和滑动步长设置不合理,导致重复计算量暴增。建议窗口大小是滑动步长的整数倍,否则性能会很难看。
3.2 Flink 环境部署:本地跑起来再说
理论讲完了,咱们动手。部署 Flink 其实不复杂,我建议你先在本地跑一个 Standalone 模式,感受一下。
3.2.1 本地部署步骤
- 下载 Flink 安装包(我一般用 1.17 版本,稳定)
- 解压后,进入
bin/目录,执行./start-cluster.sh - 浏览器打开
http://localhost:8081,看到 Web UI 就成功了
嗯,这里要注意:如果你在 Mac 上跑,可能会遇到端口冲突。我习惯把 conf/flink-conf.yaml 里的 rest.port 改成 8082,避免跟其他服务打架。
3.2.2 生产环境部署建议
生产环境我推荐用 YARN 或 Kubernetes 模式。原因很简单——资源弹性。金融业务流量波动大,白天交易高峰,晚上跑批,手动扩缩容太累了。
我曾经在 YARN 上部署过一套 Flink 集群,配置了动态资源分配。核心参数就两个:
# flink-conf.yaml
jobmanager.memory.process.size: 2048m
taskmanager.memory.process.size: 4096m
taskmanager.numberOfTaskSlots: 4
parallelism.default: 2
你想想看,如果每个 TaskManager 只配 2 个 Slot,并行度一高,资源就浪费了。我一般配 4 个 Slot,并行度根据数据量动态调整。
flink run 跑一个官方的 WordCount 示例,验证环境是否正常。别问我为什么强调这个——我见过有人部署完直接上生产,结果发现网络不通,排查了一整天。
3.3 DataStream API 实战:写一个实时监控 Demo
好,终于到写代码环节了。DataStream API 是 Flink 最核心的编程接口。说白了,就是定义「数据从哪来、怎么处理、往哪去」。
3.3.1 构建执行环境
第一步,创建 StreamExecutionEnvironment。这是所有 Flink 程序的入口。
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
env.setParallelism(2);
我个人习惯显式设置并行度,不依赖默认值。为什么?因为默认并行度等于 CPU 核数,在开发环境可能没问题,但生产环境机器配置不一样,容易出幺蛾子。
3.3.2 数据源:从 Kafka 读取交易流水
金融监控的数据源,99% 是 Kafka。Flink 提供了官方的 Kafka Connector,用起来很简单。
Properties props = new Properties();
props.setProperty("bootstrap.servers", "localhost:9092");
props.setProperty("group.id", "flink-monitor");
DataStream<String> stream = env.addSource(
new FlinkKafkaConsumer<>("trade-topic", new SimpleStringSchema(), props)
);
嗯,这里要注意:SimpleStringSchema 只适合测试。生产环境我建议用 JSONKeyValueDeserializationSchema 或者自定义反序列化器,因为金融数据通常包含嵌套结构。
3.3.3 核心处理逻辑:窗口聚合 + 告警判断
假设我们要监控「每 5 秒内,单只股票的成交额是否超过 1000 万」。代码大概长这样:
stream
.map(line -> parseTrade(line)) // 解析 JSON
.assignTimestampsAndWatermarks(
WatermarkStrategy.<Trade>forBoundedOutOfOrderness(Duration.ofSeconds(2))
.withTimestampAssigner((event, timestamp) -> event.getEventTime())
)
.keyBy(Trade::getStockCode)
.window(TumblingEventTimeWindows.of(Time.seconds(5)))
.aggregate(new TradeAggregator())
.filter(sum -> sum.getAmount() > 10_000_000)
.addSink(new AlertSink()); // 发送告警
这段代码里,我重点说两个地方:
- Watermark:我设了 2 秒的乱序容忍。为什么是 2 秒?因为根据历史数据,99% 的交易延迟都在 2 秒以内。太大会增加延迟,太小会丢数据。
- 窗口聚合:用
aggregate而不是reduce,因为aggregate可以自定义累加器,性能更好。
3.3.4 结果输出:告警写入 Kafka 或数据库
告警信息一般要持久化。我习惯写回 Kafka,方便下游消费。
DataStream<Alert> alertStream = ...; // 上面处理后的告警流
alertStream.addSink(new FlinkKafkaProducer<>(
"alert-topic",
new AlertSerializationSchema(),
props
));
你想想看,如果直接写数据库,万一数据库挂了,告警就丢了。写 Kafka 的话,下游可以重试,容错性更好。
3.4 本章知识体系总览
为了让你更直观地理解 Flink 在金融监控中的位置,我画了一张流程图。这张图涵盖了从数据接入到告警输出的完整链路。
这张图里,Flink 处于中间位置,负责「承上启下」。上面接 Kafka 数据,下面出告警结果。核心就是窗口 + 时间语义的配合。
好,这一章就到这里。Flink 的内容其实很深,但作为金融监控系统的流处理引擎,掌握这些核心概念和实战技巧,已经足够你搭建一个可用的原型了。剩下的,就是在实际项目中慢慢打磨。