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 本章知识体系总览

下面这张图,把咱们这章讲的核心概念串起来了。你可以把它当作一个“地图”,以后写代码时随时回来对照。

Flink核心概念知识体系 Flink 核心概念知识体系 DataStream 算子 (Operator) Map / FlatMap Filter KeyBy Window ProcessFunction 状态 (State) ValueState ListState MapState AggregatingState 核心:DataStream + 算子 + 状态 = 实时计算能力 所有实时指标开发,都围绕这三者展开

嗯,这张图把咱们这章的核心都串起来了。DataStream 是骨架,算子是动作,状态是记忆。三者缺一不可。

环境搭好了,概念也理清了。接下来,你就可以开始动手写自己的第一个实时指标了。别怕出错,我当年第一个 Flink 作业跑了三遍才出正确结果。多练几次,自然就熟了。