2. 实时计算基础:Flink核心概念与开发环境搭建
好,咱们进入第二章。这一章是地基,得打牢了。
很多同学刚开始学 Flink 时,容易被各种概念绕晕——DataStream 是什么?算子怎么连?状态又是干嘛的?我当年刚接触时也差不多,看了半天文档,感觉每个字都认识,但串起来就懵了。
别急,咱们一个一个拆开讲。我保证,用最直白的方式让你搞明白。
2.1 Flink 核心概念:DataStream
Flink 里最核心的抽象,就是 DataStream。说白了,它就是一条“数据流”。
你想想看,实时数据就像水管里的水,源源不断地流过来。DataStream 就是这根水管,里面流淌着一条条数据记录。每条记录可以是一个字符串、一个 JSON 对象,或者你自定义的 Java 类。
核心要点:在 Flink 中,一切皆流。你写的所有逻辑,都是围绕如何转换、过滤、聚合这条流来展开的。
我习惯把 DataStream 分成两种:
- 有界流(Bounded Stream):数据有头有尾,比如读一个文件。处理完就结束。
- 无界流(Unbounded Stream):数据源源不断,比如 Kafka 里的消息。这是实时计算的主战场。
Flink 对这两种流都支持,但咱们做实时指标,99% 的时间都在跟无界流打交道。
2.2 算子:流上的“加工厂”
光有数据流还不够,你得对它做点什么。这时候就轮到 算子(Operator) 登场了。
算子就是作用在 DataStream 上的函数,负责把一条流变成另一条流。你可以把它想象成流水线上的一个个工位:
- Map:一对一转换。进来一条,出去一条。比如把用户ID转换成用户名。
- Filter:过滤。符合条件的留下,不符合的扔掉。
- KeyBy:分组。按某个字段把数据分到不同的“桶”里。这是做聚合的前提。
- Window:窗口。把无限流切成有限段,比如每5分钟统计一次。
- ProcessFunction:最灵活的算子。你可以在这里拿到数据、状态、定时器,做各种复杂逻辑。
举个例子,咱们要统计每个商品的实时点击量:
DataStream<ClickEvent> clicks = ...; // 从Kafka来的点击流
clicks
.keyBy(event -> event.getProductId()) // 按商品ID分组
.window(TumblingProcessingTimeWindows.of(Time.minutes(1))) // 1分钟滚动窗口
.aggregate(new CountAggregate()) // 聚合计数
.print(); // 打印结果
这段代码,就是实时指标开发的“标准套路”。我在项目中写过无数次类似的逻辑。
我的经验:刚开始写 Flink 作业时,别一上来就用 ProcessFunction。能用简单的 Map/Filter 解决,就别搞复杂。等业务逻辑确实需要状态和定时器时,再上 ProcessFunction。这样代码可读性高,也容易调试。
2.3 状态:Flink 的“记忆”
实时计算里有个关键问题:Flink 怎么记住之前的数据?
比如你要算“过去5分钟每个用户的平均消费金额”。如果 Flink 没有记忆,那每来一条数据都得从头算,这显然不现实。
这个“记忆”,就是 状态(State)。
Flink 的状态分为几种:
| 状态类型 | 说明 | 典型场景 |
|---|---|---|
| ValueState | 存一个值 | 记录上一次的访问时间 |
| ListState | 存一个列表 | 缓存最近10条日志 |
| MapState | 存一个键值对集合 | 维护用户ID到用户名的映射 |
| ReducingState / AggregatingState | 存聚合结果 | 实时累加销售额 |
状态是 Flink 的“心脏”。没有状态,Flink 就是个简单的流式 ETL 工具。有了状态,它才能做真正的实时计算。
避坑指南:我曾经在一个项目里,因为状态没有配置 TTL(存活时间),导致状态无限膨胀,最后作业 OOM 挂掉了。记住:所有状态都必须设置合理的 TTL。别偷懒,这是生产环境的铁律。
2.4 Flink 开发环境搭建
光说不练假把式。咱们把环境搭起来,跑个 Hello World。
2.4.1 前置条件
- JDK 8 或 11(我推荐 JDK 11,性能更好)
- Maven 3.6+(或者 Gradle,看个人习惯)
- IDE(IntelliJ IDEA 首选,社区版就够用)
2.4.2 创建项目
我习惯用 Maven 的 archetype 来创建 Flink 项目。在命令行里执行:
mvn archetype:generate \
-DarchetypeGroupId=org.apache.flink \
-DarchetypeArtifactId=flink-quickstart-java \
-DarchetypeVersion=1.17.1 \
-DgroupId=com.example \
-DartifactId=realtime-metrics \
-Dversion=1.0-SNAPSHOT \
-Dpackage=com.example.realtimemetrics \
-DinteractiveMode=false
等几秒钟,项目就生成了。导入 IDEA,你会看到这样的结构:
realtime-metrics/
├── pom.xml
└── src/
└── main/
├── java/
│ └── com/example/realtimemetrics/
│ ├── BatchJob.java
│ └── StreamingJob.java
└── resources/
└── log4j2.properties
2.4.3 第一个实时作业
打开 StreamingJob.java,咱们改一改:
public class StreamingJob {
public static void main(String[] args) throws Exception {
// 1. 创建执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 从 socket 读取数据(方便测试)
DataStream<String> text = env.socketTextStream("localhost", 9999);
// 3. 转换:每来一条数据,就打印出来
text.map(line -> "收到消息: " + line)
.print();
// 4. 启动作业
env.execute("My First Flink Job");
}
}
然后在终端启动一个 socket 服务:
nc -lk 9999
运行 StreamingJob.main(),在终端输入点什么,IDE 的控制台就会打印出来。
小技巧:本地调试时,我经常用 socketTextStream 或 fromElements 来模拟数据源。等逻辑调通了,再换成 Kafka 或 Pulsar 这样的真实数据源。这样能省不少时间。
2.5 本章知识体系总览
下面这张图,把咱们这章讲的核心概念串起来了。你可以把它当作一个“地图”,以后写代码时随时回来对照。
嗯,这张图把咱们这章的核心都串起来了。DataStream 是骨架,算子是动作,状态是记忆。三者缺一不可。
环境搭好了,概念也理清了。接下来,你就可以开始动手写自己的第一个实时指标了。别怕出错,我当年第一个 Flink 作业跑了三遍才出正确结果。多练几次,自然就熟了。