Flink:DataSetApi开发

入门案例

测试数据:
链接:https://pan.baidu.com/s/198Mx8HhlLoeb5eCqCmD2Iw
提取码:4362
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-base</artifactId>
        <groupId>flink-base</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>day02</artifactId>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <encoding>UTF-8</encoding>
        <scala.version>2.11.2</scala.version>
        <scala.compat.version>2.11</scala.compat.version>
        <hadoop.version>2.6.0</hadoop.version>
        <flink.version>1.7.2</flink.version>
        <scala.binary.version>2.11</scala.binary.version>
        <iheart.version>1.4.3</iheart.version>
        <fastjson.version>1.2.7</fastjson.version>
    </properties>

    <dependencies>
        <!-- 导入scala的依赖 -->
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>
        <!-- 导入flink streaming和scala的依赖 -->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-scala_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <!-- 导入flink和scala的依赖 -->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-scala_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <!-- 指定flink-client API的版本 -->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-clients_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <!-- 导入flink-table的依赖 -->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <!-- 指定hadoop-client API的版本 -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>${hadoop.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>xml-apis</groupId>
                    <artifactId>xml-apis</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 指定mysql-connector的依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>
        <!-- 指定fastjson的依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.60</version>
        </dependency>
        <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path</artifactId>
            <version>2.3.0</version>
        </dependency>
        <!-- 指定flink-connector-kafka的依赖 -->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-kafka-0.11_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>

        <!-- 指定 json/xml 转对象的依赖包 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.9</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.9.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-scala_2.11</artifactId>
            <version>2.9.9</version>
        </dependency>
        <!-- 指定 redis的依赖包 -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.7.1</version><!--版本号可根据实际情况填写-->
        </dependency>


    </dependencies>
    <build>
        <sourceDirectory>src/main/scala</sourceDirectory>
        <testSourceDirectory>src/test/scala</testSourceDirectory>
        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                    <!--<encoding>${project.build.sourceEncoding}</encoding>-->
                </configuration>
            </plugin>

            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                        <configuration>
                            <args>
                                <!--<arg>-make:transitive</arg>-->
                                <arg>-dependencyfile</arg>
                                <arg>${project.build.directory}/.scala_dependencies</arg>
                            </args>

                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
                <configuration>
                    <useFile>false</useFile>
                    <disableXmlReport>true</disableXmlReport>
                    <includes>
                        <include>**/*Test.*</include>
                        <include>**/*Suite.*</include>
                    </includes>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <!--
                                        zip -d learn_spark.jar META-INF/*.RSA META-INF/*.DSA META-INF/*.SF
                                        -->
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.czxy.flink.batch.BatchWordCount</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>


Flink 批处理程序的一般流程

  1. 获取 Flink 批处理执行环境

  1. 构建 source

  1. 数据处理

  1. 构建 sink

示例

编写 Flink 程序,用来统计单词的数量。

步骤

  1. IDEA 创建项目

  2. 导入 Flink 所需的 Maven 依赖

  3. 创建 scala 单例对象,添加 main 方法

  4. 获取 Flink 批处理运行环境

  5. 构建一个 collection 源

  6. 使用 flink 操作进行单词统计

  7. 打印

实现
import org.apache.flink.api.scala.ExecutionEnvironment import org.apache.flink.api.scala._

/**
* 编写Flink程序,统计单词
*/
object BatchWordCount {
def main(args: Array[String]): Unit = { /**

*实现思路
*1、创建执行环境
*2、接入数据源
*3、进行数据处理
*4、数据保存或输出
*/

//1、创建执行环境
val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
//2、接入数据源
val testDataSet: DataSet[String] = env.fromCollection(List("hadoop spark hive","hadoop hadoop spark"))
//3、进行数据处理
//切分
val wordDataSet: DataSet[String] = testDataSet.flatMap(_.split(" "))
//每个单词标记1
val wordAndOneDataSet: DataSet[(String, Int)] = wordDataSet.map((_,1))
//按照单词进行分组
val groupDataSet: GroupedDataSet[(String, Int)] = wordAndOneDataSet.groupBy(0)
//对单词进行聚合
val sumDataSet: AggregateDataSet[(String, Int)] = groupDataSet.sum(1)
//4、数据保存或输出
//	sumDataSet.print()
//数据输出到本地
sumDataSet.writeAsText("./ResultData/BatchWordCount")
//数据输出到HDFS
sumDataSet.writeAsText("hdfs://node01:8020/test/output/BatchWordCount ")
} } env.execute("BatchWordCount")


将程序打包,提交到 yarn:

添加 maven 打包插件

<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<!--<encoding>${project.build.sourceEncoding}</encoding>-->
</configuration>
</plugin>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<args>
<!--<arg>-make:transitive</arg>-->
<arg>-dependencyfile</arg>
<arg>${project.build.directory}/.scala_dependencies</arg> </args>
</configuration>
</execution>
</executions>
</plugin>
<plugin>

<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
<configuration>
<useFile>false</useFile>
<disableXmlReport>true</disableXmlReport> <includes>
<include>**/*Test.*</include>
<include>**/*Suite.*</include>
</includes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<!--zip -d learn_spark.jar META-INF/*.RSAMETA-INF/*.DSA META-INF/*.SF -->
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResou
rceTransformer">
<mainClass>cn.czxy.batch.BatchWordCount</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>

</build>

上传 jar 包到服务器上,然后执行程序:

bin/flink run -m yarn-cluster -yn 2
/export/servers/flink-1.7.2/jar/day01-1.0-SNAPSHOT.jar cn.czxy.batch.BatchWordCount

在 yarn 的 8088 页面可以观察到提交的程序:在这里插入图片描述

输入数据集 Data Sources

Data Sources 是什么呢?就字面意思其实就可以知道: 数据来源。

Flink 做为一款流式计算框架,它可用来做批处理,即处理静态的数据集、历史的数据集;
也可以用来做流处理,即实时的处理些实时数据流,实时的产生数据流结果,只要数据源源不断 的过来,Flink 就能够一直计算下去,这个 Data Sources 就是数据的来源地。 flink 在批处理中常见的 source 主要有两大类。

  1. 基于本地集合的 source(Collection-based-source)
  2. 基于文件的 source(File-based-source)

基于本地集合的 source(Collection-based-source)

在 flink 最常见的创建 DataSet 方式有三种:

  1. 使用 env.fromElements(),这种方式也支持 Tuple,自定义对象等复合形式。

  2. 使用 env.fromCollection(),这种方式支持多种 Collection 的具体类型

  3. 使用 env.generateSequence()方法创建基于 Sequence 的 DataSet

代码

env.fromElements()
package com.czxy.flink.batch.source.collection

import org.apache.flink.api.scala.ExecutionEnvironment

//使用 env.fromElements(), 这种方式也支持 Tuple, 自定义对象等复合形式
object BatchFromElementsDemo {
  def main(args: Array[String]): Unit = {
    //1.创建执行环境
    val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    //2.使用 env.fromElements构建数据集
    import org.apache.flink.api.scala._
    val sourceElement: DataSet[String] = env.fromElements("hadoop hadoop hive flink")
    //3.输出测试
    sourceElement.print()

  }
}

env.fromCollection()
package com.czxy.flink.batch.source.collection

import org.apache.flink.api.scala.ExecutionEnvironment

object BatchFromCollectionDemo {
  def main(args: Array[String]): Unit = {
    //1.创建执行环境
    val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    //2.使用env.fromCollection()构建数据集
    import org.apache.flink.api.scala._
    val sourceCollection: DataSet[String] = env.fromCollection(List("hadoop hadoop","flink flink"))
    //3.输出打印
    sourceCollection.print()
  }
}

env.generateSequence()
package com.czxy.flink.batch.source.collection

import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment}

object  BatchGenerateSequenceDemo {
  def main(args: Array[String]): Unit = {
    //1.创建执行环境
    val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    //2.使用generateSequence构建数据集
    val numberDataSet: DataSet[Long] = env.generateSequence(1,10)
    //3.打印输出
    numberDataSet.print()
  }
}

综合练习
import org.apache.flink.api.scala.ExecutionEnvironment import scala.collection.mutable
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
/**
*读取集合中的批次数据
*/
object BatchFromCollection {
def main(args: Array[String]): Unit = {
//获取flink执行环境
val env = ExecutionEnvironment.getExecutionEnvironment
//导入隐式转换
import org.apache.flink.api.scala._
//0.用element创建DataSet(fromElements)
val ds0: DataSet[String] = env.fromElements("spark", "flink")
ds0.print()
//1.用Tuple创建DataSet(fromElements)
val ds1: DataSet[(Int, String)] = env.fromElements((1, "spark"), (2, "flink")) ds1.print()
//2.用Array创建DataSet
val ds2: DataSet[String] = env.fromCollection(Array("spark", "flink"))
ds2.print()
//3.用ArrayBuffer创建DataSet
val ds3: DataSet[String] = env.fromCollection(ArrayBuffer("spark", "flink")) ds3.print()
//4.用List创建DataSet
val ds4: DataSet[String] = env.fromCollection(List("spark", "flink"))
ds4.print()
//5.用List创建DataSet
val ds5: DataSet[String] = env.fromCollection(ListBuffer("spark", "flink")) ds5.print()
//6.用Vector创建DataSet
val ds6: DataSet[String] = env.fromCollection(Vector("spark", "flink")) ds6.print()
//7.用Queue创建DataSet
val ds7: DataSet[String] = env.fromCollection(mutable.Queue("spark", "flink")) ds7.print()
//8.用Stack创建DataSet
val ds8: DataSet[String] = env.fromCollection(mutable.Stack("spark", "flink")) ds8.print()
//9.用Stream创建DataSet(Stream相当于lazy List,避免在中间过程中生成不必要的集合)
val ds9: DataSet[String] = env.fromCollection(Stream("spark", "flink")) ds9.print()
//10.用Seq创建DataSet
val ds10: DataSet[String] = env.fromCollection(Seq("spark", "flink"))
ds10.print()
//11.用Set创建DataSet
val ds11: DataSet[String] = env.fromCollection(Set("spark", "flink"))
ds11.print()
//12.用Iterable创建DataSet
val ds12: DataSet[String] = env.fromCollection(Iterable("spark", "flink")) ds12.print()
//13.用ArraySeq创建DataSet
val ds13: DataSet[String] =
env.fromCollection(mutable.ArraySeq("spark", "flink")) ds13.print() //14.用ArrayStack创建DataSet
val ds14: DataSet[String] =
env.fromCollection(mutable.ArrayStack("spark", "flink"))
ds14.print()
//15.用Map创建DataSet
val ds15: DataSet[(Int, String)] = env.fromCollection(Map(1 -> "spark", 2 -> "flink")) ds15.print()
//16.用Range创建DataSet
val ds16: DataSet[Int] = env.fromCollection(Range(1, 9)) ds16.print() //17.用fromElements创建DataSet
val ds17: DataSet[Long] = env.generateSequence(1, 9) ds17.print()
}}

基于文件的 source(File-based-source)

读取本地文件

package com.czxy.flink.batch.source.file

import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment}

//从本地文件构建数据集
object BatchFromLocalFileSource {
  def main(args: Array[String]): Unit = {
    //1.创建执行环境
    val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    //2.从本地文件构建数据集
    val localFileSource: DataSet[String] = env.readTextFile("day02/data/input/wordcount.txt")
    //3.打印输出
    localFileSource.print()
  }
}

读取 HDFS 数据

package com.czxy.flink.batch.source.file

import org.apache.calcite.interpreter.AggregateNode.MaxInt
import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment}

object BatchFromHDFSFileSource {
  def main(args: Array[String]): Unit = {
    //1.创建执行环境
    val env = ExecutionEnvironment.getExecutionEnvironment
    //2.从HDFS文件构建数据集
    val hdfsFileSource: DataSet[String] = env.readTextFile("hdfs://node01:8020/test/input/wordcount.txt")
    //3.输出打印
    hdfsFileSource.print()
  }
}

读取 CSV 数据

package com.czxy.flink.batch.source.file

import org.apache.flink.api.scala.ExecutionEnvironment

object BatchFromCSVFileSource {
  case class Subject(id:Int,name:String)
  def main(args: Array[String]): Unit = {
    //1.创建执行环境
    val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    //2.从csv文件构建数据集
    import org.apache.flink.api.scala._
    val csvDataSet: DataSet[Subject] = env.readCsvFile[Subject]("day02/data/input/subject.csv")
    //3.输出打印
    csvDataSet.print()
  }
}

读取压缩文件

对于以下压缩类型,不需要指定任何额外的 inputformat 方法,flink 可以自动识别并且解 压。但是,压缩文件可能不会并行读取,可能是顺序读取的,这样可能会影响作业的可伸缩性。
在这里插入图片描述

package com.czxy.flink.batch.source.file

import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment}

object BatchFromCompressFileSource {
  def main(args: Array[String]): Unit = {
    //1.创建执行环境
    val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    //2.从压缩文件中构建数据集
    val compressFileSource: DataSet[String] = env.readTextFile("day02/data/input/wordcount.txt.gz")
    //3.输出打印
    compressFileSource.print()
  }
}

基于文件的 source(遍历目录)

flink 支持对一个文件目录内的所有文件,包括所有子目录中的所有文件的遍历访问方式。对于从文件中读取数据,当读取的数个文件夹的时候,嵌套的文件默认是不会被读取的,只会读取第一个文件,其他的都会被忽略。所以我们需要使用 recursive.file.enumeration 进行递归读取。

import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment} import org.apache.flink.configuration.Configuration

/**

*遍历目录的批次数据

*/
object BatchFromFolder {

def main(args: Array[String]): Unit = {

//初始化环境


val env = ExecutionEnvironment.getExecutionEnvironment val parameters = new Configuration

// recursive.file.enumeration 开启递归

parameters.setBoolean("recursive.file.enumeration", true)

val result =
env.readTextFile("day01/data/input/folder").withParameters(parameters
)

//触发程序执行

result.print()

}

}

DateSet 的 Transformation

map 函数

将 DataSet 中的每一个元素转换为另外一个元素

示例

使用 map 操作,将以下数据
“1,张三”, “2,李四”, “3,王五”, “4,赵六”
转换为一个 scala 的样例类。

步骤

  1. 获取 ExecutionEnvironment 运行环境

  2. 使用 fromCollection 构建数据源

  3. 创建一个 User 样例类

  4. 使用 map 操作执行转换

  5. 打印测试

参考代码

import org.apache.flink.api.scala.ExecutionEnvironment import org.apache.flink.api.scala._

/**
*使用 map 操作,将以下数据
*"1,张三", "2,李四", "3,王五", "4,赵六"
*转换为一个 scala 的样例类。
*/
object BatchMapDemo {
case class User(uid:String,uname:String)
def main(args: Array[String]): Unit = {
val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
val textDataSet: DataSet[String] = env.fromElements("1,张三", "2, 李四", "3,王五", "4,赵六")
val userDataSet: DataSet[User] = textDataSet.map(line => {
val x = line.split(",")

})User(x(0), x(1))

} } userDataSet.print()

flatMap 函数

将 DataSet 中的每一个元素转换为 0…n 个元素

示例

分别将以下数据,转换成 国家 、省份 、城市 三个维度的数据。
将以下数据:

张三 ,中国 ,江西省 ,南昌市
李四 ,中国 ,河北省 ,石家庄市
Tom,America,NewYork,Manhattan

----------------转换为-----------
张三 ,中国
张三 ,中国江西省
张三 ,中国江西省南昌市

思路

以上数据为一条转换为三条,显然,
应当使用 flatMap 来实现分别在 flatMap 函数中构建三个数据,
并放入到一个列表中

姓名 , 国家
姓名 , 国家省份
姓名 , 国家省份城市

步骤

  1. 构建批处理运行环境

  2. 构建本地集合数据源

  3. 使用 flatMap 将一条数据转换为三条数据 a. 使用逗号分隔字段

    3.1. 分别构建国家、国家省份、国家省份城市三个元组

  4. 打印输出

代码示例

package cn.czxy.batch.Transformation



import org.apache.flink.api.scala.ExecutionEnvironment import org.apache.flink.api.scala._

/**

*将以下数据

*张三,中国,江西省,南昌市李四,中国,河北省,石家庄市

*Tom,America,NewYork,Manhattan

*转换为

*张三,中国

*张三,中国江西省

*张三,中国江西省南昌市
*/
object BatchFlatMapDemo {
def main(args: Array[String]): Unit = {

val env: ExecutionEnvironment =
ExecutionEnvironment.getExecutionEnvironment

val userDataSet: DataSet[String] = env.fromCollection(List("张三,

中国,江西省,南昌市","李四,中国,河北省,石家庄市", "Tom,America,NewYork,Manhattan"))

val userAdressDataSet: DataSet[(String, String)] = userDataSet.flatMap(line => {
val field = line.split(",")
List(
(field(0), field(1)),
(field(0), field(1) + field(2)),

})) (field(0), field(1) + field(2) + field(3))

} } userAdressDataSet.print()

mapPartition 函数

将一个分区中的元素转换为另一个元素

示例

使用 mapPartition 操作,将以下数据
“1,张三”, “2,李四”, “3,王五”, “4,赵六”
转换为一个 scala 的样例类。

步骤

  1. 获取 ExecutionEnvironment 运行环境

  2. 使用 fromCollection 构建数据源

  3. 创建一个 User 样例类

  4. 使用 mapPartition 操作执行转换

  5. 打印测试

参考代码

import org.apache.flink.api.scala.ExecutionEnvironment import org.apache.flink.api.scala._


/**


*使用 mapPartition 操作,

*将以下数据 "1,张三", "2,李四", "3,王五", "4,赵六"

*转换为一个 scala 的样例类。
*/

object BatchMapPartitionDemo {

case class User(id: String, name: String)

def main(args: Array[String]): Unit = {

val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment

val textDataSet: DataSet[String] = env.fromCollection(List("1,张三", "2,李四", "3,王五", "4,赵六"))

val userDataSet: DataSet[User] = textDataSet.mapPartition(textPartition => {
textPartition.map(x => {
val arrays: Array[String] = x.split(",")
})})User(arrays(0), arrays(1))

userDataSet.print()

} }

filter 函数

过滤出来 一些符合条件的元素
Filter 函数在实际生产中特别实用,
数据处理阶段可以过滤掉大部分不符合业务的内容
可以极大减轻整体 flink 的运算压力

示例

过滤出来以下以 h 开头的单词。
“hadoop”, “hive”, “spark”, “flink”

步骤

  1. 获取 ExecutionEnvironment 运行环境

  2. 使用 fromCollection 构建数据源

  3. 使用 filter 操作执行过滤

  4. 打印测试

参考代码

import org.apache.flink.api.scala.ExecutionEnvironment import org.apache.flink.api.scala._
/**

* 过滤出来以下以 h 开头的单词。

* "hadoop", "hive", "spark", "flink"
*/
object BatchFilterDemo {
def main(args: Array[String]): Unit = {
val env = ExecutionEnvironment.getExecutionEnvironment

val textDataSet: DataSet[String] = env.fromElements("hadoop",

"hive", "spark", "flink")

val filterDataSet: DataSet[String] = textDataSet.filter(x=>x.startsWith("h"))
filterDataSet.print()

} }

reduce 函数

可以对一个 dataset 或者一个 group 来进行聚合计算,最终聚合成一个元素

示例

请将以下元组数据,使用 reduce 操作聚合成一个最终结果
(“java” , 1) , (“java”, 1) ,(“java” , 1)
将上传元素数据转换为 (“java”,3)

步骤

  1. 获取 ExecutionEnvironment 运行环境

  2. 使用 fromCollection 构建数据源

  3. 使用 redice 执行聚合操作

  4. 打印测试

参考代码

import org.apache.flink.api.scala.ExecutionEnvironment import org.apache.flink.api.scala._

/**

*请将以下元组数据,

*使用 reduce 操作聚合成一个最终结果 ("java" , 1) , ("java", 1) ,("java" ,

1)

*将上传元素数据转换为 ("java",3)
*/

object BatchReduceDemo {
def main(args: Array[String]): Unit = {
val env = ExecutionEnvironment.getExecutionEnvironment val textDataSet: DataSet[(String, Int)] =

env.fromCollection(List(("java" , 1),("java", 1),("java" , 1)))
val groupedDataSet: GroupedDataSet[(String, Int)] = textDataSet.groupBy(0)
val reduceDataSet: DataSet[(String, Int)] = groupedDataSet.reduce((v1, v2)=>(v1._1,v1._2+v2._2))
reduceDataSet.print()

} }

reduceGroup

可以对一个 dataset 或者一个 group 来进行聚合计算,
最终聚合成一个元素 reduce 和 reduceGroup 的 区别

在这里插入图片描述
首先 groupBy 函数会将一个个的单词进行分组,分组后的数据被 reduce 一个个的拉取过来,这种方式如果数据量大的情况下,拉取的数据会非常多,增加了网络 IO reduceGroup 是 reduce 的一种优化方案;
它会先分组 reduce,然后在做整体的 reduce;这样做的好处就是可以减少网络IO

示例

请将以下元组数据,下按照单词使用 groupBy 进行分组,再使用 reduceGroup 操作进行单
词计数
(“java” , 1) , (“java”, 1) ,(“scala” , 1)

步骤

  1. 获取 ExecutionEnvironment 运行环境

  2. 使用 fromCollection 构建数据源

  3. 使用 groupBy 按照单词进行分组

  4. 使用 reduceGroup 对每个分组进行统计

  5. 打印测试

参考代码

import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment} import org.apache.flink.api.scala._

/**

* 请将以下元组数据,先按照单词使用 groupBy 进行分组,

* 再使用 reduceGroup 操作进行单词计数

* ("java" , 1) , ("java", 1) ,("scala" , 1)
*/
object BatchReduceGroupDemo {
def main(args: Array[String]): Unit = {
val env: ExecutionEnvironment =
ExecutionEnvironment.getExecutionEnvironment val textDataSet: DataSet[(String, Int)] =
env.fromCollection(List(("java" , 1),("java", 1),("scala" , 1)))
val groupedDataSet: GroupedDataSet[(String, Int)] = textDataSet.groupBy(0)
val reduceGroupDataSet: DataSet[(String, Int)] = groupedDataSet.reduceGroup(group => {

group.reduce((v1, v2) => {

})})(v1._1, v1._2 + v2._2)

} } reduceGroupDataSet.print()

Aggregate

按照内置的方式来进行聚合。例如:SUM/MIN/MAX…

示例

请将以下元组数据,使用 aggregate 操作进行单词统计
(“java” , 1) , (“java”, 1) ,(“scala” , 1)

步骤

  1. 获取 ExecutionEnvironment 运行环境

  2. 使用 fromCollection 构建数据源

  3. 使用 groupBy 按照单词进行分组

  4. 使用 aggregate 对每个分组进行 SUM 统计

  5. 打印测试

参考代码

Aggregate 只能作用于元组上

import org.apache.flink.api.java.aggregation.Aggregations import org.apache.flink.api.scala._

/**

*请将以下元组数据,使用 aggregate 操作进行单词统计

*("java" , 1) , ("java", 1) ,("scala" , 1)

*/
object BatchAggregateDemo {

def main(args: Array[String]): Unit = {

val env = ExecutionEnvironment.getExecutionEnvironment


val textDataSet = env.fromCollection(List(("java" , 1) , ("java", 1) ,("scala" , 1)))

val grouped = textDataSet.groupBy(0)

val aggDataSet: AggregateDataSet[(String, Int)] = grouped.aggregate(Aggregations.MAX, 1)

aggDataSet.print()


} }

minBy 和 maxBy

获取指定字段的最大值、最小值

示例

请将以下元组数据,使用 aggregate 操作进行单词统计

(1, “yuwen”, 89.0) , (2, “shuxue”, 92.2),(3, “yingyu”, 89.99),(4, “wuli”, 98.9),
(1,“yuwen”, 88.88),(1, “wuli”, 93.00),(1, “yuwen”, 94.3)

步骤

  1. 获取 ExecutionEnvironment 运行环境

  2. 使用 fromCollection 构建数据源

  3. 使用 groupBy 按照单词进行分组

  4. 使用 maxBy、minBy 对每个分组进行操作

  5. 打印测试

参考代码

package cn.czxy.batch.Transformation


import org.apache.flink.api.java.aggregation.Aggregations

import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment} import scala.collection.mutable

import scala.util.Random

object BatchMinByAndMaxBy {	= {		
def main(args: Array[String]): Unit			
val env = ExecutionEnvironment.getExecutionEnvironment	
val data = new mutable.MutableList[(Int,String, Double)]	
data.+=((1,	"yuwen",89.0))			
data.+=((2,	"shuxue",92.2))			
data.+=((3,	"yingyu",89.99))			
data.+=((4,	"wuli",98.9))			
data.+=((1,	"yuwen",88.88))			
data.+=((1,	"wuli",93.00))			
data.+=((1,	"yuwen",94.3))			

//导入隐式转换
import org.apache.flink.api.scala._
//fromCollection将数据转化成DataSet
val input: DataSet[(Int, String, Double)] = env.fromCollection(Random.shuffle(data))
input.print()
println("===========获取指定字段分组后,某个字段的最大值==================")
val output = input.groupBy(1).aggregate(Aggregations.MAX, 2)
output.print()
println("===========使用【MinBy】获取指定字段分组后,某个字段的最小值==================")

//val input: DataSet[(Int, String, Double)] = env.fromCollection(Random.shuffle(data))
val output2: DataSet[(Int, String, Double)] = input.groupBy(1)
//求每个学科下的最小分数
//minBy的参数代表要求哪个字段的最小值
.minBy(2)
output2.print()
println("===========使用【maxBy】获取指定字段分组后,某个字段的最大值==================")
//val input: DataSet[(Int, String, Double)] = env.fromCollection(Random.shuffle(data))
val output3: DataSet[(Int, String, Double)] = input
.groupBy(1)
//求每个学科下的最小分数
//minBy的参数代表要求哪个字段的最小值
.maxBy(2)
output3.print()
} }

distinct 去重

去除重复的数据

示例

请将以下元组数据,使用 distinct 操作去除重复的单词
(“java” , 1) , (“java”, 1) ,(“scala” , 1)
去重得到
(“java”, 1), (“scala”, 1)

步骤

  1. 获取 ExecutionEnvironment 运行环境

  2. 使用 fromCollection 构建数据源

  3. 使用 distinct 指定按照哪个字段来进行去重

  4. 打印测试

参考代码

import org.apache.flink.api.scala._

/**
*
*请将以下元组数据,使用 distinct 操作去除重复的单词
*
*("java" , 1) , ("java", 1) ,("scala" , 1)
*
*去重得到
*
*("java", 1), ("scala", 1)
*
*/
object BatchDistinctDemo {
def main(args: Array[String]): Unit = {
val env = ExecutionEnvironment.getExecutionEnvironment val textDataSet: DataSet[(String, Int)] =
env.fromCollection(List(("java", 1), ("java", 1), ("scala", 1)))
} textDataSet.distinct(1).print()
}

Join

使用 join 可以将两个 DataSet 连接起来

示例

有两个 csv 文件,有一个为 score.csv ,一个为 subject.csv ,分别保存了成绩数据以及学科数据
在这里插入图片描述
需要将这两个数据连接到一起,然后打印出来。
在这里插入图片描述

步骤

  1. 分别将资料中的两个文件复制到项目中的 data/join/input 中所需要的的数据我会上传到百度网盘中

  2. 构建批处理环境

  3. 创建两个样例类

    3.a.学科 Subject(学科 ID、学科名字)

    3.b.成绩 Score(唯一 ID、学生姓名、学科 ID、分数——Double 类型)

  4. 分别使用 readCsvFile 加载 csv 数据源,并制定泛型

  5. 使用 join 连接两个 DataSet,并使用 where 、 equalTo 方法设置关联条件

  6. 打印关联后的数据源

参考代码

import org.apache.flink.api.scala._

/**
*
* 使用join可以将两个DataSet连接起来
*
*/

object BatchJoinDemo {
case class Subject(id: Int, name: String)
case class Score(id: Int, stuName: String, subId: Int, score: Double)
def main(args: Array[String]): Unit = {
val env = ExecutionEnvironment.getExecutionEnvironment
val subjectDataSet: DataSet[Subject] =
env.readCsvFile[Subject]("day01/data/input/subject.csv")
val scoreDataSet: DataSet[Score] =
env.readCsvFile[Score]("day01/data/input/score.csv")
//join的替代方案:broadcast
val joinDataSet: JoinDataSet[Score, Subject] = scoreDataSet.join(subjectDataSet).where(_.subId).equalTo(_.id)
joinDataSet.print()
} }

优化 join

通过给 Flink 一些提示,可以使得你的 join 更快,但是首先我们要简单了解一下 Flink 如何执 行 join 的。
当 Flink 处理批量数据的时候,每台机器只是存储了集群的部分数据。为了执行 join, Flink 需 要找到两个数据集的所有满足 join 条件的数据。为了实现这个目标,Flink 需要将两个数据集有相同 key 的数据发送到同一台机器上。有两种策略:

  1. repartition-repartition strategy
    在该情况下,两个数据集都会使用 key 进行重分区并使用通过网络传输。这就意味着假如数据集 太大的话,网络传输数据集将耗费大量的时间。
  2. broadcast-forward strategy
    在该情况下,一个数据集不动,另一个数据集会 copy 到有第一个数据集部分数据的所有机器上。如果使用小数据集与大数据集进行 join,可以选择 broadcast-forward 策略,将小数据集广播, 避免代价高的重分区。
ds1.join(ds2, JoinHint.BROADCAST_HASH_FIRST)

第二个参数就是提示,第一个数据集比第二个小。
也可以使用下面几个提示:

BROADCAST_HASH_SECOND: 第二个数据集是较小的数据集

REPARTITION_HASH_FIRST: 第一个书记集是较小的数据集

REPARTITION_HASH_SECOND: 第二个数据集是较小的数据集。

REPARTITION_SORT_MERGE: 对数据集进行重分区,同时使用 sort  merge 策略。

OPTIMIZER_CHOOSES: (默认的)Flink 的优化器决定两个数据集如何 join。

LeftOuterJoin

左外连接,左边的 Dataset 中的每一个元素,去连接右边的元素

示例

请将以下元组数据(用户 id,用户姓名)
(1, “zhangsan”) , (2, “lisi”) ,(3 , “wangwu”) ,(4 , “zhaoliu”)
元组数据(用户 id,所在城市)
(1, “beijing”), (2, “shanghai”), (4, “guangzhou”)
返回如下数据:
(3,wangwu,null)
(1,zhangsan,beijing)
(2,lisi,shanghai)
(4,zhaoliu,guangzhou)

参考代码

import org.apache.flink.api.scala.ExecutionEnvironment import scala.collection.mutable.ListBuffer

/**

* 左外连接,左边的Dataset中的每一个元素,去连接右边的元素
*/
object BatchLeftOuterJoinDemo {
def main(args: Array[String]): Unit = {

val env = ExecutionEnvironment.getExecutionEnvironment import org.apache.flink.api.scala._

val data1 = ListBuffer[Tuple2[Int, String]]()

data1.append((1, "zhangsan"))
data1.append((2, "lisi"))
data1.append((3, "wangwu"))
data1.append((4, "zhaoliu"))

val data2 = ListBuffer[Tuple2[Int, String]]()

data2.append((1, "beijing"))
data2.append((2, "shanghai"))
data2.append((4, "guangzhou"))

val text1 = env.fromCollection(data1)

val text2 = env.fromCollection(data2)


text1.leftOuterJoin(text2).where(0).equalTo(0).apply((first, second) => {

if (second == null) {
(first._1, first._2, "null")

} else {

} (first._1, first._2, second._2)
}).print()

}

}

RightOuterJoin

右外连接,左边的 Dataset 中的每一个元素,去连接左边的元素

示例

请将以下元组数据(用户 id,用户姓名)

(1, “zhangsan”) , (2, “lisi”) ,(3 , “wangwu”) ,(4 , “zhaoliu”)
元组数据(用户 id,所在城市)(1, “beijing”), (2, “shanghai”), (4, “guangzhou”)
返回如下数据:
(1,zhangsan,beijing)
(2,lisi,shanghai)
(4,zhaoliu,guangzhou)

参考代码

import org.apache.flink.api.scala.ExecutionEnvironment import scala.collection.mutable.ListBuffer

/**

* 左外连接,左边的Dataset中的每一个元素,去连接右边的元素
*/
object BatchLeftOuterJoinDemo {

def main(args: Array[String]): Unit = {

val env = ExecutionEnvironment.getExecutionEnvironment import org.apache.flink.api.scala._
val data1 = ListBuffer[Tuple2[Int, String]]()

data1.append((1, "zhangsan"))
data1.append((2, "lisi"))

data1.append((3, "wangwu"))


data1.append((4, "zhaoliu"))

val data2 = ListBuffer[Tuple2[Int, String]]()

data2.append((1, "beijing"))
data2.append((2, "shanghai"))

data2.append((4, "guangzhou"))

val text1 = env.fromCollection(data1)

val text2 = env.fromCollection(data2)


text1.leftOuterJoin(text2).where(0).equalTo(0).apply((first, second) => {

if (second == null) {
(first._1, first._2, "null")
} else {

} (first._1, first._2, second._2)
}).print()

}

}

fullOuterJoin

全外连接,左右两边的元素,全部连接

示例

请将以下元组数据(用户 id,用户姓名)
(1, “zhangsan”) , (2, “lisi”) ,(3 , “wangwu”) ,(4 , “zhaoliu”)
元组数据(用户 id,所在城市)
(1, “beijing”), (2, “shanghai”), (4, “guangzhou”)
返回如下数据:
(3,wangwu,null)
(1,zhangsan,beijing)
(2,lisi,shanghai)
(4,zhaoliu,guangzhou)

参考代码

import

org.apache.flink.api.common.operators.base.JoinOperatorBase.JoinHint import org.apache.flink.api.scala.{ExecutionEnvironment, _}

import scala.collection.mutable.ListBuffer

/**

*

*左外连接,左边的Dataset中的每一个元素,去连接右边的元素

*

*/

object BatchFullOuterJoinDemo {

def main(args: Array[String]): Unit = {
val env = ExecutionEnvironment.getExecutionEnvironment
val data1 = ListBuffer[Tuple2[Int, String]]()

data1.append((1, "zhangsan"))
data1.append((2, "lisi"))
data1.append((3, "wangwu"))
data1.append((4, "zhaoliu"))

val data2 = ListBuffer[Tuple2[Int, String]]()

data2.append((1, "beijing"))
data2.append((2, "shanghai"))
data2.append((4, "guangzhou"))


val text1 = env.fromCollection(data1)
val text2 = env.fromCollection(data2)
/**
*OPTIMIZER_CHOOSES:将选择权交予Flink优化器,相当于没有给提示;
*BROADCAST_HASH_FIRST:广播第一个输入端,同时基于它构建一个哈希表,而第二个输入端作为探索端,选择这种策略的场景是第一个输入端规模很小;
*BROADCAST_HASH_SECOND:广播第二个输入端并基于它构建哈希表,第一个输入端作为探索端,选择这种策略的场景是第二个输入端的规模很小;
*REPARTITION_HASH_FIRST:该策略会导致两个输入端都会被重分区,但会基于第一个输入端构建哈希表。该策略适用于第一个输入端数据量小于第二个输入端的数据量,但这两个输入端的规模仍然很大,优化器也是当没有办法估算大小,没有已 存在的分区以及排序顺序可被使用时系统默认采用的策略;
*REPARTITION_HASH_SECOND:该策略会导致两个输入端都会被重分区,但会基于第二个输入端构建哈希表。该策略适用于两个输入端的规模都很大,但第二个输入端的数据量小于第一个输入端的情况;
*REPARTITION_SORT_MERGE:输入端被以流的形式进行连接并合并成排过序的输入。
该策略适用于一个或两个输入端都已 排过序的情况;
*/
text1.fullOuterJoin(text2,

JoinHint.REPARTITION_SORT_MERGE).where(0).equalTo(0).apply((first, second) => {
if (first == null) {

(second._1, "null", second._2)

} else if (second == null) {

(first._1, first._2, "null")
} else {

(first._1, first._2, second._2)

}

}).print()

}

}

cross 交叉操作

和 join 类似,但是这种交叉操作会产生笛卡尔积,在数据比较大的时候,是非常消耗内存的操作;

示例

请将以下元组数据
(1, 4, 7), (2, 5, 8), (3, 6, 9)
元组数据
(10, 40, 70), (20, 50, 80), (30, 60, 90)
进行笛卡尔积,返回如下数据:
Buffer(((1,4,7),(10,40,70)), ((1,4,7),(20,50,80)), ((1,4,7),(30,60,90)), ((2,5,8),(10,40,70)), ((2,5,8),(20,50,80)), ((2,5,8),(30,60,90)), ((3,6,9),(10,40,70)), ((3,6,9),(20,50,80)), ((3,6,9),(30,60,90)))

参考代码

import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment} import org.apache.flink.api.scala._

/**
*
*通过形成这个数据集和其他数据集的笛卡尔积,创建一个新的数据集。
*
*/

object BatchCrossDemo {
def main(args: Array[String]): Unit = {
val env = ExecutionEnvironment.getExecutionEnvironment
println("============cross==================")
cross(env)
println("============cross2==================")
cross2(env)
println("============cross3==================")
cross3(env)
println("============crossWithTiny==================")
crossWithTiny(env)
println("============crossWithHuge==================")
} crossWithHuge(env)
/**
*
*@param benv
*交叉。拿第一个输入的每一个元素和第二个输入的每一个元素进行交叉操作。
*
*res71: Seq[((Int, Int, Int), (Int, Int, Int))] = Buffer(
*((1,4,7),(10,40,70)), ((2,5,8),(10,40,70)), ((3,6,9),(10,40,70)),
*((1,4,7),(20,50,80)), ((2,5,8),(20,50,80)), ((3,6,9),(20,50,80)),
*((1,4,7),(30,60,90)), ((2,5,8),(30,60,90)), ((3,6,9),(30,60,90)))
*/
def cross(benv: ExecutionEnvironment): Unit = {
//1.定义两个DataSet
val coords1 = benv.fromElements((1, 4, 7), (2, 5, 8), (3, 6, 9))
val coords2 = benv.fromElements((10, 40, 70), (20, 50, 80), (30, 60, 90))
//2.交叉两个DataSet[Coord]
val result1 = coords1.cross(coords2)
//3.显示结果
} println(result1.collect)
/**
*
*@param benv
*res69: Seq[(Coord, Coord)] = Buffer(
*(Coord(1,4,7),Coord(10,40,70)), (Coord(2,5,8),Coord(10,40,70)), (Coord(3,6,9),Coord(10,40,70)),
*(Coord(1,4,7),Coord(20,50,80)), (Coord(2,5,8),Coord(20,50,80)), (Coord(3,6,9),Coord(20,50,80)),
*(Coord(1,4,7),Coord(30,60,90)), (Coord(2,5,8),Coord(30,60,90)), (Coord(3,6,9),Coord(30,60,90)))
*/

def cross2(benv: ExecutionEnvironment): Unit = { //1.定义 case class
case class Coord(id: Int, x: Int, y: Int)
//2.定义两个DataSet[Coord]
val coords1: DataSet[Coord] = benv.fromElements( Coord(1, 4, 7),
Coord(2, 5, 8), Coord(3, 6, 9))
val coords2: DataSet[Coord] = benv.fromElements( Coord(10, 40, 70),
Coord(20, 50, 80),
Coord(30, 60, 90))
//3.交叉两个DataSet[Coord]
val result1 = coords1.cross(coords2)
//4.显示结果
} println(result1.collect)
/**
*
*@param benv
*
*res65: Seq[(Int, Int, Int)] = Buffer(
*(1,1,22), (2,1,24), (3,1,26),
*(1,2,24), (2,2,26), (3,2,28),
*(1,3,26), (2,3,28), (3,3,30))
*/
def cross3(benv: ExecutionEnvironment): Unit = { //1.定义 case class
case class Coord(id: Int, x: Int, y: Int)
//2.定义两个DataSet[Coord]
val coords1: DataSet[Coord] = benv.fromElements( Coord(1, 4, 7),
Coord(2, 5, 8),
Coord(3, 6, 9))
val coords2: DataSet[Coord] = benv.fromElements( Coord(1, 4, 7),
Coord(2, 5, 8),
Coord(3, 6, 9))
//3.交叉两个DataSet[Coord],使用自定义方法
val r = coords1.cross(coords2) { (c1, c2) => { val dist = (c1.x + c2.x) + (c1.y + c2.y)
} (c1.id, c2.id, dist)
}

//4.显示结果
} println(r.collect)
/**
*
*暗示第二个输入较小的交叉。
*
*拿第一个输入的每一个元素和第二个输入的每一个元素进行交叉操作。
*
*@param benv
*
*res67: Seq[(Coord, Coord)] = Buffer(
*
*(Coord(1,4,7),Coord(10,40,70)), (Coord(1,4,7),Coord(20,50,80)), (Coord(1,4,7),Coord(30,60,90)),
*
*(Coord(2,5,8),Coord(10,40,70)), (Coord(2,5,8),Coord(20,50,80)), (Coord(2,5,8),Coord(30,60,90)),
*
*(Coord(3,6,9),Coord(10,40,70)), (Coord(3,6,9),Coord(20,50,80)), (Coord(3,6,9),Coord(30,60,90)))
*
*/
def crossWithTiny(benv: ExecutionEnvironment): Unit = {
//1.定义 case class
case class Coord(id: Int, x: Int, y: Int)
//2.定义两个DataSet[Coord]
val coords1: DataSet[Coord] = benv.fromElements( Coord(1, 4, 7),
Coord(2, 5, 8),
Coord(3, 6, 9))
val coords2: DataSet[Coord] = benv.fromElements( Coord(10, 40, 70),
Coord(20, 50, 80),
Coord(30, 60, 90))
//3.交叉两个DataSet[Coord],暗示第二个输入较小
val result1 = coords1.crossWithTiny(coords2)
//4.显示结果
} println(result1.collect)
/**
*
*
*
*@param benv
*
*暗示第二个输入较大的交叉。
*
*拿第一个输入的每一个元素和第二个输入的每一个元素进行交叉操作。
*res68: Seq[(Coord, Coord)] = Buffer(
*(Coord(1,4,7),Coord(10,40,70)), (Coord(2,5,8),Coord(10,40,70)), (Coord(3,6,9),Coord(10,40,70)),
*(Coord(1,4,7),Coord(20,50,80)), (Coord(2,5,8),Coord(20,50,80)), (Coord(3,6,9),Coord(20,50,80)),
*(Coord(1,4,7),Coord(30,60,90)), (Coord(2,5,8),Coord(30,60,90)), (Coord(3,6,9),Coord(30,60,90)))
*
*/
def crossWithHuge(benv: ExecutionEnvironment): Unit = { //1.定义 case class
case class Coord(id: Int, x: Int, y: Int)
//2.定义两个DataSet[Coord]
val coords1: DataSet[Coord] = benv.fromElements( Coord(1, 4, 7),
Coord(2, 5, 8),
Coord(3, 6, 9))
val coords2: DataSet[Coord] = benv.fromElements( Coord(10, 40, 70),
Coord(20, 50, 80),
Coord(30, 60, 90))
//3.交叉两个DataSet[Coord],暗示第二个输入较大 val result1 = coords1.crossWithHuge(coords2)
//4.显示结果
} println(result1.collect)

}

Union

将多个 DataSet 合并成一个 DataSet【注意】:union 合并的 DataSet 的类型必须是一致

示例

将以下数据进行取并集操作
数据集 1
“hadoop”, “hive”, “flume”
数据集 2
“hadoop”, “hive”, “spark”

步骤

  1. 构建批处理运行环境

  2. 使用 fromCollection 创建两个数据源

  3. 使用 union 将两个数据源关联在一起

  4. 打印测试

参考代码

import org.apache.flink.api.scala._

/**
* 将两个DataSet取并集,并不会进行去重。
*/
object BatchUnionDemo {
def main(args: Array[String]): Unit = {
val env = ExecutionEnvironment.getExecutionEnvironment // 使用`fromCollection`创建两个数据源
val wordDataSet1 = env.fromCollection(List("hadoop", "hive", "flume"))
val wordDataSet2 = env.fromCollection(List("hadoop", "hive", "spark"))
val wordDataSet3 = env.fromElements("hadoop")
val wordDataSet4 = env.fromElements("hadoop")
wordDataSet1.union(wordDataSet2).print()
wordDataSet3.union(wordDataSet4).print()
} }

Rebalance

Flink 也有数据倾斜的时候,比如当前有数据量大概 10 亿条数据需要处理,在处理过程中可能会
发生如图所示的状况:在这里插入图片描述
这个时候本来总体数据量只需要 10 分钟解决的问题,出现了数据倾斜,机器 1 上的任务需 要 4 个小时才能完成,那么其他 3 台机器执行完毕也要等待机器 1 执行完毕后才算整体将任务完成;所以在实际的工作中,出现这种情况比较好的解决方案就是本节课要讲解的—rebalance(内 部使用 round robin 方法将数据均匀打散。这对于数据倾斜时是很好的选择。)
在这里插入图片描述

步骤

  1. 构建批处理运行环境

  2. 使用 env.generateSequence 创建 0-100 的并行数据

  3. 使用 fiter 过滤出来 大于 8 的数字

  4. 使用 map 操作传入 RichMapFunction ,将当前子任务的 ID 和数字构建成一个元组

  5. 在 RichMapFunction 中可以使用 getRuntimeContext.getIndexOfThisSubtask 获取子任务序号

  6. 打印测试

举例

1:在不使用 rebalance 的情况下,观察每一个线程执行的任务特点

import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.scala.ExecutionEnvironment import org.apache.flink.api.scala._
/**
* 对数据集进行再平衡,重分区,消除数据倾斜
*
*/
object BatchRebalanceDemo {
def main(args: Array[String]): Unit = {
val env = ExecutionEnvironment.getExecutionEnvironment val ds = env.generateSequence(0, 100) val rebalanced = ds.filter(_ > 8)
//val rebalanced = skewed.rebalance()
val countsInPartition = rebalanced.map(new RichMapFunction[Long, (Int, Long)] {
def map(in: Long) = {
//获取并行时子任务的编号getRuntimeContext.getIndexOfThisSubtask
})} (getRuntimeContext.getIndexOfThisSubtask, in)
countsInPartition.print()
}}

2:使用 rebalance

import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.scala.ExecutionEnvironment import org.apache.flink.api.scala._
/**
*
* 对数据集进行再平衡,重分区,消除数据倾斜
*
*/

object BatchDemoRebalance2 {
def main(args: Array[String]): Unit = {
val env = ExecutionEnvironment.getExecutionEnvironment //TODO rebalance
val ds = env.generateSequence(1, 3000)
val skewed = ds.filter(_ > 780)
val rebalanced = skewed.rebalance()
val countsInPartition = rebalanced.map( new RichMapFunction[Long, (Int, Long)] {
def map(in: Long) = {
//获取并行时子任务的编号
getRuntimeContext.getIndexOfThisSubtask
})} (getRuntimeContext.getIndexOfThisSubtask, in)
} } countsInPartition.print()

First

根据给定的 key 对一个数据集取前 N 条数据

步骤

  1. 构建批处理运行环境

  2. 使用 fromCollection 构建测试数据集

  3. 使用 first 获取前 N 条数据

  4. 打印测试

参考代码

import org.apache.flink.api.common.operators.Order import org.apache.flink.api.scala.ExecutionEnvironment import scala.collection.mutable.ListBuffer object BatchFirstNDemo {
def main(args: Array[String]): Unit = {
val env=ExecutionEnvironment.getExecutionEnvironment import org.apache.flink.api.scala._
val data = ListBuffer[Tuple2[Int,String]]()
data.append((2,"zs"))
data.append((4,"ls"))
data.append((3,"ww"))
data.append((1,"xw"))
data.append((1,"aw"))
data.append((1,"mw"))
val text = env.fromCollection(data)
//获取前3条数据,按照数据插入的顺序
text.first(3).print()
println("==============================")
//根据数据中的第一列进行分组,获取每组的前2个元素
text.groupBy(0).first(2).print()
println("==============================")
//根据数据中的第一列分组,再根据第二列进行组内排序[升序],获取每组的前2个元素
text.groupBy(0).sortGroup(1,Order.ASCENDING).first(2).print()
println("==============================")
//不分组,全局排序获取集合中的前3个元素,
text.sortPartition(0,Order.ASCENDING).sortPartition(1,Order.DESCENDIN G).first(3).print()
}}

数据输出 Data Sinks

flink 在批处理中常见的 sink:

  1. 基于本地集合的 sink(Collection-based-sink)

  2. 基于文件的 sink(File-based-sink)

基于本地集合的 sink(Collection-based-sink)

import org.apache.flink.api.scala.ExecutionEnvironment import org.apache.flink.api.scala._

import org.apache.flink.core.fs.FileSystem.WriteMode 
/**
*基于本地集合的 sink */
object BatchSinkCollection {
def main(args: Array[String]): Unit = {
val environment = ExecutionEnvironment.getExecutionEnvironment val textDataSet: DataSet[(Int, String, Double)] =
environment.fromElements(
(19, "zhangsan", 178.8),
(17, "lisi", 168.8),
(18, "wangwu", 184.8),
) (21, "zhaoliu", 164.8)
textDataSet.print()
textDataSet.printToErr()
print(textDataSet.collect())
} } environment.execute("BatchSinkCollection")

基于文件的 sink(File-based-sink)

flink 支持多种存储设备上的文件,包括本地文件,hdfs 文件等。
flink 支持多种文件的存储格式,包括 text 文件,CSV 文件等。
writeAsText():TextOuputFormat - 将元素作为字符串写入行。
字符串是通过调用每个元素的 toString()方法获得的。

将数据写入本地文件

import org.apache.flink.api.scala.ExecutionEnvironment import org.apache.flink.api.scala._

import org.apache.flink.core.fs.FileSystem.WriteMode /**

* 基于BatchSinkFile的 sink
*/
object BatchSinkFile {
def main(args: Array[String]): Unit = {
val environment = ExecutionEnvironment.getExecutionEnvironment val textDataSet: DataSet[(Int, String, Double)] =
environment.fromElements(
(19, "zhangsan", 178.8),
(17, "lisi", 168.8),
(18, "wangwu", 184.8),
) (21, "zhaoliu", 164.8)
textDataSet.setParallelism(1).writeAsText("day01/data/output/BatchSin kCollection",WriteMode.OVERWRITE)
environment.execute("BatchSinkFile")
} }

将数据写入 HDFS

import org.apache.flink.api.scala.ExecutionEnvironment import org.apache.flink.api.scala._
import org.apache.flink.core.fs.FileSystem.WriteMode 
/**
*基于HDFSFile的 sink 
*/
object BatchSinkHDFSFile {
def main(args: Array[String]): Unit = {
val environment = ExecutionEnvironment.getExecutionEnvironment val textDataSet: DataSet[(Int, String, Double)] =
environment.fromElements(
(19, "zhangsan", 178.8),
(17, "lisi", 168.8),
(18, "wangwu", 184.8),
) (21, "zhaoliu", 164.8)
textDataSet.setParallelism(1).writeAsText("hdfs://node01:8020/test/ou tput/sinkhdfsfile"",WriteMode.OVERWRITE)
} } environment.execute("BatchSinkHDFSFile")

Flink 的广播变量

Flink 支持广播变量,就是将数据广播到具体的 taskmanager 上,数据存储在内存中,这样可以减缓大量的 shuffle 操作; 比如在数据 join 阶段,不可避免的就是大量的 shuffle 操作,我们可以把其中一个 dataSet 广播出去,一直加载到 taskManager 的内存中,可以直接在内存中拿数据,避免了大量的 shuffle, 导致集群性能下降; 广播变量创建后,它可以运行在集群中的任何 function 上,而不需要多次传递给集群节点。另外需要记住,不应该修改广播变量,这样才能确保每个节点获取到的值都是一致的。

一句话解释,可以理解为是一个公共的共享变量,我们可以把一个 dataset 数据集广播出去, 然后不同的 task 在节点上都能够获取到,这个数据在每个节点上只会存在一份。如果不使用 broadcast,则在每个节点中的每个 task 中都需要拷贝一份 dataset 数据集,比较浪费内存(也 就是一个节点中可能会存在多份 dataset 数据)。
注意:因为广播变量是要把 dataset 广播到内存中,所以广播的数据量不能太大,否则会出现 OOM 这样的问题。

  • Broadcast:Broadcast 是通过 withBroadcastSet(dataset,string)来注册的

  • Access:通过 getRuntimeContext().getBroadcastVariable(String)访问广播变量
    在这里插入图片描述

  • 可以理解广播就是一个公共的共享变量

  • 将一个数据集广播后,不同的 Task 都可以在节点上获取到

  • 每个节点 只存一份

  • 如果不使用广播,每一个 Task 都会拷贝一份数据集,造成内存资源浪费

用法

在需要使用广播的操作后,使用 withBroadcastSet 创建广播
在操作中,使用 getRuntimeContext.getBroadcastVariable [广播数据类型] ( 广播名 ) 获取广播变量

示例

创建一个 学生 数据集,包含以下数据

|学生  ID | 姓名  |
|------|------|
List((1, "张三 "), (2, "李四 "), (3, "王五 "))

将该数据,发布到广播。
再创建一个 成绩 数据集,

|学生  ID | 学科  | 成绩  |
|------|------|-----|
List( (1, "语文 ", 50),(2, "数学 ", 70), (3, "英文 ", 86))

请通过广播获取到学生姓名,将数据转换为

步骤

  1. 获取批处理运行环境

  2. 分别创建两个数据集

  3. 使用 RichMapFunction 对 成绩 数据集进行 map 转换

  4. 在数据集调用 map 方法后,调用 withBroadcastSet 将 学生 数据集创建广播

  5. 实现 RichMapFunction

    5.a.将成绩数据(学生 ID,学科,成绩) -> (学生姓名,学科,成绩)

    5.b.重写 open 方法中,获取广播数据

    5.c.导入 scala.collection.JavaConverters._ 隐式转换

    5.d. 将广播数据使用 asScala 转换为 Scala 集合,再使用 toList 转换为 scala List
    集合

    5.e. 在 map 方法中使用广播进行转换

  6. 打印测试

参考代码

import java.util
import org.apache.flink.api.common.functions.RichMapFunction import org.apache.flink.api.scala._
import org.apache.flink.configuration.Configuration
/**
*需求:
*创建一个 学生数据集,包含以下数据
*|学生ID | 姓名|
*|------|------|
*List((1, "张三"), (2, "李四"), (3, "王五"))
*再创建一个 成绩数据集,
*|学生ID | 学科| 成绩|
*|------|------|-----|
*List( (1, "语文", 50),(2, "数学", 70), (3, "英文", 86))
*请通过广播获取到学生姓名,将数据转换为
* List( ("张三", "语文", 50),("李四", "数学", 70), ("王五", "英文", 86))
*/
object BatchBroadcastDemo {
def main(args: Array[String]): Unit = { /**
*1. 获取批处理运行环境
*2. 分别创建两个数据集
*3. 使用RichMapFunction 对成绩数据集进行map转换
*4. 在数据集调用map 方法后,调用withBroadcastSet 将学生数据集创建广播
*5. 实现RichMapFunction
*将成绩数据(学生ID,学科,成绩) -> (学生姓名,学科,成绩)
*重写 open 方法中,获取广播数据
*导入 scala.collection.JavaConverters._ 隐式转换
*将广播数据使用asScala 转换为Scala集合,再使用toList转换为scala List 集合
*在map 方法中使用广播进行转换
*6. 打印测试
*/
//1. 获取批处理运行环境
val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
//2. 分别创建两个数据集
//创建学生数据集
val stuDataSet: DataSet[(Int, String)] = env.fromCollection(
List((1, "张三"), (2, "李四"), (3, "王五"))
)
//创建成绩数据集
val socreDataSet: DataSet[(Int, String, Int)] = env.fromCollection(
List( (1, "语文", 50),(2, "数学", 70), (3, "英文", 86))
)
//3. 使用RichMapFunction 对成绩数据集进行map转换
//返回值类型(学生名字,学科成名,成绩)
val result: DataSet[(String, String, Int)] = socreDataSet.map( new RichMapFunction[(Int, String, Int), (String, String, Int)] {
//定义获取学生数据集的集合
var studentMap:Map[Int, String] = null
//初始化的时候被执行一次,在对象的生命周期中只被执行一次 
override def open(parameters: Configuration): Unit = {
//因为获取到的广播变量中的数据类型是java的集合类型,但是我们的代码是scala因此需要将java的集合转换成scala的集合
//我们这里将list转换成了map对象,之所以能够转换是因为list中的元素是对偶元祖,因此可以转换成kv键值对类型
//之所以要转换,是因为后面好用,传递一个学生id,可以直接获取到学生的名字
import scala.collection.JavaConversions._
val studentList: util.List[(Int, String)] = getRuntimeContext.getBroadcastVariable[(Int, String)]("student")
studentMap = studentList.toMap

//要对集合中的每个元素执行map操作,也就是说集合中有多少元素,就被执行多少次
override def map(value: (Int, String, Int)): (String, String, Int)= {
//(Int, String, Int)=》(学生id,学科名字,学生成绩)
//返回值类型(学生名字,学科成名,成绩)
val stuId = value._1
val stuName = studentMap.getOrElse(stuId, "")
//(学生名字,学科成名,成绩)
(stuName, value._2, value._3)
}
}).withBroadcastSet(stuDataSet, "student")
} result.print()
}

Flink 的分布式缓存

Flink 提供了一个类似于 Hadoop 的分布式缓存,让并行运行实例的函数可以在本地访问。这 个功能可以被使用来分享外部静态的数据,例如:机器学习的逻辑回归模型等!缓存的使用流程:

使用 ExecutionEnvironment 实例对本地的或者远程的文件(例如:HDFS 上的文件),为缓存 文件指定一个名字注册该缓存文件!当程序执行时候,Flink 会自动将复制文件或者目录到所有 worker 节点的本地文件系统中,函数可以根据名字去该节点的本地文件系统中检索该文件!

【注意】广播是将变量分发到各个 worker 节点的内存上,分布式缓存是将文件缓存到各个 worker 节点上;

用法

使用 Flink 运行时环境的 registerCachedFile 注册一个分布式缓存
在操作中,使用 getRuntimeContext.getDistributedCache.getFile ( 文件名 )获取分布式缓存

示例

创建一个 成绩 数据集

List( (1, "语文 ", 50),(2, "数学 ", 70), (3, "英文 ", 86))

请通过分布式缓存获取到学生姓名,将数据转换为

List( ("张三 ", "语文 ", 50),("李四 ", "数学 ", 70), ("王五 ", "英文 ", 86))

注: distribute_cache_student 测试文件保存了学生 ID 以及学生姓名
操作步骤:

  1. 将 distribute_cache_student 文件上传到 HDFS / 目录下

  2. 获取批处理运行环境

  3. 创建成绩数据集

  4. 对 成绩 数据集进行 map 转换,将(学生 ID, 学科, 分数)转换为(学生姓名,学科,分数)
    4.a.RichMapFunction 的 open 方法中,获取分布式缓存数据

    4.b.在 map 方法中进行转换

  5. 实现 open 方法

    5.a.使用 getRuntimeContext.getDistributedCache.getFile 获取分布式缓存文件

    5.b.使用 Scala.fromFile 读取文件,并获取行

    5.c.将文本转换为元组(学生 ID,学生姓名),再转换为 List

  6. 实现 map 方法

    6.a.从分布式缓存中根据学生 ID 过滤出来学生

    6.b.获取学生姓名

    6.c.构建最终结果元组

  7. 打印测试

参考代码

import java.io.File

import org.apache.flink.api.common.functions.RichMapFunction import org.apache.flink.api.scala._

import org.apache.flink.configuration.Configuration

import scala.io.Source

/**

*需求:

*创建一个 成绩 数据集

*List( (1, "语文", 50),(2, "数学", 70), (3, "英文", 86))

*请通过分布式缓存获取到学生姓名,将数据转换为

*List( ("张三", "语文", 50),("李四", "数学", 70), ("王五", "英文", 86))

*注: distribute_cache_student 测试文件保存了学生 ID 以及学生姓名

*/

object BatchDisCachedFile {

def main(args: Array[String]): Unit = {

/**

*实现步骤:
*1) 将 distribute_cache_student 文件上传到 HDFS /test/input/ 目录下
*2) 获取批处理运行环境
*3) 创建成绩数据集
*4) 对成绩 数据集进行 map 转换,将(学生 ID, 学科, 分数)转换为(学生姓名,学科,分数)
*a. RichMapFunction 的 open 方法中,获取分布式缓存数据
*b. 在 map 方法中进行转换
*5) 实现 open 方法
*a. 使用 getRuntimeContext.getDistributedCache.getFile 获取分布式缓存文件
*b. 使用 Scala.fromFile 读取文件,并获取行
*c. 将文本转换为元组(学生 ID,学生姓名),再转换为 List
*6) 实现 map 方法
*a. 从分布式缓存中根据学生 ID 过滤出来学生
*b. 获取学生姓名
*c. 构建最终结果元组
*7) 打印测试

*/

//获取批处理运行环境
val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
//注册一个分布式缓存
env.registerCachedFile("hdfs://node01:8020/test/input/distribute_cache_student", "student")
//创建成绩数据集
val scoreDataSet: DataSet[(Int, String, Int)] = env.fromCollection(List((1, "
语文", 50), (2, "数学", 70), (3, "英文", 86)))
val resultDataSet: DataSet[(String, String, Int)] = scoreDataSet.map(new RichMapFunction[(Int, String, Int), (String, String, Int)] {
var studentMap: Map[Int, String] = null
//初始化的时候之被调用一次
override def open(parameters: Configuration): Unit = { //获取分布式缓存的文件
val studentFile: File =
getRuntimeContext.getDistributedCache.getFile("student")
val linesIter: Iterator[String] = Source.fromFile(studentFile).getLines() studentMap = linesIter.map(lines => {
val words: Array[String] = lines.split(",")
(words(0).toInt, words(1))
}).toMap

}
override def map(value: (Int, String, Int)): (String, String, Int) = {
val stuName: String = studentMap.getOrElse(value._1, "")
(stuName, value._2, value._3)

}
})
//输出打印测试
resultDataSet.print()
}
}

Flink Accumulators & Counters(了解)

Accumulator 即累加器,与 Mapreduce counter 的应用场景差不多,都能很好地观察 task 在运行期间的数据变化 可以在 Flink job 任务中的算子函数中操作累加器,但是只能在任务执行结束之后才能获得累加器的最终结果。

Counter 是一个具体的累加器 (Accumulator) 实现 IntCounter, LongCounter 和 DoubleCounter

步骤

  1. 创建累加器
private IntCounter numLines = new IntCounter();
  1. 注册累加器
getRuntimeContext().addAccumulator("num-lines", this.numLines);
  1. 使用累加器
this.numLines.add(1);
  1. 获取累加器的结果
 myJobExecutionResult.getAccumulatorResult("num-lines")

示例:

需求:
给定一个数据源
"a","b","c","d"
通过累加器打印出多少个元素

参考代码

package cn.czxy.batch
import org.apache.flink.api.common.JobExecutionResult import org.apache.flink.api.common.accumulators.IntCounter
import org.apache.flink.api.common.functions.RichMapFunction import org.apache.flink.api.scala._
import org.apache.flink.configuration.Configuration import org.apache.flink.core.fs.FileSystem.WriteMode
/**
* 需求:
* 给定一个数据源
* "a","b","c","d"
* 通过累加器打印出多少个元素
*/
object BatchCounterDemo {
def main(args: Array[String]): Unit = {
//1.创建执行环境
val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
//2.创建数据源
val sourceDataSet: DataSet[String] = env.fromElements("a", "b", "c", "d")
//3.对sourceDataSet 进行map操作
val resultDataSet: DataSet[String] = sourceDataSet.map(new RichMapFunction[String, String] {
//创建累加器
val counter: IntCounter = new IntCounter
//初始化的时候被执行一次
override def open(parameters: Configuration): Unit = {
//注册累加器
getRuntimeContext.addAccumulator("MyAccumulator", this.counter)
} 

//每条数据都会被执行一次
override def map(value: String): String = { counter.add(1)
value
})}

//resultDataSet.print()
resultDataSet.writeAsText("day01/data/output/Accumulators",WriteMode. OVERWRITE)
val result: JobExecutionResult = env.execute("BatchCounterDemo")
val MyAccumulatorValue: Int =result.getAccumulatorResult[Int]("MyAccumulator")
print("累加器的值:"+MyAccumulatorValue)
} }

Flink Broadcast 和 Accumulators 的区别:

Broadcast(广播变量)允许程序员将一个只读的变量缓存在每台机器上,而不用在任务之间传递变量。广播变量可以进行共享,但是不可以进行修改Accumulators(累加器)是可以在不同任务中对同一个变量进行累加操作。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

weixin_43563705

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

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

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

打赏作者

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

抵扣说明:

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

余额充值