Flink 系列:Flink 入门不再难!3000字深入浅出 WordCount 实战及精解

107848bd74d13b5f9ede8c89768bdcd2.gif

点击卡片“大数据实战演练”,选择“设为星标”或“置顶”

回复“资料”可领取独家整理的大数据学习资料!

回复“Ambari知识库”可领取独家整理的Ambari学习资料!

a25acec214fcc93f05da3f3e455dbf1e.jpeg

大家好,我是create17,见字如面。

在这个数据驱动的时代,掌握大数据技术成为了每一位开发者必不可少的技能。而在众多技术栈中,Flink无疑占据了重要的位置。作为一个高性能、可扩展的实时数据处理框架,Flink已经成为了很多企业和开发者的首选。但对于初学者来说,Flink的学习曲线可能会显得有些陡峭。因此,我们决定打造一系列通俗易懂的Flink学习文章,希望能帮助大家更快地掌握这一强大的技术。

那希望我接下来的分享给大家带来一些帮助和启发🤔

版本说明:
Java:1.8
Flink:1.12.0

一、前言

Apache Flink 是一个流处理框架,它允许用户以高吞吐量和低延迟的方式处理实时数据流。Flink 提供了强大的流处理能力,能够处理有界(批处理)和无界(流处理)的数据流。通过 Flink,开发者可以轻松实现复杂的数据处理和分析应用。

今天和大家一起学习 Flink 入门级 demo:WordCount。WordCount 简单来讲就是单词计数,是一般大数据计算框架(Hadoop、Spark、Flink)的入门学习案例,相当于编程语言(Java、Python)中的 HelloWorld 案例,适合刚开始了解 Flink 作业提交流程的同学。

WordCount 程序编写好以后,我们可以本地运行测试,也可以打成 jar 包,使用命令提交 Job 运行。本篇文章,这两种方式我们都试一下。好了,准备好了吗?我们开始吧。

二、编写 WordCount 程序

1、创建 maven 工程

我们使用 Java 语言来编写 WordCount 程序。首先创建 maven 工程,可运行下述代码创建工程:

mvn archetype:generate \
    -DarchetypeGroupId=org.apache.flink \
    -DarchetypeArtifactId=flink-quickstart-java \
    -DarchetypeVersion=1.12.0 \
    -DgroupId=org.myorg.quickstart \
    -DartifactId=quickstart \
    -Dversion=0.1 \
    -Dpackage=org.myorg.quickstart \
    -DinteractiveMode=false
  • mvn archetype:generate:这是 Maven 命令的一部分,用于触发 Maven 架构生成的目标。这个命令告诉 Maven 你想要生成一个新的项目,基于指定的架构模板。

  • -DarchetypeGroupId=org.apache.flink:这个参数指定了架构的 group ID。对于 Apache Flink 的快速开始模板,其 group ID 是 org.apache.flink。Group ID 是 Maven 项目的一部分,用于唯一标识项目所属的组织或项目组。

  • -DarchetypeArtifactId=flink-quickstart-java:这个参数指定了架构的 artifact ID,即模板的具体名称。对于 Flink 的快速开始 java 项目,artifact ID 是 flink-quickstart-java。Artifact ID 用于唯一标识一个项目或模块。

  • -DarchetypeVersion=1.12.0:指定了架构的版本号。对于你提供的命令,使用的 Flink 架构版本是 1.12.0。需要注意的是,可能存在多个版本的架构,每个版本可能会有不同的特性或结构。

  • -DgroupId=org.myorg.quickstart:这是你的项目的 group ID。在 Maven 中,group ID 用于唯一标识你的项目所属的组织或项目组。这里,它被设置为 org.myorg.quickstart。

  • -DartifactId=quickstart:这是你的项目的 artifact ID,它是你项目的唯一标识。在这个例子中,它被设置为 quickstart。

  • -Dversion=0.1:这个参数指定了你的项目版本。在这里,项目版本被设置为 0.1。

  • -Dpackage=org.myorg.quickstart:这个参数用于指定你的项目包的基本名称。在 Java 中,包名用于组织和管理类。这里,包名被设置为 org.myorg.quickstart。

  • -DinteractiveMode=false:这个参数用于告诉 Maven 不要进入交互模式。当设置为 false 时,Maven 会使用命令行提供的参数来生成项目,而不会在过程中询问用户输入。

你可以编辑上面的 groupId, artifactId, package 成你喜欢的路径。使用上面的参数,Maven 将自动为你创建如下所示的项目结构:

$ tree quickstart
quickstart
├── pom.xml
└── src
    └── main
        ├── java
        │   └── org.myorg.quickstart
        │       ├── BatchJob.java
        │       └── StreamingJob.java
        └── resources
            └── log4j.properties

我们的 pom.xml 文件已经包含了所需的 Flink 依赖,并且在 src/main/java 下有几个示例程序框架。接下来我们将开始编写第一个 Flink 程序。

pom.xml 文件的 flink 相关依赖有:

<dependency>
   <groupId>org.apache.flink</groupId>
   <artifactId>flink-java</artifactId>
   <version>${flink.version}</version>
   <scope>provided</scope>
</dependency>
<dependency>
   <groupId>org.apache.flink</groupId>
   <artifactId>flink-streaming-java_${scala.binary.version}</artifactId>
   <version>${flink.version}</version>
   <scope>provided</scope>
</dependency>
<dependency>
   <groupId>org.apache.flink</groupId>
   <artifactId>flink-clients_${scala.binary.version}</artifactId>
   <version>${flink.version}</version>
   <scope>provided</scope>
</dependency>
2、编写 Socket WordCount 程序

在本次示例中,我们使用 socket 来模拟实时数据流,然后统计指定周期内每个单词出现的频次。

import org.apache.flink.api.common.functions.FlatMapFunction;

import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;


/**
 * @author create17
 * @date 2024/03/17
 */
public class SocketWindowWordCount {

    public static void main(String[] args) throws Exception {
        // 创建Flink任务运行的环境,实时数据流
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        // DataStream 是 Flink 中做流处理的核心 API
        // 使用换行符来分割从 socket 流中接收到的文本数据,每当它读取到一个换行符,就会将前面的文本作为一个单独的记录(字符串)
        DataStream<String> text = env.socketTextStream("x.x.x.x", 9002, "\n");
        DataStream<Tuple2<String, Integer>> wordCounts = text.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception {
                // 根据空格截取字符串
                for (String word : s.split("\\s")) {
                    collector.collect(new Tuple2<>(word, 1));
                }
            }
        }).keyBy(new KeySelector<Tuple2<String, Integer>, Object>() {
            @Override
            public Object getKey(Tuple2<String, Integer> value) throws Exception {
                return value.f0;
            }
        }).window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
                // .sum(1)是一个转换操作,用于在一个keyed stream上进行聚合操作。这里的1是参数,表示在Tuple2<String, Integer>中要进行求和操作的字段索引,
                // 由于Tuple是从0开始索引的,0表示第一个字段(这里是单词),1表示第二个字段(这里是整数计数)。
                .sum(1);

        // 将结果打印到控制台,如果需要有序,需要设置 parallelism 为 1
        wordCounts.print().setParallelism(1);
        // 重要
        env.execute("Socket Window WordCount");

    }

}

我们现在来逐步分析上述代码:

2.1 设置环境

创建 StreamExecutionEnvironment:

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

这行代码初始化了 Flink 的流执行环境,它是所有 Flink 程序的起点,用于设置执行参数和创建数据源。

2.2 数据源定义

接收 Socket 文本流:

DataStream<String> text = env.socketTextStream("x.x.x.x", 9002, "\n");

这行代码定义了数据源,从指定 IP 地址 (x.x.x.x) 和端口 (9002) 接收文本流,以换行符 (\n) 作为记录的分隔符。这里的 IP 地址应替换为实际的源地址。

2.3 数据转换
  • 使用 flatMap 操作进行单词切分:

text.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
    @Override
    public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception {for (String word : s.split("\\s")) {
            collector.collect(new Tuple2<>(word, 1));
        }
    }
})

这段代码将文本行切分成单词,并为每个单词生成一个 (单词, 1) 的元组。

  • 按单词进行分组:

.keyBy(new KeySelector<Tuple2<String, Integer>, Object>() {
    @Override
    public Object getKey(Tuple2<String, Integer> value) throws Exception {
        return value.f0;
    }
})

keyBy 根据元组的第一个字段(f0,即单词)进行分组。

2.4 定义窗口

应用滚动窗口:

.window(TumblingProcessingTimeWindows.of(Time.seconds(5)))

这行代码定义了一个基于处理时间的滚动窗口,窗口大小为 5 秒。每个窗口独立计算过去 5 秒内的数据。

2.5 聚合操作

单词计数累加:

.sum(1)

在每个窗口内,对分组后的单词计数 (1 表示元组的第二个字段) 进行求和。

2.6 输出结果

打印结果并设置并行度:

wordCounts.print().setParallelism(1);

这行代码将计算结果输出到控制台,并将并行度设置为 1,以确保输出的顺序性。

2.7 启动 Flink 程序

执行 Flink 任务:

env.execute("Socket Window WordCount");

最后一行代码启动 Flink 流处理作业。execute 方法触发程序执行,"Socket Window WordCount" 是作业的名称。

3、本地启动 WordCount 程序

3.1、首先使用 nc -l 开启监听 9002 端口:

252ae5e807779a755808a19007f3b2f7.png

nc 命令说明:

9f230fbd3cb075ebfdf1a87a2000a105.png

3.2、然后执行 SocketWindowWordCount 类的 main() 方法,本地启动。

运行报错,提示:
java.lang.NoClassDefFoundError: org/apache/flink/api/common/functions/FlatMapFunction

60f4fac730955f32be5c7de620b869a2.png

解决办法:

a2fc01cd5c400fa60dfa388256969815.png

推荐解决方法2,idea 启动类配置 include depencies with "Provided" scope,配置如下图所示:

77dc5c34d8fdb731135201859535f265.png

启动后,需要在 nc 监听的那里,输入文本:

b3cac7acd6a79a6343e9720051a40196.png

每当程序读取到一个换行符(\n),就会将前面的文本作为一个单独的记录(字符串),然后将这单独记录根据空格切分统计单词数量。

输出如下图所示:

8d21b889231165241391ce99df3e245f.png

4、Flink 运行 jar 包启动

4.1 部署源码

在这里我将 flink-1.12.0 源码包放到了 Linux 虚机上,配置好 Java 环境,然后配置 Flink 环境变量。编辑 /etc/profile,填写以下内容,并保存。

# Flink HOME
FLINK_HOME=/opt/flink-1.12.0
export PATH=$FLINK_HOME/bin:$PATH

然后再执行 source /etc/profile 即可。最后执行 flink --version 验证效果。

6a1c2bd153b23265073167ba480ec9db.png

4.2 运行 WordCount 程序

将打的 jar 包,放到 Linux 虚机上,然后运行命令:

# 因为配置了flink的环境变量,所以在任意目录下都可以执行flink命令
flink run -c org.myorg.quickstart.SocketWindowWordCount /tmp/quickstart-0.1.jar
  • run:这个子命令用来提交和启动一个 Flink 作业。

  • -c:指定包名和类名,这个类是你要运行的 Flink 作业的逻辑。

  • /tmp/quickstart-0.1.jar:这是包含你 Flink 应用程序的 JAR 文件的路径。这个 JAR 文件应该包含了上述类及其依赖项。

dec6ad5fd2f36e1fe96e94aa26b3736c.png

提交成功后,我们可以访问 Flink Web UI,查看任务运行日志:

967319ee10356eb7c1f2f87343ac365f.png

在 nc -l 9002 的命令窗口,造些数据,如下图所示:

cd87f4dcaf02ec43cebae51098e6bc20.jpeg

查看 flink WordCount 程序输出日志:
因为程序里设置的 wordCounts.print(),是控制台输出,所以我们的统计结果在 Stdout 里面:

f3d81203ad4b6d0eef06a3e602eee87e.png

5、将统计结果打印到文件中

上面我们是将统计结果打印到控制台,现在我们将统计结果打印到文件中。
自动生成的这个 maven 项目,好像缺少了 slf4j-api 依赖,添加如下:

<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>1.7.30</version> <!-- Make sure to use the correct version -->
</dependency>

然后自定义 Sink 端,继承 SinkFunction:

import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.functions.sink.SinkFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class WordCountSink implements SinkFunction<Tuple2<String, Integer>> {

    private static final Logger logger = LoggerFactory.getLogger(WordCountSink.class);

    @Override
    public void invoke(Tuple2<String, Integer> value, Context context) throws Exception {
        logger.info("Word: {}, windows Count: {}", value.f0, value.f1);
    }
}

然后返回到 SocketWindowWordCount.java 中,将 wordCounts.print().setParallelism(1); 注释掉,添加 wordCounts.addSink(new WordCountSink()).name("WordCount log Sink");就 OK 了。

d76121f83ddb1dc44b10a5659e8d1317.png

提交 jar 包,运行如下:

e37f4202abef9f84f50d74c93c809d56.png

三、总结

本文主要介绍了 Apache Flink 这一流处理框架的基本使用,以及如何通过实现 WordCount 程序来学习 Flink 的基本编程模型。本文从创建 Maven 工程开始,详细介绍了如何编写、本地启动以及通过jar包运行 WordCount 程序,包括环境设置、数据源定义、数据转换、定义窗口、聚合操作和输出结果等关键步骤。

此外,还提到了如何将统计结果输出到文件中,以及解决运行中可能遇到的问题。

文档通过逐步分析代码和执行过程,帮助读者理解 Flink 程序的开发和运行流程,适合刚开始了解 Flink 作业提交流程的同学。

ending

一个人可以走得很快,但一群人才能走得更远。我的Ambari课程累计学员已经有 400+。感谢信任的同时,如果你需要一个良好的Ambari学习与交流环境,就请加入我们吧。这是一个学习Ambari的付费私密圈子,里面的人都是Ambari的活跃二次开发者,报名后,你可以享有知识星球 + 学员微信群 + 课程资料(笔记、视频等)+ 导师学习陪伴答疑服务,认识更多大佬,和大家一起成长。也欢迎大家点击左下角阅读原文了解我,希望我能提供的服务可以帮助到你。

c7e4afdfdb07b0ab267976d7da7e30b9.png

最后,把我的座右铭送给大家:执行是消除焦虑的有效办法,明确并拆解自己的目标,一直行动,剩下的交给时间。共勉 💪。

“阅读原文”,查看最新内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

create17

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值