Flink 快速上手Day-02

前言

通过第一篇Flink简介我们对Flink有了基本的了解,接下来我们进行实操上手写代码。Flink底层是Java编写的,并为开发者提供了完整的Java和Scala API。本文编写Flink项目环境及工具:Java 8 ,IDEA ,Git和Maven工具。

一、创建项目

1.1 使用Git创建仓库

1.1.1这里只是再一次演示创建的步骤,可以看出flink-study仓库名是已经存在的了,点击创建即创建仓库完成。
在这里插入图片描述
1.1.2 复制仓库的URL,打开IDEA点击File->Project from Version Control->
在这里插入图片描述
输入URL可项目对应的目录点击克隆即可完成项目初始化。
在这里插入图片描述

flink-study的gitee地址https://gitee.com/Fh_1214/flink-study.git

1.2 创建maven工程

1.2.1 File->new->Project->Maven->Next 填写项目信息后点击finish。
在这里插入图片描述
1.2.2 拷贝quick-start的pom.xml文件粘贴到flink-study目录下,编写pom,xml信息如下(若pom.xml变红色则点击pom.xml右击选择Add as maven project即可),使用module标签flink-study项目作为quick-start的父项目统一管理。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.huang</groupId>
    <artifactId>flink-study</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>flink-study</name>
    <description>聚合服务</description>
    <packaging>pom</packaging>

    <modules>
       <module>quick-start</module>
    </modules>
</project>

1.2.3 添加flink和scala等依赖到quick-start的pom.xml文件中

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>flink-study</artifactId>
        <groupId>com.huang</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>quick-start</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <flink.version>1.13.0</flink.version>
        <java.version>1.8</java.version>
        <scala.binary.version>2.12</scala.binary.version>
        <slf4j.version>1.7.30</slf4j.version>
    </properties>

    <dependencies>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-java</artifactId>
        <version>${flink.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-streaming-java_${scala.binary.version}</artifactId>
        <version>${flink.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-clients_${scala.binary.version}</artifactId>
        <version>${flink.version}</version>
    </dependency>

    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-runtime-web_${scala.binary.version}</artifactId>
        <version>${flink.version}</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-to-slf4j</artifactId>
            <version>2.14.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

1.2.4 在quick-start的resources目录新建log4j.properties日在配置

log4j.rootLogger=error, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

1.2.5 在quick-start目录下创建input目录,新建文本words.txt。文本信息如下

hello atguigu
hello flink
hello java
hello shanghai
nihao flink

1.2.6 注意在flink-study目录下创建.gitignore文件,避免提交二外代码到gitee仓库。

target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar

**/mvnw
**/mvnw.cmd
**/.mvn
**/target
.idea
**/.gitignore

二、批处理

2.1 统计单词频次

统计一段文字中,每个单词出现的频次,WordCount 程序——大数据领域入门案例等同于初学编程语言时的Hello World。input文件夹下的文本文件words.txt即是作数据集。词频次统计的基本思路是:先逐行读入文件数据,然后将每一行文字拆分成单词;接着按照单词分组,统计每组数据的个数,就是对应单词的频次。

2.2 代码实现

/**
 * 批处理单词个数
 * @Author huang.bX
 * @Date 2022/9/29
 */
public class BatchWordCount {
    public static void main(String[] args) {
        //1.创建执行环境
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        //2.从文件读取数据
        DataSource<String> dataSource = env.readTextFile("quick-start/input/words.txt");
        //3.将每行数据进行分词,换成二元组类型
        FlatMapOperator<String, Tuple2<String, Long>> wordAndOne = dataSource
                .flatMap((String line, Collector<Tuple2<String, Long>> out) -> {
                    //将一行文本进行分词
                    String[] split = line.split(" ");
                    //将每个单词转换成二元组输出
                    for (String word : split) {
                        out.collect(Tuple2.of(word, 1L));
                    }
                })
                .returns(Types.TUPLE(Types.STRING, Types.LONG));  //当Lambda表达式使用 java 泛型的
        //4.按照word进行分组
        UnsortedGrouping<Tuple2<String, Long>> group = wordAndOne.groupBy(0);
        //5.分组内聚合统计
        AggregateOperator<Tuple2<String, Long>> sum = group.sum(1);
        //6. 打印结果
        try {
            sum.print();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.3 打印输出

(flink,2)
(hello,4)
(nihao,1)
(atguigu,1)
(java,1)
(shanghai,1)

Process finished with exit code 0

三、流处理

对于 Flink 而言,流才是整个处理逻辑的底层核心,所以流批统一之后的 DataStream API 更加强大,可以直接处理批处理和流处理的所有场景。

3.1 读取文件

我们同样试图读取文档 words.txt 中的数据,并统计每个单词出现的频次。这是一个“有界流”的处理,整体思路与之前的批处理非常类似,代码模式也基本一致。

3.1.1 代码实现

package com.huang.wordcount;

import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
import java.util.Arrays;
public class BoundedStreamWordCount {
    public static void main(String[] args) throws Exception {
        //1.创建流式执行环境
        StreamExecutionEnvironment env =
                StreamExecutionEnvironment.getExecutionEnvironment();
        //2.读取文件
        DataStreamSource<String> lineDSS = env.readTextFile("quick-start/input/words.txt");
        //3 转换数据格式
        SingleOutputStreamOperator<Tuple2<String, Long>> wordAndOne = lineDSS
                .flatMap((String line, Collector<String> words) -> {
                    Arrays.stream(line.split(" ")).forEach(words::collect);
                })
                .returns(Types.STRING)
                .map(word -> Tuple2.of(word, 1L))
                .returns(Types.TUPLE(Types.STRING, Types.LONG));
        //4.分组
        KeyedStream<Tuple2<String, Long>, String> wordAndOneKS = wordAndOne
                .keyBy(t -> t.f0);
        //5.求和
        SingleOutputStreamOperator<Tuple2<String, Long>> result = wordAndOneKS
                .sum(1);
        //6.打印
        result.print();
        //7.执行
        env.execute();
    }
}

3.1.2 打印输出

7> (flink,1)
1> (nihao,1)
3> (hello,1)
7> (flink,2)
2> (java,1)
8> (atguigu,1)
3> (hello,2)
5> (shanghai,1)
3> (hello,3)
3> (hello,4)

Process finished with exit code 0

主要观察与批处理程序 BatchWordCount 的不同:

  • 创建执行环境的不同,流处理程序使用的是 StreamExecutionEnvironment。
  • 每一步处理转换之后,得到的数据对象类型不同。
  • 分组操作调用的是 keyBy 方法,可以传入一个匿名函数作为键选择器(KeySelector),指定当前分组的 key 是什么。
  • 代码末尾需要调用 env 的 execute 方法,开始执行任务。

3.2 读取文本流

在实际的生产环境中,真正的数据流其实是无界的,有开始却没有结束,这就要求我们需要保持一个监听事件的状态,持续地处理捕获的数据。为了模拟这种场景,我们就不再通过读取文件来获取数据了,而是监听数据发送端主机的指定端口,统计发送来的文本数据中出现过的单词的个数。具体实现上,我们只要对BoundedStreamWordCount 代码中读取数据的步骤稍做修改,就可以实现对真正无界流的处理。

3.2.1 代码实现

package com.huang.wordcount;

import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
import java.util.Arrays;

public class StreamWordCount {
    public static void main(String[] args) {
        //1.创建流式执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //2. 读取文本流
        DataStreamSource<String> lineDSS = env.socketTextStream("localhost", 7777);
        //3. 转换数据格式
        SingleOutputStreamOperator<Tuple2<String, Long>> wordAndOne = lineDSS
                .flatMap((String line, Collector<String> words) -> {
                    Arrays.stream(line.split(" ")).forEach(words::collect);
                })
                .returns(Types.STRING).map(word -> Tuple2.of(word, 1L))
                .returns(Types.TUPLE(Types.STRING, Types.LONG));
        //4.分组
        KeyedStream<Tuple2<String, Long>, String> wordAndOneKS = wordAndOne
                .keyBy(t -> t.f0);
        //5.求和
        SingleOutputStreamOperator<Tuple2<String, Long>> result = wordAndOneKS
                .sum(1);
        //6.打印
        result.print();
        //7.执行
        try {
            env.execute();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.2.2 输入流数据
在这里插入图片描述
3.2.3 控制台输出
在这里插入图片描述
3.2.4 代码说明和注意事项

  • socket 文本流的读取需要配置两个参数:发送端主机名和端口号。这里代码中指定了主机“localhost”的 7777 端口作为发送数据的 socket 端口,读者可以根据测试环境自行配置。
  • 在实际项目应用中,主机名和端口号这类信息往往可以通过配置文件,或者传入程序运行参数的方式来指定。
  • socket文本流数据的发送,可以通过Linux系统自带的netcat工具进行模拟。
    (1)在 Windows 环境的主机 localhost 上,执行下列命令,发送数据进行测试:nc -l -p 7777
    (2)启动 StreamWordCount 程序我们会发现程序启动之后没有任何输出、也不会退出。这是正常的——因为 Flink 的流处理是事件驱动的,当前程序会一直处于监听状态,只有接收到数据才会执行任务、输出统计结果。

总结

本章主要实现一个 Flink 开发的入门程序——词频统计 WordCount。通过批处理和流处理两种不同模式的实现,可以对 Flink 的 API 风格和编程方式有所熟悉,并且更加深刻地理解批处理和流处理的不同。另外,通过读取有界数据(文件)和无界数据(socket 文本流)进行流处理的比较,我们也可以更加直观地体会到 Flink 流处理的方式和特点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值