前摘
代码示例将全 部用 Scala 实现
一、环境准备
-
提前安装Java 8 和 Scala 2.12
-
集成开发环境(IDE)使用IntelliJ IDEA
-
安装的插件——Maven、Git 和 Scala
-
Maven 用来 管理项目依赖;
补:详细的Maven介绍在我的另一篇CSDN文章大数据学习之Maven项目和pom文件上,点击蓝色链接直接跳转查看
-
Git 可以轻松获取我们的示例代码,并进行本地代码的版本控制。
-
-
程序基于 FLink 1.13.0
二、创建项目
在 IDEA 中搭建一个 Flink 项目的骨架。使用 Maven 来进行依赖管理。
1.创建Maven工程
2.添加项目依赖
在项目的 pom 文件中,增加标签设置属性,然后增加标签引 入需要的依赖。我们需要添加的依赖最重要的就是 Flink 的相关组件,包括 flink-scala、 flink-streaming-scala,以及 flink-clients(客户端,也可以省略)。
补:需要pom文件的,可以去我另一篇CSDN文章大数据学习所有上的pom文件依赖大全上获取,直接点击蓝色链接直接跳转查看
3.编写代码
3.1批处理
-
在工程根目录下新建一个 input 文件夹,并在下面创建文本文件 words.txt
-
在 words.txt 中输入一些文字,例如:
hello world
hello flink
hello scala
-
新建 Scala 的单例对象(object)BatchWordCount,在 静态 main 方法中编写测试代码。
我们进行单词频次统计的基本思路是:先逐行读入文件数据,然后将每一行文字拆分成单 词;接着按照单词分组,统计每组数据的个数,就是对应单词的频次。
具体代码实现如下:
package chapter02 import org.apache.flink.api.scala._ object BatchWordCount { def main(args: Array[String]): Unit = { //创建执行环境并配置并行度 val env = ExecutionEnvironment.getExecutionEnvironment //读取文本文件 val lineDS = env.readTextFile("input/words.txt") //对数据进行格式转换 val wordAndOne = lineDS.flatMap(_.split(" ")).map(r => (r,1)) //对数据进行分组 val wordAndOneUG = wordAndOne.groupBy(0)//按照第一个字段进行分组 //对分组进行聚合 val sum = wordAndOneUG.sum(1)//按照第二个字段的值进行求和 //打印结果 sum.print() } }
-
结果如下:
可以看到,我们将文档中的所有单词的频次,全部统计出来,以二元组的形式在控制台打印输出了。
需要注意的是,这种代码的实现方式,是基于 DataSet API 的,也就是我们对数据的处理转换,是看作数据集来进行操作的。事实上 Flink 本身是流批统一的处理架构,批量的数据集本质上也是流,没有必要用两套不同的 API 来实现。 所以从 Flink 1.12 开始,官方推荐的做法是直接使用 DataStream API,在提交任务时通过将执行模式设为 BATCH 来进行批处理:
$ bin/flink run -Dexecution.runtime-mode=BATCH BatchWordCount.jar
3.2 流处理
3.2.1 读取文件
读取文档 words.txt 中的数据,并统计每个单词出现的频次。
-
下新建 Scala 的单例对象 BoundedStreamWordCount,在 静态 main 方法中编写测试代码。具体代码实现如下:
import org.apache.flink.streaming.api.scala._ object BoundedStreamWordCount { def main(args: Array[String]): Unit = { //创建流执行环境 val env = StreamExecutionEnvironment.getExecutionEnvironment //读取文件获取数据流 val lineDs = env.readTextFile("input/words.txt") //对数据流执行转换操作 val wordAndOne = lineDs.flatMap(_.split(" ")).map(data => (data,1)) //对数据进行分组 val wordAndOneKS = wordAndOne.keyBy(_._1)//根据根据每一行的第一个字段分组 //对分组数据进行聚合 val result = wordAndOneKS.sum(1) //打印结果 result.print() //执行任务 env.execute() } }
-
结果如下:
我们可以看到,这与批处理的结果是完全不同的。批处理针对每个单词,只会输出一个最 终的统计个数;而在流处理的打印结果中,“hello”这个单词每出现一次,都会有一个频次统计 数据输出。这就是流处理的特点,数据逐个处理,每来一条数据就会处理输出一次。我们通过 打印结果,可以清晰地看到单词“hello”数量增长的过程。
3.2.2 读取文本流
在实际的生产环境中,真正的数据流其实是无界的,有开始却没有结束,这就要求我们需 要保持一个监听事件的状态,持续地处理捕获的数据。 为了模拟这种场景,我们就不再通过读取文件来获取数据了,而是监听数据发送端主机的 指定端口,统计发送来的文本数据中出现过的单词的个数。具体实现上,我们只要对 BoundedStreamWordCount 代码中读取数据的步骤稍做修改,就可以实现对真正无界流的处理。
-
将 BoundedStreamWordCount 代码中读取文件数据的 readTextFile()方法,替换成读 取 socket 文本流的方法 socketTextStream()方法。
具体代码实现如下:
import org.apache.flink.streaming.api.scala._ object StreamWordCount { def main(args: Array[String]): Unit = { val env = StreamExecutionEnvironment.getExecutionEnvironment //通过主机名和端口号读取 socket 文本流 val lineDS = env.socketTextStream("hadoop102", 7777) // 进行转换计算 val result = lineDS .flatMap(data => data.split(" ")) // 用空格切分字符串 .map((_, 1)) // 切分以后的单词转换成一个元组 .keyBy(_._1) // 使用元组的第一个字段进行分组 .sum(1) // 对分组后的数据的第二个字段进行累加 // 打印计算结果 result.print() // 执行程序 env.execute() } }
-
在 Linux 环境的主机bigdata1 上,执行下列命令,发送数据进行测试:
$ nc -lk 7777
-
启动 StreamWordCount 程序
我们会发现程序启动之后没有任何输出、也不会退出。这是正常的——因为 Flink 的流处 理是事件驱动的,当前程序会一直处于监听状态,只有接收到数据才会执行任务、输出统计结 果。
-
从 bigdata1 发送数据:
hello flink hello world hello scala
-
可以看到控制台输出结果如下:
我们会发现,输出的结果与之前读取文件的流处理非常相似。而且可以非常明显地看到, 每输入一条数据,就有一次对应的输出。具体对应关系是:输入“hello flink”,就会输出两条 统计结果(flink,1)和(hello,1);之后再输入“hello world”,同样会将 hello 和 world 的个 数统计输出,hello 的个数会对应增长为 2。