IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> flink流处理 -> 正文阅读

[大数据]flink流处理

<?xml version="1.0" encoding="UTF-8"?>

-<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">


-<parent>

<artifactId>flink_warehouse</artifactId>

<groupId>com.kaikeba.flink</groupId>

<version>1.0-SNAPSHOT</version>

</parent>

<modelVersion>4.0.0</modelVersion>

<artifactId>flink_study</artifactId>


-<repositories>


-<repository>

<id>cloudera</id>

<url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>

</repository>

</repositories>


-<dependencies>

<!-- https://mvnrepository.com/artifact/org.apache.flink/flink-streaming-scala -->



-<dependency>

<groupId>org.apache.flink</groupId>

<artifactId>flink-streaming-scala_2.11</artifactId>

<version>1.8.1</version>

<!-- <scope>provided</scope>-->


</dependency>


-<dependency>

<groupId>org.apache.flink</groupId>

<artifactId>flink-scala_2.11</artifactId>

<version>1.8.1</version>

<!-- <scope>provided</scope>-->


</dependency>

<!-- 使用cdh版本的软件 与我们生产环境保持一致开课吧电话:400-996-0826kaikeba.com -->



-<dependency>

<groupId>org.apache.hadoop</groupId>

<artifactId>hadoop-client</artifactId>

<version>2.6.0-mr1-cdh5.14.2</version>

</dependency>


-<dependency>

<groupId>org.apache.hadoop</groupId>

<artifactId>hadoop-common</artifactId>

<version>2.6.0-cdh5.14.2</version>

</dependency>


-<dependency>

<groupId>org.apache.hadoop</groupId>

<artifactId>hadoop-hdfs</artifactId>

<version>2.6.0-cdh5.14.2</version>

</dependency>


-<dependency>

<groupId>org.apache.hadoop</groupId>

<artifactId>hadoop-mapreduce-client-core</artifactId>

<version>2.6.0-cdh5.14.2</version>

</dependency>

</dependencies>


-<build>


-<plugins>

<!-- 限制jdk版本插件 -->



-<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<version>3.0</version>


-<configuration>

<source>1.8</source>

<target>1.8</target>

<encoding>UTF-8</encoding>

</configuration>

</plugin>

<!-- 编译scala需要用到的插件 -->



-<plugin>

<groupId>net.alchim31.maven</groupId>

<artifactId>scala-maven-plugin</artifactId>

<version>3.2.2</version>


-<executions>


-<execution>


-<goals>

<goal>compile</goal>

<goal>testCompile</goal>

</goals>

</execution>

</executions>

</plugin>

<!-- 项目打包用到的插件 -->



-<plugin>

<artifactId>maven-assembly-plugin</artifactId>


-<configuration>


-<descriptorRefs>

<descriptorRef>jar-with-dependencies</descriptorRef>

</descriptorRefs>


-<archive>


-<manifest>

<mainClass/>

</manifest>

</archive>

</configuration>


-<executions>


-<execution>

<id>make-assembly</id>

<phase>package</phase>


-<goals>

<goal>single</goal>

</goals>

</execution>

</executions>

</plugin>

</plugins>

</build>

</project>
import org.apache.flink.api.scala.ExecutionEnvironment
import org.apache.flink.core.fs.FileSystem.WriteMode

object BatchOperate {
  //实现单词计数统计
  def main(args: Array[String]): Unit = {
    //获取程序入口类
    val environment: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment

    //需要导入隐式转换的包
    import org.apache.flink.api.scala._

    //读取数据
    val sourceDatas: DataSet[String] = environment.readTextFile("D:\\开课吧课程资料\\Flink实时数仓\\数据\\datas\\count.txt","UTF-8")
    //实现单词计数统计
    val resultCount: AggregateDataSet[(String, Int)] = sourceDatas.flatMap(x => x.split(" "))
      .map(x => (x, 1)) //将每个出现的单词记做1
      .groupBy(0) //按照哪一个下表进行分组
      .sum(1)//对我们的单词进行统计求和
   // resultCount.print()
    resultCount.writeAsText("D:\\开课吧课程资料\\Flink实时数仓\\数据\\datas\\count_result.txt",WriteMode.OVERWRITE)
    environment.execute()  //提交任务
  }
}


import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}

/**
  * 通过flink的程序,从socket里面接受数据,然后将数据进行单词计数统计
  */
object StreamSocket {
  def main(args: Array[String]): Unit = {
    //如果需要实现流式计算,需要获取flink的程序的入口类 StreamExecutionEnvironment
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    //注意:如果需要执行流式处理,需要导入隐式转换的
    import org.apache.flink.api.scala._
    //接受socket里面的数据
    val sockeLine: DataStream[String] = environment.socketTextStream("node01",9000)
    //实现单词计数统计
    val result: DataStream[(String, Int)] = sockeLine.flatMap(x => x.split(" ")) //将我们的单词按照空格进行切割,然后压平
      .map(x => (x, 1)) //将每一个单词出现的次数记做1 次
      .keyBy(0) //对我们的数据按下表为0的位置,进行分组
      .sum(1)//分组之后,对我们的数据进行统计求和
    result.print()  //最终通过print方法来触发整个任务执行

    //提交任务
    environment.execute()


  }

}


import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}

object FileSource {
  /**
    * 从hdfs读取文件数据,统计单词出现的次数,将结果,写入到hdfs文件里面去
    */
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    import  org.apache.flink.api.scala._
    //通过输入路径,读取这个路径下面所有的文件
    val sourceFile: DataStream[String] = environment.readTextFile("hdfs://node01:8020/flink_input")
    //统计我们的结果
    val result: DataStream[(String, Int)] = sourceFile.flatMap(x =>x.split(" ")).map((_,1)).keyBy(0).sum(1)


    result.writeAsText("hdfs://node01:8020/out_file").setParallelism(1)

    environment.execute()

  }


}


import org.apache.flink.streaming.api.functions.source.{ParallelSourceFunction, SourceFunction}
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}

object MyOwnSource {

  //通过自定义数据源,来实现数据源接收数据
  def main(args: Array[String]): Unit = {

    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //导入隐式转换的包
    import  org.apache.flink.api.scala._
    val sourceStream: DataStream[String] = environment.addSource(new MySource)

    val result: DataStream[(String, Int)] = sourceStream.flatMap(x =>x.split(" ")).map(x =>(x,1)).keyBy(0).sum(1)
    result.print()
    environment.execute("myOwnSource")
  }
}


class MySource extends ParallelSourceFunction[String]{
  //定义全局的变量
  var  isRunning:Boolean = true

  /**
    * 最核心的方法,这个方法主要是用于获取数据
    * @param sourceContext
    */
  override def run(sourceContext: SourceFunction.SourceContext[String]): Unit = {
    //表示程序一直在运行
    while(isRunning){
      //通过soruceContext调用collect来进行发送数据
      sourceContext.collect("hello spark")
      Thread.sleep(1000)
    }
  }

  /**
    * 判断如果flink的程序停止了,那么就不用再继续发送数据了
    */
  override def cancel(): Unit = {
    isRunning=false

  }
}


import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.windowing.time.Time

/**
  * 统计最近5S钟出现的单词的次数
  */
object SocektSource {

  def main(args: Array[String]): Unit = {
    //第一步:获取程序入口类
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    //导入隐式转换的包
    import org.apache.flink.api.scala._

    //第二步:读取socket的数据
    val socketStream: DataStream[String] = environment.socketTextStream("node01",9000)


    //第三步:统计最近5S钟出现单词的次数
    val result: DataStream[(String, Int)] = socketStream.flatMap(x => x.split(" ")).map(x => (x, 1)).keyBy(0)
      .timeWindow(Time.seconds(5), Time.seconds(5))
      .sum(1)
    result.print().setParallelism(1)  //设置打印的并行度为1

    //第四步:提交任务
    environment.execute("socketStream")


  }

}
Flink实战
1、Flink基本介绍
1、Flink介绍
Flink起源于一个名为Stratosphere的研究项目,目的是建立下一代大数据分析平台,于2014年4月16日成为Apache孵化器项目。
Apache Flink是一个面向数据流处理和批量数据处理的可分布式的开源计算框架,它基于同一个Flink流式执行模型(streaming execution model),能够支持流处理和批处理两种应用类型。由于流处理和批处理所提供的SLA(服务等级协议)是完全不相同, 流处理一般需要支持低延迟、Exactly-once保证,而批处理需要支持高吞吐、高效处理,所以在实现的时候通常是分别给出两套实现方法,或者通过一个独立的开源框架来实现其中每一种处理方案。比较典型的有:实现批处理的开源方案有MapReduce、Spark;实现流处理的开源方案有Storm;Spark的Streaming 其实本质上也是微批处理。
Flink在实现流处理和批处理时,与传统的一些方案完全不同,它从另一个视角看待流处理和批处理,将二者统一起来:Flink是完全支持流处理,也就是说作为流处理看待时输入数据流是无界的;批处理被作为一种特殊的流处理,只是它的输入数据流被定义为有界的。
2、Flink特性
1:有状态计算的Exactly-once语义。状态是指flink能够维护数据在时序上的聚类和聚合,同时它的checkpoint机制
2:支持带有事件时间(event time)语义的流处理和窗口处理。事件时间的语义使流计算的结果更加精确,尤其在事件到达无序或者延迟的情况下。
3:支持高度灵活的窗口(window)操作。支持基于time、count、session,以及data-driven的窗口操作,能很好的对现实环境中的创建的数据进行建模。
4:轻量的容错处理( fault tolerance)。 它使得系统既能保持高的吞吐率又能保证exactly-once的一致性。通过轻量的state snapshots实现
5:支持高吞吐、低延迟、高性能的流处理
6:支持savepoints 机制(一般手动触发)。即可以将应用的运行状态保存下来;在升级应用或者处理历史数据是能够做到无状态丢失和最小停机时间。
7:支持大规模的集群模式,支持yarn、Mesos。可运行在成千上万的节点上
8:支持具有Backpressure功能的持续流模型
9:Flink在JVM内部实现了自己的内存管理
10:支持迭代计算
11:支持程序自动优化:避免特定情况下Shuffle、排序等昂贵操作,中间结果进行缓存

3、Flink功能模块

?  Deployment层: 该层主要涉及了Flink的部署模式,Flink支持多种部署模式:本地、集群(Standalone/YARN),(GCE/EC2)。 
?  Runtime层:Runtime层提供了支持Flink计算的全部核心实现,比如:支持分布式Stream处理、JobGraph到ExecutionGraph的映射、调度等等,为上层API层提供基础服务。 
?  API层: 主要实现了面向无界Stream的流处理和面向Batch的批处理API,其中面向流处理对应DataStream API,面向批处理对应DataSet API。 
?  Libraries层:该层也可以称为Flink应用框架层,根据API层的划分,在API层之上构建的满足特定应用的实现计算框架,也分别对应于面向流处理和面向批处理两类。面向流处理支持:CEP(复杂事件处理)、基于SQL-like的操作(基于Table的关系操作);面向批处理支持:FlinkML(机器学习库)、Gelly(图处理)

4、Flink编程模型

?  有状态的数据流处理层。最底层的抽象仅仅提供有状态的数据流,它通过处理函数(Process Function)嵌入到数据流api(DataStream API). 用户可以通过它自由的处理单流或者多流,并保持一致性和容错。同时用户可以注册事件时间和处理时间的回调处理,以实现复杂的计算逻辑。
?  核心API层。 它提供了数据处理的基础模块,像各种transformation, join,aggregations,windows,stat 以及数据类型等等
?  Table API层。 定了围绕关系表的DSL(领域描述语言)。Table API遵循了关系模型的标准:Table类型关系型数据库中的表,API也提供了相应的操作,像select,project,join,group-by,aggregate等。Table API声明式的定义了逻辑上的操作(logical operation)不是code for the operation;Flink会对Table API逻辑在执行前进行优化。同时代码上,Flink允许混合使用Table API和DataStram/DataSet API
?  SQL层。 它很类似Table API的语法和表达,也是定义与Table API层次之上的,但是提供的是纯SQL的查询表达式。
2、Flink重新编译
由于实际生产环境当中,我们一般都是使用基于CDH的大数据软件组件,因此我们Flink也会选择基于CDH的软件组件,但是由于CDH版本的软件并没有对应的Flink这个软件安装包,所以我们可以对开源的Flink进行重新编译,然后用于适配我们对应的CDH版本的hadoop
第一步:准备工作
安装maven3版本及以上:省略
安装jdk1.8:省略
第二步:下载flink源码包
cd /kkb/soft
wget http://archive.apache.org/dist/flink/flink-1.8.1/flink-1.8.1-src.tgz
tar -zxf flink-1.8.1-src.tgz -C /kkb/install/
cd /kkb/install/flink-1.8.1/
mvn -T2C clean install -DskipTests -Dfast -Pinclude-hadoop -Pvendor-repos -Dhadoop.version=2.6.0-cdh5.14.2

编译成功之后的文件夹目录位于
/kkb/install/flink-1.8.1/flink-dist/target


3、Flink架构模型图


----------


Client:Flink 作业在哪台机器上面提交,那么当前机器称之为Client。用户开发的Program 代码,它会构建出DataFlow graph,然后通过Client提交给JobManager。
JobManager:是主(master)节点,相当于YARN里面的ResourceManager,生成环境中一般可以做HA 高可用。JobManager会将任务进行拆分,调度到TaskManager上面执行。
TaskManager:是从节点(slave),TaskManager才是真正实现task的部分。
Client提交作业到JobManager,就需要跟JobManager进行通信,它使用Akka框架或者库进行通信,另外Client与JobManager进行数据交互,使用的是Netty框架。Akka通信基于Actor System,Client可以向JobManager发送指令,比如Submit job或者Cancel /update job。JobManager也可以反馈信息给Client,比如status updates,Statistics和results。
Client提交给JobManager的是一个Job,然后JobManager将Job拆分成task,提交给TaskManager(worker)。JobManager与TaskManager也是基于Akka进行通信,JobManager发送指令,比如Deploy/Stop/Cancel Tasks或者触发Checkpoint,反过来TaskManager也会跟JobManager通信返回Task Status,Heartbeat(心跳),Statistics等。另外TaskManager之间的数据通过网络进行传输,比如Data Stream做一些算子的操作,数据往往需要在TaskManager之间做数据传输。
当Flink系统启动时,首先启动JobManager和一至多个TaskManager。JobManager负责协调Flink系统,TaskManager则是执行并行程序的worker。当系统以本地形式启动时,一个JobManager和一个TaskManager会启动在同一个JVM中。当一个程序被提交后,系统会创建一个Client来进行预处理,将程序转变成一个并行数据流的形式,交给JobManager和TaskManager执行。


4、Flink部署运行模式
类似于spark一样,flink也有各种运行模式,其中flink主要支持三大运行模式
第一种运行模式:local模式,适用于测试调试
Flink 可以运行在 Linux、Mac OS X 和 Windows 上。本地模式的安装唯一需要的只是Java 1.7.x或更高版本,本地运行会启动Single JVM,主要用于测试调试代码。一台服务器即可运行
第二种运行模式:standAlone模式,适用于flink自主管理资源
Flink自带了集群模式Standalone,主要是将资源调度管理交给flink集群自己来处理,standAlone是一种集群模式,可以有一个或者多个主节点JobManager(HA模式),用于资源管理调度,任务管理,任务划分等工作,多个从节点taskManager,主要用于执行JobManager分解出来的任务
第三种模式:flink  on yarn模式,适用于使用yarn来统一调度管理资源

Flink ON YARN工作流程如下所示:
首先提交job给YARN,就需要有一个Flink YARN Client。
第一步:Client将Flink 应用jar包和配置文件上传到HDFS。
第二步:Client向ResourceManager注册resources和请求APPMaster Container。
第三步:REsourceManager就会给某一个Worker节点分配一个Container来启动APPMaster,JobManager会在APPMaster中启动。
第四步:APPMaster为Flink的TaskManagers分配容器并启动TaskManager,TaskManager内部会划分很多个Slot,它会自动从HDFS下载jar文件和修改后的配置,然后运行相应的task。TaskManager也会与APPMaster中的JobManager进行交互,维持心跳等。
Flink的支持以上这三种部署模式,一般在学习研究环节,资源不充足的情况下,采用Local模式就行,生产环境中Flink ON YARN比较常见。

5、flink的部署安装
部署安装准备工作:关闭防火墙,关闭selinux,安装jdk,更改主机名,更改主机名与IP地址的映射关系,ssh免密码登录等
1、Flink的local模式部署安装
在local模式下,不需要启动任何的进程,仅仅是使用本地线程来模拟flink的进程,适用于测试开发调试等,这种模式下,不用更改任何配置,只需要保证jdk8安装正常即可
第一步:上传安装包并解压
将我们编译之后的压缩包,上传到node01服务器的/kkb/soft路径下,然后进行解压
cd /kkb/soft/
tar -zxf flink-1.8.1.tar.gz  -C /kkb/install/
第二步:直接使用脚本启动
flink在处于local模式下,不需要更改任何配置,直接解压之后启动即可
执行以下命令直接启动local模式
cd /kkb/install/flink-1.8.1
bin/start-cluster.sh
启动成功之后,执行jps就能查看到启动了两个进程
18180 StandaloneSessionClusterEntrypoint
18614 TaskManagerRunner
第三步:webUI界面访问
启动两个进程成功之后,访问8081端口号即可访问到flink的web管理界面
http://node01:8081/#/overview
第四步:运行flink自带的测试
node01使用linux的nc命令来向socket当中发送一些单词
sudo yum -y install nc
nc -lk 9000
node01启动flink的自带的单词统计程序,接受输入的socket数据并进行统计
cd /kkb/install/flink-1.8.1
bin/flink run examples/streaming/SocketWindowWordCount.jar   --hostname localhost  --port 9000
查看统计结果:
flink自带的测试用例统计结果在log文件夹下面
node01执行以下命令查看统计结果
cd /kkb/install/flink-1.8.1/log
tail -200f flink-hadoop-taskexecutor-0-node01.kaikeba.com.out
local模式运行成功之后,关闭local模式,我们接下来运行standAlone模式
cd /kkb/install/flink-1.8.1
bin/stop-cluster.sh
2、Flink的standAlone模式环境安装
使用standalone模式,需要启动flink的主节点JobManager以及从节点taskManager
服务以及ip	192.168.52.100	192.168.52.110	192.168.52.120
JobManager	是	否	否
TaskManager	是	是	是
第一步:更改配置文件
停止node01服务器上面local模式下的两个进程,然后修改node01服务器配置文件
node01服务器更改flink-conf.yaml配置文件文件
node01服务器执行以下命令更改flink配置文件
cd /kkb/install/flink-1.8.1/conf/
vim flink-conf.yaml
更改这个配置,指定jobmanager所在的服务器为node01
jobmanager.rpc.address: node01
node01服务器更改slaves配置文件
node01执行以下命令更改从节点slaves配置文件
cd /kkb/install/flink-1.8.1/conf
vim slaves

node01
node02
node03
第二步:安装包分发
将node01服务器的flink安装包分发到其他机器上面去
node01服务器执行以下命令分发安装包
cd /kkb/install
scp -r flink-1.8.1/ node02:$PWD
scp -r flink-1.8.1/ node03:$PWD
第三步:启动flink集群
node01执行以下命令启动flink集群
cd /kkb/install/flink-1.8.1
bin/start-cluster.sh
第四步:页面访问
http://node01:8081/#/overview
第五步:运行flink自带的测试用例
node01执行以下命令启动socket服务,输入单词
nc -lk 9000
node01启动flink的自带的单词统计程序,接受输入的socket数据并进行统计
cd /kkb/install/flink-1.8.1
bin/flink run examples/streaming/SocketWindowWordCount.jar   --hostname node01  --port 9000
node01服务器执行以下命令查看统计结果
cd /kkb/install/flink-1.8.1/log
tail -200f flink-hadoop-taskexecutor-0-node01.kaikeba.com.out
3、Flink的standAlone模式的HA环境
在上一节当中,我们实现了flink的standAlone模式的环境安装,并且能够正常提交任务到集群上面去,我们的主节点是jobManager,但是唯一的问题是jobmanager是单节点的,必然会有单节点故障问题的产生,所以我们也可以在standAlone模式下,借助于zk,将我们的jobManager实现成为高可用的模式
首先停止Flink的standAlone模式,并启动zk和hadoop集群服务
第一步:修改配置文件
node01执行以下命令修改Flink的配置文件
node01修改flink-conf.yaml配置文件
cd /kkb/install/flink-1.8.1/conf
vim flink-conf.yaml

jobmanager.rpc.address: node01
high-availability: zookeeper
high-availability.storageDir: hdfs://node01:8020/flink
high-availability.zookeeper.path.root: /flink
high-availability.zookeeper.quorum: node01:2181,node02:2181,node03:218
node01修改masters配置文件
node01执行以下命令修改master配置文件
cd /kkb/install/flink-1.8.1/conf
vim masters

node01:8081
node02:8081
node01修改slaves配置文件
node01执行以下命令修改slaves配置文件
cd /kkb/install/flink-1.8.1/conf
vim slaves

node01
node02
node03
第二步:hdfs上面创建flink对应的文件夹
node01执行以下命令,在hdfs上面创建文件夹
hdfs dfs -mkdir -p /flink
第三步:拷贝配置文件
将node01服务器修改后的配置文件拷贝到其他服务器上面去
node01执行以下命令拷贝配置文件
cd /kkb/install/flink-1.8.1/conf
scp flink-conf.yaml  masters  slaves  node02:$PWD
scp flink-conf.yaml  masters  slaves  node03:$PWD
第四步:启动flink集群
node01执行以下命令启动flink集群
cd /kkb/install/flink-1.8.1
bin/start-cluster.sh
第五步:页面访问
访问node01服务器的web界面
http://node01:8081/#/overview
访问node02服务器的web界面
http://node02:8081/#/overview
注意:一旦访问node02的web界面,会发现我们的web界面会自动跳转到node01的web界面上,因为此时,我们的node01服务器才是真正的active状态的节点
第六步:模拟故障宕机实现自动切换
将node01服务器的jobManager进程杀死,然后过一段时间之后查看node02的jobManager是否能够访问
注意:  JobManager发生切换时,TaskManager也会跟着发生重启,这其实是一个隐患问题
第七步:flink的standAlone模式在HA下提交任务
在HA这种模式下,提交任务与standAlone单节点模式提交任务是一样的,即使JobManager服务器宕机了也没有关系,会自动进行切换
node01执行以下命令启动socket服务,输入单词
nc -lk 9000
node01启动flink的自带的单词统计程序,接受输入的socket数据并进行统计
cd /kkb/install/flink-1.8.1
bin/flink run examples/streaming/SocketWindowWordCount.jar   --hostname node01  --port 9000
node01服务器执行以下命令查看统计结果
cd /kkb/install/flink-1.8.1/log
tail -200f flink-hadoop-taskexecutor-0-node01.kaikeba.com.out

4、flink  on  yarn模式
flink的任务也可以运行在yarn上面,将flnk的任务提交到yarn平台,通过yarn平台来实现我们的任务统一的资源调度管理,方便我们管理集群当中的CPU和内存等资源
依赖环境说明:
至少hadoop2.2版本及以上
hdfs以及yarn服务正常启动
flink  on yarn又分为两种模式:

1、第一种模式:单个yarn session模式
这种方式需要先启动集群,然后在提交作业,接着会向yarn申请一块资源空间后,资源永远保持不变。如果资源满了,下一个作业就无法提交,只能等到yarn中的其中一个作业执行完成后,释放了资源,那下一个作业才会正常提交,实际工作当中一般不会使用这种模式
这种模式,不需要做任何配置,直接将任务提价到yarn集群上面去,我们需要提前启动hdfs以及yarn集群即可
启动单个Yarn Session模式
第一步:修改yarn-site.xml配置为文件
node01执行以下命令修改yarn-site.xml,添加以下配置属性
cd /kkb/install/hadoop-2.6.0-cdh5.14.2/etc/hadoop
vim yarn-site.xml

<property>
<name>yarn.resourcemanager.am.max-attempts</name>
<value>4</value>
<description>
The maximum number of application master execution attempts.
</description>
</property>
然后将修改后的配置文件拷贝到node02与node03服务器
node01执行以下命令进行拷贝配置文件
cd /kkb/install/hadoop-2.6.0-cdh5.14.2/etc/hadoop
scp yarn-site.xml  node02:$PWD
scp yarn-site.xml  node03:$PWD
然后重新启动yarn集群即可

第二步:修改flink配置文件
node01执行以下命令更改flink配置文件
cd /kkb/install/flink-1.8.1/conf
vim flink-conf.yaml

high-availability: zookeeper
high-availability.storageDir: hdfs://node01:8020/flink_yarn_ha
high-availability.zookeeper.path.root: /flink-yarn
high-availability.zookeeper.quorum: node01:2181,node02:2181,node03:2181
yarn.application-attempts: 10

hdfs上面创建文件夹
node01执行以下命令创建hdfs文件夹
hdfs dfs -mkdir -p /flink_yarn_ha
第三步:在yarn当中启动flink集群
直接在node01执行以下命令,在yarn当中启动一个全新的flink集群,可以直接使用yarn-session.sh这个脚本来进行启动
cd /kkb/install/flink-1.8.1/
bin/yarn-session.sh -n 2 -jm 1024 -tm 1024 [-d]
我们也可以使用 --help 来查看更多参数设置      
bin/yarn-session.sh –help

Usage:
   Required
     -n,--container <arg>   Number of YARN container to allocate (=Number of Task Managers)
   Optional
     -D <property=value>             use value for given property
     -d,--detached                   If present, runs the job in detached mode
     -h,--help                       Help for the Yarn session CLI.
     -id,--applicationId <arg>       Attach to running YARN session
     -j,--jar <arg>                  Path to Flink jar file
     -jm,--jobManagerMemory <arg>    Memory for JobManager Container with optional unit (default: MB)
     -m,--jobmanager <arg>           Address of the JobManager (master) to which to connect. Use this flag to connect to a different JobManager than the one specified in the configuration.
     -n,--container <arg>            Number of YARN container to allocate (=Number of Task Managers)
     -nl,--nodeLabel <arg>           Specify YARN node label for the YARN application
     -nm,--name <arg>                Set a custom name for the application on YARN
     -q,--query                      Display available YARN resources (memory, cores)
     -qu,--queue <arg>               Specify YARN queue.
     -s,--slots <arg>                Number of slots per TaskManager
     -sae,--shutdownOnAttachedExit   If the job is submitted in attached mode, perform a best-effort cluster shutdown when the CLI is terminated abruptly, e.g., in response to a user interrupt, such
                                     as typing Ctrl + C.
     -st,--streaming                 Start Flink in streaming mode
     -t,--ship <arg>                 Ship files in the specified directory (t for transfer)
     -tm,--taskManagerMemory <arg>   Memory per TaskManager Container with optional unit (default: MB)
     -yd,--yarndetached              If present, runs the job in detached mode (deprecated; use non-YARN specific option instead)
     -z,--zookeeperNamespace <arg>   Namespace to create the Zookeeper sub-paths for high availability mode
注意:如果在启动的时候,yarn的内存太小,可能会报以下错误
Diagnostics: Container [] is running beyond virtual memory limits. Current usage: 250.5 MB of 1 GB physical memory used; 2.2 GB of 2.1 GB virtual memory used. Killing containerpid=6386,containerID=container_1521277661809_0006_01_000001
我们需要修改yarn-site.xml添加以下配置,然后重启yarn即可
<property>  
    <name>yarn.nodemanager.vmem-check-enabled</name>  
    <value>false</value>  
</property>  

第二步:查看yarn管理界面8088
访问yarn的8088管理界面,发现yarn当中有一个应用
http://node01:8088/cluster
yarn当中会存在一个常驻的application,就是为我们flink单独启动的一个session
第三步:提交任务
使用flink自带的jar包,实现单词计数统计功能
node01准备文件并上传hdfs
cd /kkb
vim wordcount.txt
内容如下
hello world
flink hadoop
hive spark

hdfs上面创建文件夹并上传文件
hdfs dfs -mkdir -p /flink_input
hdfs dfs -put wordcount.txt  /flink_input

node01执行以下命令,提交任务到flink集群
cd /kkb/install/flink-1.8.1
bin/flink run ./examples/batch/WordCount.jar -input hdfs://node01:8020/flink_input -output hdfs://node01:8020/flink_output/wordcount-result.txt 

第四步:验证Yarn Session的高可用
通过node01:8088这个界面,查看yarn session启动在哪一台机器上,然后杀死yarn session进程,我们会发现yarn session会重新启动在另外一台机器上面
找到YarnSessionClusterEntrypoint所在的服务器,然后杀死该进程
[hadoop@node02 ~]$ jps
10065 QuorumPeerMain
10547 YarnSessionClusterEntrypoint
10134 DataNode
10234 NodeManager
10652 Jps
[hadoop@node02 ~]$ kill -9 10547
杀死YarnSessionClusterEntrypoint进程之后,会发现,yarn集群会重新启动一个YarnSessionClusterEntrypoint进程在其他机器上面


2、第二种模式:多个yarn session模式
这种方式的好处是一个任务会对应一个job,即每提交一个作业会根据自身的情况,向yarn申请资源,直到作业执行完成,并不会影响下一个作业的正常运行,除非是yarn上面没有任何资源的情况下。
注意:client端必须要设置YARN_CONF_DIR或者HADOOP_CONF_DIR或者HADOOP_HOME环境变量,通过这个环境变量来读取YARN和HDFS的配置信息,否则启动会失败
不需要在yarn当中启动任何集群,直接提交任务即可
第一步:直接执行命令提交任务
cd /kkb/install/flink-1.8.1/
bin/flink run -m yarn-cluster -yn 2 -yjm 1024 -ytm 1024 ./examples/batch/WordCount.jar -input hdfs://node01:8020/flink_input -output hdfs://node01:8020/out_result/out_count.txt
第二步:查看输出结果
hdfs执行以下命令查看输出结果
hdfs dfs -text hdfs://node01:8020/out_result/out_count.txt
第三步:查看flink  run帮助文档
我们可以使用--help 来查看帮助文档可以添加哪些参数
cd /kkb/install/flink-1.8.1/     
bin/flink run --help
得到结果内容如下
Action "run" compiles and runs a program.

  Syntax: run [OPTIONS] <jar-file> <arguments>
  "run" action options:
     -c,--class <classname>               Class with the program entry point
                                          ("main" method or "getPlan()" method.
                                          Only needed if the JAR file does not
                                          specify the class in its manifest.
     -C,--classpath <url>                 Adds a URL to each user code
                                          classloader  on all nodes in the
                                          cluster. The paths must specify a
                                          protocol (e.g. file://) and be
                                          accessible on all nodes (e.g. by means
                                          of a NFS share). You can use this
                                          option multiple times for specifying
                                          more than one URL. The protocol must
                                          be supported by the {@link
                                          java.net.URLClassLoader}.
     -d,--detached                        If present, runs the job in detached
                                          mode
     -n,--allowNonRestoredState           Allow to skip savepoint state that
                                          cannot be restored. You need to allow
                                          this if you removed an operator from
                                          your program that was part of the
                                          program when the savepoint was
                                          triggered.
     -p,--parallelism <parallelism>       The parallelism with which to run the
                                          program. Optional flag to override the
                                          default value specified in the
                                          configuration.
     -q,--sysoutLogging                   If present, suppress logging output to
                                          standard out.
     -s,--fromSavepoint <savepointPath>   Path to a savepoint to restore the job
                                          from (for example
                                          hdfs:///flink/savepoint-1537).
     -sae,--shutdownOnAttachedExit        If the job is submitted in attached
                                          mode, perform a best-effort cluster
                                          shutdown when the CLI is terminated
                                          abruptly, e.g., in response to a user
                                          interrupt, such as typing Ctrl + C.
  Options for yarn-cluster mode:
     -d,--detached                        If present, runs the job in detached
                                          mode
     -m,--jobmanager <arg>                Address of the JobManager (master) to
                                          which to connect. Use this flag to
                                          connect to a different JobManager than
                                          the one specified in the
                                          configuration.
     -sae,--shutdownOnAttachedExit        If the job is submitted in attached
                                          mode, perform a best-effort cluster
                                          shutdown when the CLI is terminated
                                          abruptly, e.g., in response to a user
                                          interrupt, such as typing Ctrl + C.
     -yD <property=value>                 use value for given property
     -yd,--yarndetached                   If present, runs the job in detached
                                          mode (deprecated; use non-YARN
                                          specific option instead)
     -yh,--yarnhelp                       Help for the Yarn session CLI.
     -yid,--yarnapplicationId <arg>       Attach to running YARN session
     -yj,--yarnjar <arg>                  Path to Flink jar file
     -yjm,--yarnjobManagerMemory <arg>    Memory for JobManager Container with
                                          optional unit (default: MB)
     -yn,--yarncontainer <arg>            Number of YARN container to allocate
                                          (=Number of Task Managers)
     -ynl,--yarnnodeLabel <arg>           Specify YARN node label for the YARN
                                          application
     -ynm,--yarnname <arg>                Set a custom name for the application
                                          on YARN
     -yq,--yarnquery                      Display available YARN resources
                                          (memory, cores)
     -yqu,--yarnqueue <arg>               Specify YARN queue.
     -ys,--yarnslots <arg>                Number of slots per TaskManager
     -yst,--yarnstreaming                 Start Flink in streaming mode
     -yt,--yarnship <arg>                 Ship files in the specified directory
                                          (t for transfer)
     -ytm,--yarntaskManagerMemory <arg>   Memory per TaskManager Container with
                                          optional unit (default: MB)
     -yz,--yarnzookeeperNamespace <arg>   Namespace to create the Zookeeper
                                          sub-paths for high availability mode
     -z,--zookeeperNamespace <arg>        Namespace to create the Zookeeper
                                          sub-paths for high availability mode

  Options for default mode:
     -m,--jobmanager <arg>           Address of the JobManager (master) to which
                                     to connect. Use this flag to connect to a
                                     different JobManager than the one specified
                                     in the configuration.
     -z,--zookeeperNamespace <arg>   Namespace to create the Zookeeper sub-paths
                                     for high availability mode
3、flink run脚本分析
我们提交flink任务的时候,可以加以下这些参数
1、默认查找当前yarn集群中已有的yarn-session信息中的jobmanager【/tmp/.yarn-properties-root】:
bin/flink run ./examples/batch/WordCount.jar -input hdfs://hostname:port/hello.txt -output hdfs://hostname:port/result1
2、连接指定host和port的jobmanager:
bin/flink run -m node01:8081 ./examples/batch/WordCount.jar -input hdfs://hostname:port/hello.txt -output hdfs://hostname:port/result1
3、启动一个新的yarn-session:
bin/flink run -m yarn-cluster -yn 2 ./examples/batch/WordCount.jar -input hdfs://hostname:port/hello.txt -output hdfs://hostname:port/result1
注意:yarn session命令行的选项也可以使用./bin/flink 工具获得。它们都有一个y或者yarn的前缀
例如:bin/flink run -m yarn-cluster -yn 2 ./examples/batch/WordCount.jar 


6、Flink编程入门案例



实时处理代码开发
开发flink代码,实现统计socket当中的单词数量
第一步:创建maven工程,导入jar包
<dependencies>
    <!-- https://mvnrepository.com/artifact/org.apache.flink/flink-streaming-scala -->
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-streaming-scala_2.11</artifactId>
        <version>1.8.1</version>
    </dependency>

    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-scala_2.11</artifactId>
        <version>1.8.1</version>
    </dependency>

</dependencies>
<build>
    <plugins>
        <!-- 限制jdk版本插件 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
        <!-- 编译scala需要用到的插件 -->
        <plugin>
            <groupId>net.alchim31.maven</groupId>
            <artifactId>scala-maven-plugin</artifactId>
            <version>3.2.2</version>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>testCompile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <!-- 项目打包用到的插件 -->
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifest>
                        <mainClass></mainClass>
                    </manifest>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>




第二步:开发flink代码统计socket当中的单词数量
开发flink代码实现接受socket单词数据,然后对数据进行统计
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.windowing.time.Time


case class CountWord(word:String,count:Long)

object FlinkCount {

  def main(args: Array[String]): Unit = {
    //获取程序入口类
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //从socket当中获取数据
    val result: DataStream[String] = environment.socketTextStream("node01",9000)
    //导入隐式转换的包,否则时间不能使用
    import org.apache.flink.api.scala._
    //将数据进行切割,封装到样例类当中,然后进行统计
    val resultValue: DataStream[CountWord] = result
      .flatMap(x => x.split(" "))
      .map(x => CountWord(x,1))
      .keyBy("word")
     // .timeWindow(Time.seconds(1),Time.milliseconds(1))  按照每秒钟时间窗口,以及每秒钟滑动间隔来进行数据统计
      .sum("count")
    //打印最终输出结果
    resultValue.print().setParallelism(1)
    //启动服务
    environment.execute()
  }
}

第三步:打包上传到服务器运行
将我们的程序打包,然后上传到服务器进行运行,将我们打包好的程序上传到node01服务器,然后体验在各种模式下进行运行我们的程序
1、standAlone模式运行程序
第一步:启动flink集群
node01执行以下命令启动flink集群
cd /kkb/install/flink-1.8.1
bin/start-cluster.sh

第二步:启动node01的socket服务,并提交flink任务
node01执行以下命令启动node01的socket服务
nc -lk 9000
提交任务
将我们打包好的jar包上传到node01服务器的/kkb路径下,然后提交任务,注意,在pom.xml当中需要添加我们的打包插件,然后将任务代码进行打包,且集群已有的代码需要将打包scope设置为provided,在pom.xml将我们关于flink的jar包scope设置为provided

打包,并将我们的jar-with-dependencies的jar包上传到node01服务器的/kkb路径下

node01执行以下命令提交任务
cd /kkb/install/flink-1.8.1/
bin/flink run --class com.kkb.flink.demo1.FlinkCount /kkb/flink_day01-1.0-SNAPSHOT-jar-with-dependencies.jar
第三步:查询运行结果
node01查看运行结果
cd /kkb/install/flink-1.8.1/log
tail -200f flink-hadoop-taskexecutor-1-node01.kaikeba.com.out

注意:结果保存在以.out结尾的文件当中,哪个文件当中有数据,就查看哪个文件即可

离线批量处理代码开发
flink也可以通过批量处理代码来实现批量数据处理
需求:处理附件中的count.txt文件,实现单词计数统计

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

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

    val inputPath = "D:\\count.txt"
    val outPut = "D:\\data\\result2"

    //获取程序入口类ExecutionEnvironment
    val env = ExecutionEnvironment.getExecutionEnvironment
    val text = env.readTextFile(inputPath)

    //引入隐式转换
    import org.apache.flink.api.scala._

    val value: AggregateDataSet[(String, Int)] = text.flatMap(x => x.split(" ")).map(x =>(x,1)).groupBy(0).sum(1)
    value.writeAsText("d:\\datas\\result.txt").setParallelism(1)

    env.execute("batch word count")
  }

}


7、Flink的shell命令行代码调试
为了方便我们的开发调试,Flink支持通过shell命令行的方式来对我们的代码进行开发运行,类似于Spark的shell命令行对代码的调试是一样的,可以方便的对我们的代码执行结果进行跟踪调试,查验代码的问题所在
Flink shell方式支持流处理和批处理。当启动shell命令行之后,两个不同的ExecutionEnvironments会被自动创建。使用senv(Stream)和benv(Batch)分别去处理流处理和批处理程序。(类似于spark-shell中sc变量)
批量处理代码调试
第一步:进入flink的scala-shell
node01执行以下命令进入scala-shell
cd /kkb/install/flink-1.8.1/
bin/start-scala-shell.sh local
或者我们也可以启动flink的集群,然后进入flink的shell客户端,将任务提交到flink集群上面去
cd /kkb/install/flink-1.8.1/
bin/start-scala-shell.sh remote node01 8081

第二步:使用benv变量执行批量处理
在scala-shell下,使用批处理来调试代码
val line =benv.fromElements("hello world","spark flink")
line.flatMap(x => x.split(" ")).map(x =>(x,1)).groupBy(0).sum(1).print
实时处理代码调试
通过senv变量实现代码
第一步:node01启动nc -lk  服务端
node01执行以下命令启动服务端
[hadoop@node01 ~]$ nc -lk 9000
第二步:进入scala-shell客户端
node01执行以下命令进入scala-shell
cd /kkb/install/flink-1.8.1/
bin/start-scala-shell.sh local
第三步:使用senv来统计单词出现次数
node01使用senv变量来实时统计单词出现的次数
senv.socketTextStream("node01",9000).flatMap(x => x.split(" ")).map(x =>(x,1)).keyBy(0).sum(1).print
senv.execute
第四步:node01发送单词
node01服务器发送单词

8、Flink实时处理之DataStream
Flink的API概览

1、dataStream的数据源
1、socket数据源
从socket当中接收数据,并统计最近5秒钟每个单词出现的次数
第一步:node01开发socket服务  
node01执行以下命令开启socket服务
nc -lk  9000
第二步:开发代码实现
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.windowing.time.Time

object FlinkSource1 {
  def main(args: Array[String]): Unit = {
    //获取程序入口类
    val streamExecution: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    val socketText: DataStream[String] = streamExecution.socketTextStream("node01",9000)
    //注意:必须要添加这一行隐式转行,否则下面的flatmap方法执行会报错
    import org.apache.flink.api.scala._
    val result: DataStream[(String, Int)] = socketText.flatMap(x => x.split(" "))
      .map(x => (x, 1))
      .keyBy(0)
      .timeWindow(Time.seconds(5), Time.seconds(5)) //统计最近5秒钟的数据
      .sum(1)

    //打印结果数据
    result.print().setParallelism(1)
    //执行程序
    streamExecution.execute()
  }
}

2、文件数据源
读取hdfs路径下面所有的文件数据进行处理
第一步:添加maven依赖
<repositories>
    <repository>
        <id>cloudera</id>
        <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
    </repository>
</repositories>

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>2.6.0-mr1-cdh5.14.2</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-common</artifactId>
    <version>2.6.0-cdh5.14.2</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs</artifactId>
    <version>2.6.0-cdh5.14.2</version>
</dependency>

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-mapreduce-client-core</artifactId>
    <version>2.6.0-cdh5.14.2</version>
</dependency>

第二步:代码实现
object FlinkSource2 {
  def main(args: Array[String]): Unit = {
    val executionEnvironment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    //从文本读取数据
    val hdfStream: DataStream[String] = executionEnvironment.readTextFile("hdfs://node01:8020/flink_input/")
    val result: DataStream[(String, Int)] = hdfStream.flatMap(x => x.split(" ")).map(x =>(x,1)).keyBy(0).sum(1)

    result.print().setParallelism(1)

    executionEnvironment.execute("hdfsSource")
  }
}

3、从一个已经存在的集合当中获取数据
代码实现
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}

object FlinkSource3 {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    val value: DataStream[String] = environment.fromElements[String]("hello world","spark flink")
    val result2: DataStream[(String, Int)] = value.flatMap(x => x.split(" ")).map(x =>(x,1)).keyBy(0).sum(1)
    result2.print().setParallelism(1)
    environment.execute()
  }
}


4、自定义数据源
如果flink自带的一些数据源还不够的工作使用的话,我们还可以自定义数据源
flink提供了大量的已经实现好的source方法,你也可以自定义source
通过实现sourceFunction接口来自定义source,
或者你也可以通过实现ParallelSourceFunction 接口 or 继承RichParallelSourceFunction 来自定义source。
1、通过ParallelSourceFunction 来实现自定义数据源
如果需要实现一个多并行度的数据源,那么我们可以通过实现ParallelSourceFunction 接口或者继承RichParallelSourceFunction 来自定义有并行度的source。
第一步:使用scala代码实现ParallelSourceFunction接口
import org.apache.flink.streaming.api.functions.source.{ParallelSourceFunction, SourceFunction}

class MyParalleSource  extends ParallelSourceFunction[String] {
  var isRunning:Boolean = true

  override def run(sourceContext: SourceFunction.SourceContext[String]): Unit = {
    while (true){
      sourceContext.collect("hello world")
    }
  }
  override def cancel(): Unit = {
    isRunning = false
  }
}

第二步:使用自定义数据源
object FlinkSource5 {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    val sourceStream: DataStream[String] = environment.addSource(new MyParalleSource)
    val result: DataStream[(String, Int)] = sourceStream.flatMap(x => x.split(" ")).map(x => (x, 1))
      .keyBy(0)
      .sum(1)
    result.print().setParallelism(2)
    environment.execute("paralleSource")
  }
}





2、dataStream的算子介绍
官网算子介绍:
https://ci.apache.org/projects/flink/flink-docs-master/dev/stream/operators/index.html

flink当中对于实时处理,有很多的算子,我们可以来看看常用的算子主要有哪些,dataStream当中的算子主要分为三大类,
Transformations:转换的算子,都是懒执行的,只有真正碰到sink的算子才会真正加载执行
partition:对数据进行重新分区等操作
Sink:数据下沉目的地

DataStream的Transformations算子
?map:输入一个元素,然后返回一个元素,中间可以做一些清洗转换等操作
?flatmap:输入一个元素,可以返回零个,一个或者多个元素
?filter:过滤函数,对传入的数据进行判断,符合条件的数据会被留下
?keyBy:根据指定的key进行分组,相同key的数据会进入同一个分区【典型用法见备注】
?reduce:对数据进行聚合操作,结合当前元素和上一次reduce返回的值进行聚合操作,然后返回一个新的值
?aggregations:sum(),min(),max()等
?window:在后面单独详解
?Union:合并多个流,新的流会包含所有流中的数据,但是union是一个限制,就是所有合并的流类型必须是一致的。
?Connect:和union类似,但是只能连接两个流,两个流的数据类型可以不同,会对两个流中的数据应用不同的处理方法。
?CoMap, CoFlatMap:在ConnectedStreams中需要使用这种函数,类似于map和flatmap
?Split:根据规则把一个数据流切分为多个流
?Select:和split配合使用,选择切分后的流
案例一:使用union算子来合并多个DataStream
获取两个dataStream,然后使用union将两个dataStream进行合并
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}

object FlinkUnion {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    //获取第一个dataStream
    val firstStream: DataStream[String] = environment.fromElements("hello world","test scala")
    //获取第二个dataStream
    val secondStream: DataStream[String] = environment.fromElements("second test","spark flink")
    //将两个流进行合并起来
    val unionAll: DataStream[String] = firstStream.union(secondStream)
    //结果不做任何处理
    val unionResult: DataStream[String] = unionAll.map(x => {
     // println(x)
      x
    })
    //调用sink算子,打印输出结果
    unionResult.print().setParallelism(1)
    //开始运行
    environment.execute()
  }
}
案例二:使用connect实现不同类型的DataStream进行连接
import org.apache.flink.streaming.api.scala.{ConnectedStreams, DataStream, StreamExecutionEnvironment}

object FlinkConnect {

  def main(args: Array[String]): Unit = {
    //获取程序入口类
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //导入隐式转换的包
    import  org.apache.flink.api.scala._
    //定义string类型的dataStream
    val strStream: DataStream[String] = environment.fromElements("hello world","abc test")
    //定义int类型的dataStream
    val intStream: DataStream[Int] = environment.fromElements(1,2,3,4,5)
    //两个流进行connect操作
    val connectedStream: ConnectedStreams[String, Int] = strStream.connect(intStream)
    //通过map对数据进行处理,传入两个函数
    val connectResult: DataStream[Any] = connectedStream.map(x =>{ x  + "abc"},y =>{ y * 2 })
    connectResult.print().setParallelism(1)
    environment.execute("connect stream")
  }
}

案例三:使用split将一个DataStream切成多个DataStream
import java.{lang, util}
import org.apache.flink.streaming.api.collector.selector.OutputSelector
import org.apache.flink.streaming.api.scala.{DataStream, SplitStream, StreamExecutionEnvironment}

object FlinkSplit {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    //获取第一个dataStream
    val resultDataStream: DataStream[String] = environment.fromElements("hello world","test spark","spark flink")
    //通过split来对我们的流进行切分操作
    val splitStream: SplitStream[String] = resultDataStream.split(new OutputSelector[String] {
      override def select(out: String): lang.Iterable[String] = {
        val strings = new util.ArrayList[String]()
        if (out.contains("hello")) {
          //如果包含hello,那么我们就给这个流起名字叫做hello
          strings.add("hello")
        } else {
          strings.add("other")
        }
        strings
      }
    })
    //对我么的stream进行选择
    val helloStream: DataStream[String] = splitStream.select("hello")
    //打印包含hello的所有的字符串
    helloStream.print().setParallelism(1)
    environment.execute()
  }
}

DataStream的Partition算子
https://blog.csdn.net/lmalds/article/details/60575205   flink的各种算子介绍

partition算子允许我们对数据进行重新分区,或者解决数据倾斜等问题
?Random partitioning:随机分区
?dataStream.shuffle()
?Rebalancing:对数据集进行再平衡,重分区,消除数据倾斜
?dataStream.rebalance()
?Rescaling:Rescaling是通过执行oepration算子来实现的。由于这种方式仅发生在一个单一的节点,因此没有跨网络的数据传输。
?dataStream.rescale()



?Custom partitioning:自定义分区
?自定义分区需要实现Partitioner接口
?dataStream.partitionCustom(partitioner, "someKey")
?或者dataStream.partitionCustom(partitioner, 0);
?Broadcasting:广播变量,后面详细讲解

需求:对我们filter过后的数据进行重新分区
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}

object FlinkPartition {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    import org.apache.flink.api.scala._
    val dataStream: DataStream[String] = environment.fromElements("hello world","test spark","abc hello","hello flink")

    val resultStream: DataStream[(String, Int)] = dataStream.filter(x => x.contains("hello"))
      // .shuffle  //随机的重新分发数据,上游的数据,随机的发送到下游的分区里面去
     // .rescale
      .rebalance //对数据重新进行分区,涉及到shuffle的过程
      .flatMap(x => x.split(" "))
      .map(x => (x, 1))
      .keyBy(0)
      .sum(1)

    resultStream.print().setParallelism(1)
    environment.execute()
  }
}
案例实战:自定义分区策略
如果以上的几种分区方式还没法满足我们的需求,我们还可以自定义分区策略来实现数据的分区
需求:自定义分区策略,实现不同分区的数据发送到不同分区里面去进行处理,将包含hello的字符串发送到一个分区里面去,其他的发送到另外一个分区里面去
第一步:自定义分区类
import org.apache.flink.api.common.functions.Partitioner

class MyPartitioner  extends Partitioner[String]{
  override def partition(word: String, num: Int): Int = {
    println("分区个数为" +  num)
    if(word.contains("hello")){
      0
    }else{
      1
    }
  }
}


第二步:代码实现进行分区
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}

object FlinkCustomerPartition {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //设置我们的分区数,如果不设置,默认使用CPU核数作为分区个数

    environment.setParallelism(2)
    import  org.apache.flink.api.scala._
    //获取dataStream
    val sourceStream: DataStream[String] = environment.fromElements("hello world","spark flink","hello world","hive hadoop")
    val rePartition: DataStream[String] = sourceStream.partitionCustom(new MyPartitioner,x => x +"")
    rePartition.map(x =>{
      println("数据的key为" +  x + "线程为" + Thread.currentThread().getId)
      x
    })
    rePartition.print()
    environment.execute()

  }
}

DataStream的sink算子
https://ci.apache.org/projects/flink/flink-docs-master/dev/connectors/

?writeAsText():将元素以字符串形式逐行写入,这些字符串通过调用每个元素的toString()方法来获取
?print() / printToErr():打印每个元素的toString()方法的值到标准输出或者标准错误输出流中
?自定义输出addSink【kafka、redis】
我们可以通过sink算子,将我们的数据发送到指定的地方去,例如kafka或者redis或者hbase等等,前面我们已经使用过将数据打印出来调用print()方法,接下来我们来实现自定义sink将我们的数据发送到redis里面去
第一步:导入flink整合redis的jar包
<!-- https://mvnrepository.com/artifact/org.apache.flink/flink-connector-kafka-0.11 -->
<dependency>
    <groupId>org.apache.bahir</groupId>
    <artifactId>flink-connector-redis_2.11</artifactId>
    <version>1.0</version>
</dependency>


第二步:代码开发
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.connectors.redis.RedisSink
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig
import org.apache.flink.streaming.connectors.redis.common.mapper.{RedisCommand, RedisCommandDescription, RedisMapper}

object Stream2Redis {

  def main(args: Array[String]): Unit = {
    //获取程序入口类
    val executionEnvironment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    import org.apache.flink.api.scala._
    //组织数据
    val streamSource: DataStream[String] = executionEnvironment.fromElements("hello world","key value")
    //将数据包装成为key,value对形式的tuple
    val tupleValue: DataStream[(String, String)] = streamSource.map(x =>(x.split(" ")(0),x.split(" ")(1)))


    val builder = new FlinkJedisPoolConfig.Builder

    builder.setHost("node03")
    builder.setPort(6379)

    builder.setTimeout(5000)
    builder.setMaxTotal(50)
    builder.setMaxIdle(10)
    builder.setMinIdle(5)
    val config: FlinkJedisPoolConfig = builder.build()
    //获取redis  sink
    val redisSink = new RedisSink[Tuple2[String,String]](config,new MyRedisMapper)

    //使用我们自定义的sink
    tupleValue.addSink(redisSink)
    //执行程序
    executionEnvironment.execute("redisSink")
  }
}


class MyRedisMapper  extends RedisMapper[Tuple2[String,String]]{
  override def getCommandDescription: RedisCommandDescription = {
    new RedisCommandDescription(RedisCommand.SET)


  }

  override def getKeyFromData(data: (String, String)): String = {
    data._1

  }

  override def getValueFromData(data: (String, String)): String = {
    data._2

  }
}


9、Flink的window和Time详解
对于流式处理,如果我们需要求取总和,平均值,或者最大值,最小值等,是做不到的,因为数据一直在源源不断的产生,即数据是没有边界的,所以没法求最大值,最小值,平均值等,所以为了一些数值统计的功能,我们必须指定时间段,对某一段时间的数据求取一些数据值是可以做到的。或者对某一些数据求取数据值也是可以做到的
所以,流上的聚合需要由 window 来划定范围,比如 “计算过去的5分钟” ,或者 “最后100个元素的和” 。
window是一种可以把无限数据切割为有限数据块的手段
窗口可以是 时间驱动的 【Time Window】(比如:每30秒)或者 数据驱动的【Count Window】 (比如:每100个元素)。

窗口类型汇总:


1、窗口的基本类型介绍
窗口通常被区分为不同的类型:
?tumbling windows:滚动窗口 【没有重叠】 
?sliding windows:滑动窗口 【有重叠】
?session windows:会话窗口 ,一般没人用
tumbling windows类型:没有重叠的窗口


sliding windows:滑动窗口 【有重叠】 

2、Flink的窗口介绍
Time Window窗口的应用
time window又分为滚动窗口和滑动窗口,这两种窗口调用方法都是一样的,都是调用timeWindow这个方法,如果传入一个参数就是滚动窗口,如果传入两个参数就是滑动窗口

Count Windos窗口的应用
与timeWindow类型,CountWinodw也可以分为滚动窗口和滑动窗口,这两个窗口调用方法一样,都是调用countWindow,如果传入一个参数就是滚动窗口,如果传入两个参数就是滑动窗口

自定义window的应用
如果time window和 countWindow还不够用的话,我们还可以使用自定义window来实现数据的统计等功能。


3、window的数值聚合统计
对于某一个window内的数值统计,我们可以增量的聚合统计或者全量的聚合统计
增量聚合统计:
窗口当中每加入一条数据,就进行一次统计
?reduce(reduceFunction)
?aggregate(aggregateFunction)
?sum(),min(),max()

需求:通过接收socket当中输入的数据,统计每5秒钟数据的累计的值
代码实现:
import org.apache.flink.api.common.functions.ReduceFunction
import org.apache.flink.streaming.api.datastream.DataStreamSink
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.windowing.time.Time

object FlinkTimeCount {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._

    val socketStream: DataStream[String] = environment.socketTextStream("node01",9000)
    val print: DataStreamSink[(Int, Int)] = socketStream
      .map(x => (1, x.toInt))
      .keyBy(0)
      .timeWindow(Time.seconds(5))
      .reduce(new ReduceFunction[(Int, Int)] {
        override def reduce(t: (Int, Int), t1: (Int, Int)): (Int, Int) = {
          (t._1, t._2 + t1._2)
        }
      }).print()

    environment.execute("startRunning")
  }
}
全量聚合统计:
等到窗口截止,或者窗口内的数据全部到齐,然后再进行统计,可以用于求窗口内的数据的最大值,或者最小值,平均值等
等属于窗口的数据到齐,才开始进行聚合计算【可以实现对窗口内的数据进行排序等需求】
apply(windowFunction)
process(processWindowFunction)
processWindowFunction比windowFunction提供了更多的上下文信息。
需求:通过全量聚合统计,求取每3条数据的平均值
import org.apache.flink.api.java.tuple.Tuple
import org.apache.flink.streaming.api.datastream.DataStreamSink
import org.apache.flink.streaming.api.scala.function.ProcessWindowFunction
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment, WindowedStream}
import org.apache.flink.streaming.api.windowing.windows.{GlobalWindow, TimeWindow}
import org.apache.flink.util.Collector


object FlinkCountWindowAvg {

  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    import org.apache.flink.api.scala._
    val socketStream: DataStream[String] = environment.socketTextStream("node01",9000)
    //统计一个窗口内的数据的平均值
    val socketDatas: DataStreamSink[Double] = socketStream.map(x => (1, x.toInt))
      .keyBy(0)
      //.timeWindow(Time.seconds(10))
      .countWindow(3)
      //通过process方法来统计窗口的平均值
      .process(new MyProcessWindowFunctionclass).print()
    //必须调用execute方法,否则程序不会执行
    environment.execute("count avg")
  }
}

/**ProcessWindowFunction 需要跟四个参数
  * 输入参数类型,输出参数类型,聚合的key的类型,window的下界
  *
  */
class MyProcessWindowFunctionclass extends ProcessWindowFunction[(Int , Int) , Double , Tuple , GlobalWindow]{
  override def process(key: Tuple, context: Context, elements: Iterable[(Int, Int)], out: Collector[Double]): Unit = {
    var totalNum = 0;
    var countNum = 0;
    for(data <-  elements){
      totalNum +=1
      countNum += data._2
    }
    out.collect(countNum/totalNum)
  }
}


4、Flink的Time三兄弟
前面我们已经介绍过我们可以通过window窗口来统计每一段时间或者每多少条数据的一些数值统计,但是也存在另外一个问题,就是如果数据有延迟该如何解决,例如一个窗口定义的是每隔五分钟统计一次,我们应该在上午九点至九点零五分这段时间统计一次数据的结果值,但是由于某一条数据由于网络延迟,数据产生时间是在九点零三分,数据到达我们的flink框架已经是在十点零三分了,这种问题怎么解决??
再例如:
原始日志如下:
日志自带时间
2018-10-10 10:00:01,134 INFO executor.Executor: Finished task in state 0.0
数据进入flink框架时间:
这条数据进入Flink的时间是2018-10-10 20:00:00,102
数据被window窗口处理时间:
到达window处理的时间为2018-10-10 20:00:01,100 
为了解决这个问题,flink在实时处理当中,对数据当中的时间规划为以下三个类型
针对stream数据中的时间,可以分为以下三种
?Event Time:事件产生的时间,它通常由事件中的时间戳描述。
?Ingestion time:事件进入Flink的时间
?Processing Time:事件被处理时当前系统的时间



1、EventTime详解
EventTime
1.事件生成时的时间,在进入Flink之前就已经存在,可以从event的字段中抽取。
2.必须指定watermarks(水位线)的生成方式。
3.优势:确定性,乱序、延时、或者数据重放等情况,都能给出正确的结果
4.弱点:处理无序事件时性能和延迟受到影响
2、IngestTime
1.事件进入flink的时间,即在source里获取的当前系统的时间,后续操作统一使用该时间。
2.不需要指定watermarks的生成方式(自动生成)
3.弱点:不能处理无序事件和延迟数据
3、ProcessingTime
1.执行操作的机器的当前系统时间(每个算子都不一样)
2.不需要流和机器之间的协调
3.优势:最佳的性能和最低的延迟
4.弱点:不确定性 ,容易受到各种因素影像(event产生的速度、到达flink的速度、在算子之间传输速度等),压根就不管顺序和延迟

4、三种时间的综合比较
性能: ProcessingTime> IngestTime> EventTime
延迟: ProcessingTime< IngestTime< EventTime
确定性: EventTime> IngestTime> ProcessingTime

5、如何设置time类型
在我们创建StreamExecutionEnvironment的时候可以设置time类型,不设置time类型,默认是processingTime,如果设置time类型为eventTime,那么必须要在我们的source之后明确指定Timestamp Assigner & Watermark Generator
// 设置时间特性
val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
//  不设置Time 类型,默认是processingTime。
//   如果使用EventTime则需要在source之后明确指定Timestamp Assigner & Watermark Generator
environment.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)
5、Flink的waterMark实现解决乱序以及延迟数据
1、watermark的作用
watermark是用于处理乱序事件的,而正确的处理乱序事件,通常用watermark机制结合window来实现。
我们知道,流处理从事件产生,到流经source,再到operator,中间是有一个过程和时间的。虽然大部分情况下,流到operator的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络、背压等原因,导致乱序的产生(out-of-order或者说late element)。
但是对于late element,我们又不能无限期的等下去,必须要有个机制来保证一个特定的时间后,必须触发window去进行计算了。这个特别的机制,就是watermark。
2、watermark解决迟到的数据
out-of-order/late element
实时系统中,由于各种原因造成的延时,造成某些消息发到flink的时间延时于事件产生的时间。如果基于event time构建window,但是对于late element,我们又不能无限期的等下去,必须要有个机制来保证一个特定的时间后,必须触发window去进行计算了。这个特别的机制,就是watermark。
Watermarks(水位线)就是来处理这种问题的机制
1.参考google的DataFlow。
2.是event time处理进度的标志。
3.表示比watermark更早(更老)的事件都已经到达(没有比水位线更低的数据 )。
4.基于watermark来进行窗口触发计算的判断。
有序的数据流watermark:
在某些情况下,基于Event Time的数据流是有续的(相对event time)。在有序流中,watermark就是一个简单的周期性标记。

无序的数据流watermark:
在更多场景下,基于Event Time的数据流是无续的(相对event time)。
在无序流中,watermark至关重要,她告诉operator比watermark更早(更老/时间戳更小)的事件已经到达, operator可以将内部事件时间提前到watermark的时间戳(可以触发window计算啦)



并行流当中的watermark:
通常情况下, watermark在source函数中生成,但是也可以在source后任何阶段,如果指定多次 watermark,后面指定的 watermarker会覆盖前面的值。 source的每个sub task独立生成水印。
watermark通过operator时会推进operators处的当前event time,同时operators会为下游生成一个新的watermark。
多输入operator(union、 keyBy、 partition)的当前event time是其输入流event time的最小值。
注意:多并行度的情况下,watermark对齐会取所有channel最小的watermark



watermark介绍参考链接:
https://blog.csdn.net/xorxos/article/details/80715113
3、watermark如何生成
通常,在接收到source的数据后,应该立刻生成watermark;但是,也可以在source后,应用简单的map或者filter操作,然后再生成watermark。
生成watermark的方式主要有2大类:
1.(1):With Periodic Watermarks
2.(2):With Punctuated Watermarks
第一种可以定义一个最大允许乱序的时间,这种情况应用较多。 
我们主要来围绕Periodic Watermarks来说明,下面是生成periodic watermark的方法:
4、watermark处理顺序数据
需求:定义一个窗口为10s,通过数据的event time时间结合watermark实现延迟10s的数据也能够正确统计
我们通过数据的eventTime来向前推10s,得到数据的watermark,
代码实现:
import java.text.SimpleDateFormat
import org.apache.flink.api.java.tuple.Tuple
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.{AssignerWithPeriodicWatermarks, AssignerWithPunctuatedWatermarks}
import org.apache.flink.streaming.api.scala.function.WindowFunction
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.watermark.Watermark
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector

import scala.collection.mutable.ArrayBuffer
import scala.util.Sorting

object FlinkWaterMark2 {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._

    //设置flink的数据处理时间为eventTime
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    env.setParallelism(1)
    val tupleStream: DataStream[(String, Long)] = env.socketTextStream("node01", 9000).map(x => {
      val strings: Array[String] = x.split(" ")

      (strings(0), strings(1).toLong)
    })

    //注册我们的水印
    val waterMarkStream: DataStream[(String, Long)] = tupleStream.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks[(String, Long)] {
      var currentTimemillis: Long = 0L
      var timeDiff: Long = 10000L
      val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

      /* //获取当前数据的waterMark
       override def getNext: Watermark = {
       }*/
      override def getCurrentWatermark: Watermark = {
        val watermark = new Watermark(currentTimemillis - timeDiff)
        watermark
      }

      //抽取数据的eventTime
      override def extractTimestamp(element: (String, Long), l: Long): Long = {
        val enventTime = element._2
        currentTimemillis = Math.max(enventTime, currentTimemillis)
        val id = Thread.currentThread().getId
        println("currentThreadId:" + id + ",key:" + element._1 + ",eventtime:[" + element._2 + "|" + sdf.format(element._2) + "],currentMaxTimestamp:[" + currentTimemillis + "|" + sdf.format(currentTimemillis) + "],watermark:[" + this.getCurrentWatermark.getTimestamp + "|" + sdf.format(this.getCurrentWatermark.getTimestamp) + "]")
        enventTime
      }
    })
    waterMarkStream.keyBy(0)
      .window(TumblingEventTimeWindows.of(Time.seconds(10)))
      .apply(new MyWindowFunction2).print()
    env.execute()
  }
}


class MyWindowFunction2 extends WindowFunction[(String,Long),String,Tuple,TimeWindow]{
  override def apply(key: Tuple, window: TimeWindow, input: Iterable[(String, Long)], out: Collector[String]): Unit = {
    val keyStr = key.toString
    val arrBuf = ArrayBuffer[Long]()
    val ite = input.iterator
    while (ite.hasNext){
      val tup2 = ite.next()
      arrBuf.append(tup2._2)
    }
    val arr = arrBuf.toArray
    Sorting.quickSort(arr)  //对数据进行排序,按照eventTime进行排序
    val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    val result = "聚合数据的key为:"+keyStr + "," + "窗口当中数据的条数为:"+arr.length + "," + "窗口当中第一条数据为:"+sdf.format(arr.head) + "," +"窗口当中最后一条数据为:"+ sdf.format(arr.last)+ "," + "窗口起始时间为:"+sdf.format(window.getStart) + "," + "窗口结束时间为:"+sdf.format(window.getEnd)  + "!!!!!看到这个结果,就证明窗口已经运行了"
    out.collect(result)
  }
}

输入测验数据
注意:如果需要触发flink的窗口调用,必须满足两个条件
1:waterMarkTime  >   eventTime
2:窗口内有数据

数据输入测验:
按照十秒钟统计一次,我们程序会将时间划分成为以下时间间隔段
2019-10-01 10:11:00  到  2019-10-01 10:11:10
2019-10-01 10:11:10  到  2019-10-01 10:11:20
2019-10-01 10:11:20  到  2019-10-01 10:11:30
2019-10-01 10:11:30  到  2019-10-01 10:11:40
2019-10-01 10:11:40  到  2019-10-01 10:11:50
2019-10-01 10:11:50  到  2019-10-01 10:12:00
	
顺序计算:
触发数据计算的条件依据为两个
第一个waterMark时间大于数据的eventTime时间,第二个窗口之内有数据
我们这里的waterMark直接使用eventTime的最大值减去10秒钟

0001 1569895882000	 数据eventTime为:2019-10-01 10:11:22  数据waterMark为  2019-10-01 10:11:12
0001 1569895885000	 数据eventTime为:2019-10-01 10:11:25  数据waterMark为  2019-10-01 10:11:15
0001 1569895888000	 数据eventTime为:2019-10-01 10:11:28  数据waterMark为  2019-10-01 10:11:18

0001 1569895890000	 数据eventTime为:2019-10-01 10:11:30  数据waterMark为  2019-10-01 10:11:20
0001 1569895891000	 数据eventTime为:2019-10-01 10:11:31  数据waterMark为  2019-10-01 10:11:21
0001 1569895895000	 数据eventTime为:2019-10-01 10:11:35  数据waterMark为  2019-10-01 10:11:25
0001 1569895898000	 数据eventTime为:2019-10-01 10:11:38  数据waterMark为  2019-10-01 10:11:28

0001 1569895900000	 数据eventTime为:2019-10-01 10:11:40  数据waterMark为  2019-10-01 10:11:30  触发第一条到第三条数据计算,数据包前不包后,不会计算2019-10-01 10:11:30 这条数据
0001 1569895911000	 数据eventTime为:2019-10-01 10:11:51  数据waterMark为  2019-10-01 10:11:41  触发2019-10-01 10:11:20到2019-10-01 10:11:28时间段的额数据计算,数据包前不包后,不会触发2019-10-01 10:11:30这条数据的计算

5、watermark处理乱序数据
输入测验数据
接着继续输入以下乱序数据,验证flink乱序数据的问题是否能够解决

乱序数据
0001 1569895948000	 数据eventTime为:2019-10-01 10:12:28  数据waterMark为  2019-10-01 10:12:18  
0001 1569895945000	 数据eventTime为:2019-10-01 10:12:25  数据waterMark为  2019-10-01 10:12:18  
0001 1569895947000	 数据eventTime为:2019-10-01 10:12:27  数据waterMark为  2019-10-01 10:12:18  

0001 1569895950000	 数据eventTime为:2019-10-01 10:12:30  数据waterMark为  2019-10-01 10:12:20  

0001 1569895960000	 数据eventTime为:2019-10-01 10:12:40  数据waterMark为  2019-10-01 10:12:30  触发计算 waterMark > eventTime 并且窗口内有数据,触发 2019-10-01 10:12:28到2019-10-01 10:12:27 这三条数据的计算,数据包前不包后,不会触发2019-10-01 10:12:30 这条数据的计算
0001 1569895949000	 数据eventTime为:2019-10-01 10:12:29  数据waterMark为  2019-10-01 10:12:30  迟到太多的数据,flink直接丢弃,可以设置flink将这些迟到太多的数据保存起来,便于排查问题







6、比watermark更晚的数据如何解决
如果我们设置数据的watermark为每条数据的eventtime往后一定的时间,例如数据的eventtime为2019-08-20 15:30:30,程序的window窗口为10s,然后我们设置的watermark为2019-08-20 15:30:40,
那么如果某一条数据eventtime为2019-08-20 15:30:32,到达flink程序的时间为2019-08-20 15:30:45 该怎么办,这条数据比窗口的watermark时间还要晚了5S钟该怎么办??对于这种比watermark还要晚的数据,flink有三种处理方式
1、直接丢弃
我们输入一个乱序很多的(其实只要 Event Time < watermark 时间)数据来测试下: 输入:【输入两条内容】
late element
0001 1569895948000	 数据eventTime为:2019-10-01 10:12:28  数据直接丢弃 
0001 1569895945000	 数据eventTime为:2019-10-01 10:12:25  数据直接丢弃
注意:此时并没有触发 window。因为输入的数据所在的窗口已经执行过了,flink 默认对这 些迟到的数据的处理方案就是丢弃。
2、allowedLateness 指定允许数据延迟的时间
在某些情况下,我们希望对迟到的数据再提供一个宽容的时间。
Flink 提供了 allowedLateness 方法可以实现对迟到的数据设置一个延迟时间,在指定延迟时间内到达的数据还是可以触发 window 执行的。

修改代码:

waterMarkStream
  .keyBy(0)
  .window(TumblingEventTimeWindows.of(Time.seconds(3)))
  .allowedLateness(Time.seconds(2))//允许数据迟到2S
  //function: (K, W, Iterable[T], Collector[R]) => Unit
  .apply(new MyWindowFunction).print()

验证数据迟到性:
输入数据:

更改代码之后重启我们的程序,然后从新输入之前的数据
0001 1569895882000
0001 1569895885000
0001 1569895888000
0001 1569895890000
0001 1569895891000
0001 1569895895000
0001 1569895898000
0001 1569895900000
0001 1569895911000
0001 1569895948000
0001 1569895945000
0001 1569895947000
0001 1569895950000
0001 1569895960000
0001 1569895949000


验证数据的延迟性:定义数据仅仅延迟2S的数据重新接收,重新计算
0001 1569895948000	 数据eventTime为:2019-10-01 10:12:28  触发数据计算  数据waterMark为  2019-10-01 10:12:30
0001 1569895945000	 数据eventTime为:2019-10-01 10:12:25  触发数据计算  数据waterMark为  2019-10-01 10:12:30


0001 1569895958000	 数据eventTime为:2019-10-01 10:12:38  不会触发数据计算 数据waterMark为  2019-10-01 10:12:30  waterMarkTime  <  eventTime,所以不会触发计算

将数据的waterMark调整为41秒就可以触发上面这条数据的计算了
0001 1569895971000	 数据eventTime为:2019-10-01 10:12:51  数据waterMark为  2019-10-01 10:12:41
又会继续触发0001 1569895958000	这条数据的计算了


3、sideOutputLateData 收集迟到的数据
通过 sideOutputLateData 可以把迟到的数据统一收集,统一存储,方便后期排查问题。 需要先调整代码:
import java.text.SimpleDateFormat

import org.apache.flink.api.java.tuple.Tuple
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.datastream.DataStreamSink
import org.apache.flink.streaming.api.functions.AssignerWithPunctuatedWatermarks
import org.apache.flink.streaming.api.scala.function.WindowFunction
import org.apache.flink.streaming.api.scala.{DataStream, OutputTag, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.watermark.Watermark
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector

import scala.collection.mutable.ArrayBuffer
import scala.util.Sorting


object FlinkWaterMark {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    //设置time类型为eventtime
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    //暂时定义并行度为1
    env.setParallelism(1)
    val text = env.socketTextStream("node01",9000)
    val inputMap: DataStream[(String, Long)] = text.map(line => {
      val arr = line.split(" ")
      (arr(0), arr(1).toLong)
    })

    //给我们的数据注册waterMark
    val waterMarkStream: DataStream[(String, Long)] = inputMap
      .assignTimestampsAndWatermarks(new AssignerWithPunctuatedWatermarks[(String, Long)] {
      var currentMaxTimestamp = 0L

      //watermark基于eventTime向后推迟10秒钟,允许消息最大乱序时间为10s
      val waterMarkDiff: Long = 10000L

      val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
      //获取下一个水印
      override def checkAndGetNextWatermark(t: (String, Long), l: Long): Watermark = {
        val watermark = new Watermark(currentMaxTimestamp - waterMarkDiff)
        watermark
      }
      //抽取当前数据的时间作为eventTime
      override def extractTimestamp(element: (String, Long), l: Long): Long = {
        val eventTime = element._2
        currentMaxTimestamp = Math.max(eventTime, currentMaxTimestamp)
        val id = Thread.currentThread().getId
        println("currentThreadId:"+id+",key:"+element._1+",eventtime:["+element._2+"|"+sdf.format(element._2)+"],currentMaxTimestamp:["+currentMaxTimestamp+"|"+ sdf.format(currentMaxTimestamp)+"],watermark:["+this.checkAndGetNextWatermark(element,l).getTimestamp+"|"+sdf.format(this.checkAndGetNextWatermark(element,l).getTimestamp)+"]")
        eventTime
      }
    })


    val outputTag: OutputTag[(String, Long)] = new OutputTag[(String,Long)]("late_data")
    val outputWindow: DataStream[String] = waterMarkStream
      .keyBy(0)
      .window(TumblingEventTimeWindows.of(Time.seconds(3)))
      // .allowedLateness(Time.seconds(2))//允许数据迟到2S
      .sideOutputLateData(outputTag)
      //function: (K, W, Iterable[T], Collector[R]) => Unit
      .apply(new MyWindowFunction)


    val sideOuptut: DataStream[(String, Long)] = outputWindow.getSideOutput(outputTag)

    sideOuptut.print()
    outputWindow.print()

    //执行程序
    env.execute()

  }
}

class MyWindowFunction extends WindowFunction[(String,Long),String,Tuple,TimeWindow]{
  override def apply(key: Tuple, window: TimeWindow, input: Iterable[(String, Long)], out: Collector[String]): Unit = {
    val keyStr = key.toString
    val arrBuf = ArrayBuffer[Long]()
    val ite = input.iterator
    while (ite.hasNext){
      val tup2 = ite.next()
      arrBuf.append(tup2._2)
    }
    val arr = arrBuf.toArray
    Sorting.quickSort(arr)
    val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    val result = keyStr + "," + arr.length + "," + sdf.format(arr.head) + "," + sdf.format(arr.last)+ "," + sdf.format(window.getStart) + "," + sdf.format(window.getEnd)
    out.collect(result)
  }
}

我们来输入一些数据验证一下 输入:
0001 1569895882000
0001 1569895885000
0001 1569895888000
0001 1569895890000
0001 1569895891000
0001 1569895895000
0001 1569895898000
0001 1569895900000
0001 1569895911000
0001 1569895948000
0001 1569895945000
0001 1569895947000
0001 1569895950000
0001 1569895960000
0001 1569895949000
输入两条迟到的数据,会被收集起来
0001 1569895948000
0001 1569895945000

此时,针对这几条迟到的数据,都通过 sideOutputLateData 保存到了 outputTag 中。

7、多并行度的watermark机制
前面代码中设置了并行度为 1 
env.setParallelism(1);

如果这里不设置的话,代码在运行的时候会默认读取本机 CPU 数量设置并行度。 把代码的并行度代码注释掉

//env.setParallelism(1)
然后在输出内容前面加上线程 id

会出现如下数据: 输入如下几行内容:






输出:


会发现 window 没有被触发。
因为此时,这 7 条数据都是被不同的线程处理的。每个线程都有一个 watermark。
因为在多并行度的情况下,watermark 对齐会取所有 channel 最小的 watermark 但是我们现在默认有 8 个并行度,这 7 条数据都被不同的线程所处理,到现在还没获取到最 小的 watermark,所以 window 无法被触发执行。

下面我们来验证一下,把代码中的并行度调整为 2.
env.setParallelism(2)
输入如下内容:
0001 1569895890000
0001 1569895903000
0001 1569895908000
输出:

此时会发现,当第三条数据输入完以后,[10:11:30,10:11:33)这个 window 被触发了。 前两条数据输入之后,获取到的最小 watermark 是 10:11:20,这个时候对应的 window 中没 有数据。
第三条数据输入之后,获取到的最小 watermark 是 10:11:33,这个时候对应的窗口就是 [10:11:30,10:11:33)。所以就触发了。

10、Flink的dataStream的状态保存和恢复
我们前面写的word count的例子,没有包含状态管理。如果一个task在处理过程中挂掉了,那么它在内存中的状态都会丢失,所有的数据都需要重新计算。从容错和消息处理的语义上(at least once, exactly once),Flink引入了state和checkpoint。
首先区分一下两个概念
state一般指一个具体的task/operator的状态【state数据默认保存在java的堆内存中】
而checkpoint【可以理解为checkpoint是把state数据持久化存储了】,则表示了一个Flink Job在一个特定时刻的一份全局状态快照,即包含了所有task/operator的状态
注意:task是Flink中执行的基本单位。operator指算子(transformation)。
State可以被记录,在失败的情况下数据还可以恢复
Flink中有两种基本类型的State
Keyed State
Operator State
针对两种state,每种state都有两种方式存在
?原始状态(raw state)
?托管状态(managed state)
托管状态是由Flink框架管理的状态
而原始状态,由用户自行管理状态具体的数据结构,框架在做checkpoint的时候,使用byte[]来读写状态内容,对其内部数据结构一无所知。
通常在DataStream上的状态推荐使用托管的状态,当实现一个用户自定义的operator时,会使用到原始状态
flink官网关于state的介绍
https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/state/state.html#using-managed-operator-state

1、keyed state的托管状态
顾名思义,就是基于KeyedStream上的状态。这个状态是跟特定的key绑定的,对KeyedStream流上的每一个key,都对应一个state。stream.keyBy(…)
保存state的数据结构
ValueState<T>:即类型为T的单值状态。这个状态与对应的key绑定,是最简单的状态了。它可以通过update方法更新状态值,通过value()方法获取状态值
ListState<T>:即key上的状态值为一个列表。可以通过add方法往列表中附加值;也可以通过get()方法返回一个Iterable<T>来遍历状态值
ReducingState<T>:这种状态通过用户传入的reduceFunction,每次调用add方法添加值的时候,会调用reduceFunction,最后合并到一个单一的状态值
MapState<UK, UV>:即状态值为一个map。用户通过put或putAll方法添加元素
需要注意的是,以上所述的State对象,仅仅用于与状态进行交互(更新、删除、清空等),而真正的状态值,有可能是存在内存、磁盘、或者其他分布式存储系统中。相当于我们只是持有了这个状态的句柄
2、operator state托管状态
对于与key无关的dataStream可以进行状态托管,与算子进行绑定,对我们的数据进行处理
与Key无关的State,与Operator绑定的state,整个operator只对应一个state保存state的数据结构一般使用ListState<T>
举例来说,Flink中的Kafka Connector,就使用了operator state。它会在每个connector实例中,保存该实例中消费topic的所有(partition, offset)映射

3、Flink的checkPoint保存数据
1、checkPoint的基本概念
为了保证state的容错性,Flink需要对state进行checkpoint。
Checkpoint是Flink实现容错机制最核心的功能,它能够根据配置周期性地基于Stream中各个Operator/task的状态来生成快照,从而将这些状态数据定期持久化存储下来,当Flink程序一旦意外崩溃时,重新运行程序时可以有选择地从这些快照进行恢复,从而修正因为故障带来的程序数据异常
2、checkPoint的前提
Flink的checkpoint机制可以与(stream和state)的持久化存储交互的前提:
1、持久化的source,它需要支持在一定时间内重放事件。这种sources的典型例子是持久化的消息队列(比如Apache Kafka,RabbitMQ等)或文件系统(比如HDFS,S3,GFS等)
2、用于state的持久化存储,例如分布式文件系统(比如HDFS,S3,GFS等)
3、Flink进行checkpoint需要的步骤
1.暂停新数据的输入
2.等待流中on-the-fly的数据被处理干净,此时得到flink graph的一个snapshot
3.将所有Task中的State拷贝到State Backend中,如HDFS。此动作由各个Task Manager完成
4.各个Task Manager将Task State的位置上报给Job Manager,完成checkpoint
5.恢复数据的输入
如上所述,这里才需要“暂停输入+排干on-the-fly数据”的操作,这样才能拿到同一时刻下所有subtask的state
4、配置checkPoint
默认checkpoint功能是disabled的,想要使用的时候需要先启用
checkpoint开启之后,默认的checkPointMode是Exactly-once
checkpoint的checkPointMode有两种,Exactly-once和At-least-once
Exactly-once对于大多数应用来说是最合适的。At-least-once可能用在某些延迟超低的应用程序(始终延迟为几毫秒)
//默认checkpoint功能是disabled的,想要使用的时候需要先启用
// 每隔1000 ms进行启动一个检查点【设置checkpoint的周期】
environment.enableCheckpointing(1000);
// 高级选项:
// 设置模式为exactly-once (这是默认值)
environment.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 确保检查点之间有至少500 ms的间隔【checkpoint最小间隔】
environment.getCheckpointConfig.setMinPauseBetweenCheckpoints(500);
// 检查点必须在一分钟内完成,或者被丢弃【checkpoint的超时时间】
environment.getCheckpointConfig.setCheckpointTimeout(60000);
// 同一时间只允许进行一个检查点
environment.getCheckpointConfig.setMaxConcurrentCheckpoints(1);
// 表示一旦Flink处理程序被cancel后,会保留Checkpoint数据,以便根据实际需要恢复到指定的Checkpoint【详细解释见备注】

/**
  * ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION:表示一旦Flink处理程序被cancel后,会保留Checkpoint数据,以便根据实际需要恢复到指定的Checkpoint
  * ExternalizedCheckpointCleanup.DELETE_ON_CANCELLATION: 表示一旦Flink处理程序被cancel后,会删除Checkpoint数据,只有job执行失败的时候才会保存checkpoint
  */
environment.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);

5、Flink的checkPoint状态管理之State Backend
默认情况下,state会保存在taskmanager的内存中,checkpoint会存储在JobManager的内存中。
state 的store和checkpoint的位置取决于State Backend的配置
env.setStateBackend(…)
一共有三种State Backend
MemoryStateBackend   # 内存存储
FsStateBackend    #  文件系统存储
RocksDBStateBackend   # rocksDB是一个数据库
1、MemoryStateBackend
将数据持久化状态存储到内存当中,state数据保存在java堆内存中,执行checkpoint的时候,会把state的快照数据保存到jobmanager的内存中。基于内存的state backend在生产环境下不建议使用
代码配置:
// environment.setStateBackend(new MemoryStateBackend())

2、FsStateBackend
state数据保存在taskmanager的内存中,执行checkpoint的时候,会把state的快照数据保存到配置的文件系统中。可以使用hdfs等分布式文件系统
代码配置:
//environment.setStateBackend(new FsStateBackend("hdfs://node01:8020"))
3、RocksDBStateBackend   
RocksDB介绍:RocksDB使用一套日志结构的数据库引擎,为了更好的性能,这套引擎是用C++编写的。 Key和value是任意大小的字节流。RocksDB跟上面的都略有不同,它会在本地文件系统中维护状态,state会直接写入本地rocksdb中。同时它需要配置一个远端的filesystem uri(一般是HDFS),在做checkpoint的时候,会把本地的数据直接复制到filesystem中。fail over的时候从filesystem中恢复到本地RocksDB克服了state受内存限制的缺点,同时又能够持久化到远端文件系统中,比较适合在生产中使用
代码配置:导入jar包然后配置代码
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-statebackend-rocksdb_2.11</artifactId>
    <version>1.8.1</version>
</dependency>
配置代码
environment.setStateBackend(new RocksDBStateBackend("hdfs://node01:8020/flink/checkDir",true))
4、修改state-backend的两种方式
修改State Backend的两种方式
第一种:单任务调整
修改当前任务代码
env.setStateBackend(new FsStateBackend("hdfs://node01:8020/flink/checkpoints"));
或者new MemoryStateBackend()
或者new RocksDBStateBackend(filebackend, true);【需要添加第三方依赖】
第二种:全局调整
修改flink-conf.yaml
state.backend: filesystem
state.checkpoints.dir: hdfs://namenode:9000/flink/checkpoints
注意:state.backend的值可以是下面几种:
jobmanager(MemoryStateBackend), 
filesystem(FsStateBackend),
rocksdb(RocksDBStateBackend)
6、从checkPoint恢复数据以及checkPoint保存多个
保存多个历史版本
默认情况下,如果设置了Checkpoint选项,则Flink只保留最近成功生成的1个Checkpoint,而当Flink程序失败时,可以从最近的这个Checkpoint来进行恢复。但是,如果我们希望保留多个Checkpoint,并能够根据实际需要选择其中一个进行恢复,这样会更加灵活,比如,我们发现最近4个小时数据记录处理有问题,希望将整个状态还原到4小时之前
Flink可以支持保留多个Checkpoint,需要在Flink的配置文件conf/flink-conf.yaml中,添加如下配置,指定最多需要保存Checkpoint的个数
state.checkpoints.num-retained: 20
这样设置以后就查看对应的Checkpoint在HDFS上存储的文件目录
hdfs dfs -ls hdfs://node01:8020/flink/checkpoints
如果希望回退到某个Checkpoint点,只需要指定对应的某个Checkpoint路径即可实现
恢复历史某个版本数据
如果Flink程序异常失败,或者最近一段时间内数据处理错误,我们可以将程序从某一个Checkpoint点进行恢复
bin/flink run -s hdfs://node01:8020/flink/checkpoints/467e17d2cc343e6c56255d222bae3421/chk-56/_metadata flink-job.jar
程序正常运行后,还会按照Checkpoint配置进行运行,继续生成Checkpoint数据


4、Flink的savePoint保存数据
savePoint的介绍
Flink通过Savepoint功能可以做到程序升级后,继续从升级前的那个点开始执行计算,保证数据不中断。
全局,一致性快照。可以保存数据源offset,operator操作状态等信息,可以从应用在过去任意做了savepoint的时刻开始继续消费
用户手动执行,是指向Checkpoint的指针,不会过期
在程序升级的情况下使用
注意:为了能够在作业的不同版本之间以及 Flink 的不同版本之间顺利升级,强烈推荐程序员通过 uid(String) 方法手动的给算子赋予 ID,这些 ID 将用于确定每一个算子的状态范围。如果不手动给各算子指定 ID,则会由 Flink 自动给每个算子生成一个 ID。只要这些 ID 没有改变就能从保存点(savepoint)将程序恢复回来。而这些自动生成的 ID 依赖于程序的结构,并且对代码的更改是很敏感的。因此,强烈建议用户手动的设置 ID。

savePoint的使用
1:在flink-conf.yaml中配置Savepoint存储位置
不是必须设置,但是设置后,后面创建指定Job的Savepoint时,可以不用在手动执行命令时指定Savepoint的位置
state.savepoints.dir: hdfs://node01:8020/flink/savepoints
2:触发一个savepoint【直接触发或者在cancel的时候触发】
bin/flink savepoint jobId [targetDirectory] [-yid yarnAppId]【针对on yarn模式需要指定-yid参数】

bin/flink cancel -s [targetDirectory] jobId [-yid yarnAppId]【针对on yarn模式需要指定-yid参数】
3:从指定的savepoint启动job
bin/flink run -s savepointPath [runArgs]
11、Flink的DataStream集成kafka
对于实时处理当中,我们实际工作当中的数据源一般都是使用kafka,所以我们一起来看看如何通过Flink来集成kafka
flink提供了一个特有的kafka connector去读写kafka topic的数据。flink消费kafka数据,并不是完全通过跟踪kafka消费组的offset来实现去保证exactly-once的语义,而是flink内部去跟踪offset和做checkpoint去实现exactly-once的语义,而且对于kafka的partition,Flink会启动对应的并行度去处理kafka当中的每个分区的数据
flink整合kafka官网介绍
https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/connectors/kafka.html
第一步:导入jar包
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-kafka-0.11_2.11</artifactId>
    <version>1.8.1</version>
</dependency>
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>1.1.0</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>



第二步:将kafka作为flink的source来使用
实际工作当中一般都是将kafka作为flink的source来使用
创建kafka的topic
安装好kafka集群,并启动kafka集群,然后在node01执行以下命令创建kafka的topic为test
cd /kkb/install/kafka_2.11-1.1.0
bin/kafka-topics.sh --create --partitions 3 --topic test --replication-factor 1 --zookeeper node01:2181,node02:2181,node03:2181
代码实现:
import java.util.Properties
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.contrib.streaming.state.RocksDBStateBackend
import org.apache.flink.streaming.api.CheckpointingMode
import org.apache.flink.streaming.api.environment.CheckpointConfig
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer011

object FlinkKafkaSource {

  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    //隐式转换
    import org.apache.flink.api.scala._
    //checkpoint配置
    env.enableCheckpointing(100);
    env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
    env.getCheckpointConfig.setMinPauseBetweenCheckpoints(500);
    env.getCheckpointConfig.setCheckpointTimeout(60000);
    env.getCheckpointConfig.setMaxConcurrentCheckpoints(1);
    env.getCheckpointConfig.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
    val topic = "test"
    val prop = new Properties()
    prop.setProperty("bootstrap.servers","node01:9092")
    prop.setProperty("group.id","con1")
    prop.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
    prop.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

    var kafkaSoruce: FlinkKafkaConsumer011[String] = new FlinkKafkaConsumer011[String](topic, new SimpleStringSchema(), prop)

    kafkaSoruce.setCommitOffsetsOnCheckpoints(true)
    //设置statebackend
    env.setStateBackend(new RocksDBStateBackend("hdfs://node01:8020/flink_kafka/checkpoints",true));
    val result: DataStream[String] = env.addSource(kafkaSoruce)
    result.print()
    env.execute()
  }
}
kafka生产数据
node01执行以下命令,通过shell命令行来生产数据到kafka当中去
cd /kkb/install/kafka_2.11-1.1.0
bin/kafka-console-producer.sh --broker-list node01:9092,node02:9092,node03:9092 --topic  test
第三步:将kafka作为flink的sink来使用
我们也可以将kafka作为flink的sink来使用,就是将flink处理完成之后的数据写入到kafka当中去
代码实现
import java.util.Properties
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.contrib.streaming.state.RocksDBStateBackend
import org.apache.flink.streaming.api.CheckpointingMode
import org.apache.flink.streaming.api.environment.CheckpointConfig
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer011
import org.apache.flink.streaming.connectors.kafka.internals.KeyedSerializationSchemaWrapper

object FlinkKafkaSink {
  def main(args: Array[String]): Unit = {
      val env = StreamExecutionEnvironment.getExecutionEnvironment
      //隐式转换
      import org.apache.flink.api.scala._
      //checkpoint配置
      env.enableCheckpointing(5000);
      env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
      env.getCheckpointConfig.setMinPauseBetweenCheckpoints(500);
      env.getCheckpointConfig.setCheckpointTimeout(60000);
      env.getCheckpointConfig.setMaxConcurrentCheckpoints(1);
      env.getCheckpointConfig.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
      //设置statebackend
      env.setStateBackend(new RocksDBStateBackend("hdfs://node01:8020/flink_kafka_sink/checkpoints",true));


      val text = env.socketTextStream("node01",9000)

      val topic = "test"
      val prop = new Properties()
      prop.setProperty("bootstrap.servers","node01:9092")
      prop.setProperty("group.id","kafka_group1")


      //第一种解决方案,设置FlinkKafkaProducer011里面的事务超时时间
      //设置事务超时时间
      prop.setProperty("transaction.timeout.ms",60000*15+"");
      //第二种解决方案,设置kafka的最大事务超时时间
      //FlinkKafkaProducer011<String> myProducer = new FlinkKafkaProducer011<>(brokerList, topic, new SimpleStringSchema());
      //使用支持仅一次语义的形式
      val myProducer = new FlinkKafkaProducer011[String](topic,new KeyedSerializationSchemaWrapper[String](new SimpleStringSchema()), prop, FlinkKafkaProducer011.Semantic.EXACTLY_ONCE)
      text.addSink(myProducer)
      env.execute("StreamingFromCollectionScala")
  }
}

启动socket服务发送数据
node01执行以下命令,发送数据到socket服务里面去
nc -lk  9000
启动kafka消费者
node01执行以下命令启动kafka消费者,消费数据
bin/kafka-console-consumer.sh --bootstrap-server node01:9092,node02:9092 --topic test


12、Flink批量处理之DataSet
flink不仅可以支持实时流式处理,它也可以支持批量处理,其中批量处理也可以看作是实时处理的一个特殊情况
1、dataSet的内置数据源
基于文件数据源:
readTextFile(path) / TextInputFormat:逐行读取文件并将其作为字符串(String)返回 
readTextFileWithValue(path) / TextValueInputFormat:逐行读取文件并将其作为StringValue返回。StringValue是Flink对String的封装,可变、可序列化,一定程度上提高性能。 
readCsvFile(path) / CsvInputFormat:解析以逗号(或其他字符)分隔字段的文件。返回元组或pojo 
readFileOfPrimitives(path, Class) / PrimitiveInputFormat 
readFileOfPrimitives(path, delimiter, Class) / PrimitiveInputFormat 跟readCsvFile类似,只不过以原生类型返回而不是Tuple。 
readSequenceFile(Key, Value, path) / SequenceFileInputFormat:读取SequenceFile,以Tuple2<Key, Value>返回


基于集合数据源:

fromCollection(Collection) 
fromCollection(Iterator, Class) 
fromElements(T ...) 
fromParallelCollection(SplittableIterator, Class) 
generateSequence(from, to)


通用数据源:
readFile(inputFormat, path) / FileInputFormat 
createInput(inputFormat) / InputFormat

文件数据源
入门案例就是基于文件数据源,如果需要对文件夹进行递归,那么我们也可以使用参数来对文件夹下面的多级文件夹进行递归

import org.apache.flink.api.scala.{AggregateDataSet, DataSet, ExecutionEnvironment}
object BatchOperate {
  def main(args: Array[String]): Unit = {
    val inputPath = "D:\\count.txt"
    val outPut = "D:\\data\\result2"

val configuration: Configuration = new Configuration()
configuration.setBoolean("recursive.file.enumeration",true)

    //获取程序入口类ExecutionEnvironment
    val env = ExecutionEnvironment.getExecutionEnvironment
    val text = env.readTextFile(inputPath) .withParameters(configuration)

    //引入隐式转换
    import org.apache.flink.api.scala._
    val value: AggregateDataSet[(String, Int)] = text.flatMap(x => x.split(" ")).map(x =>(x,1)).groupBy(0).sum(1)
    value.writeAsText("d:\\datas\\result.txt").setParallelism(1)
    env.execute("batch word count")
  }
}

集合数据源
import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment}

object DataSetSource {

  def main(args: Array[String]): Unit = {
    //获取批量处理程序入口类ExecutionEnvironment
    val environment: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment

    import org.apache.flink.api.scala._
    //从集合当中创建dataSet
    val myArray = Array("hello world","spark flink")
    val collectionSet: DataSet[String] = environment.fromCollection(myArray)
    val result: AggregateDataSet[(String, Int)] = collectionSet.flatMap(x => x.split(" ")).map(x =>(x,1)).groupBy(0).sum(1)
    result.setParallelism(1).print()
   // result.writeAsText("c:\\HELLO.TXT")
    environment.execute()
  }

}


2、dataSet的算子介绍
官网算子介绍:
https://ci.apache.org/projects/flink/flink-docs-master/dev/batch/dataset_transformations.html
dataSet的transformation算子
Map:输入一个元素,然后返回一个元素,中间可以做一些清洗转换等操作
FlatMap:输入一个元素,可以返回零个,一个或者多个元素
MapPartition:类似map,一次处理一个分区的数据【如果在进行map处理的时候需要获取第三方资源链接,建议使用MapPartition】
Filter:过滤函数,对传入的数据进行判断,符合条件的数据会被留下
Reduce:对数据进行聚合操作,结合当前元素和上一次reduce返回的值进行聚合操作,然后返回一个新的值
Aggregate:sum、max、min等
Distinct:返回一个数据集中去重之后的元素,data.distinct()
Join:内连接
OuterJoin:外链接
需求一:使用mapPartition将数据保存到数据库
第一步:导入mysql的jar包坐标
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.38</version>
</dependency>

第二步:创建mysql数据库以及数据库表
/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`flink_db` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `flink_db`;

/*Table structure for table `user` */

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
第三步:代码开发
import java.sql.PreparedStatement
import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment}
object MapPartition2MySql {
  def main(args: Array[String]): Unit = {
    val environment: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._

    val sourceDataset: DataSet[String] = environment.fromElements("1 zhangsan","2 lisi","3 wangwu")
    sourceDataset.mapPartition(part => {
      Class.forName("com.mysql.jdbc.Driver").newInstance()
      val conn = java.sql.DriverManager.getConnection("jdbc:mysql://localhost:3306/flink_db", "root", "123456")
      part.map(x => {
        val statement: PreparedStatement = conn.prepareStatement("insert into user (id,name) values(?,?)")
        statement.setInt(1, x.split(" ")(0).toInt)
        statement.setString(2, x.split(" ")(1))
        statement.execute()
      })
    }).print()
    environment.execute()

  }
}




需求二:连接操作
左外连接,右外连接,满外连接等算子的操作可以实现对两个dataset进行join操作,按照我们指定的条件进行join
object BatchDemoOuterJoinScala {

  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,"zs"))
    data1.append((2,"ls"))
    data1.append((3,"ww"))


    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()

    println("===============================")

    text1.rightOuterJoin(text2).where(0).equalTo(0).apply((first,second)=>{
      if(first==null){
        (second._1,"null",second._2)
      }else{
        (first._1,first._2,second._2)
      }
    }).print()


    println("===============================")

    text1.fullOuterJoin(text2).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()




  }

}


dataSet的partition算子
Rebalance:对数据集进行再平衡,重分区,消除数据倾斜
Hash-Partition:根据指定key的哈希值对数据集进行分区
partitionByHash()
Range-Partition:根据指定的key对数据集进行范围分区
.partitionByRange()
Custom Partitioning:自定义分区规则,自定义分区需要实现Partitioner接口partitionCustom(partitioner, "someKey")或者partitionCustom(partitioner, 0)


在flink批量处理当中,分区算子主要涉及到rebalance,partitionByHash
,partitionByRange以及partitionCustom来进行分区
object FlinkPartition {
  def main(args: Array[String]): Unit = {
    val environment: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    environment.setParallelism(2)
    import org.apache.flink.api.scala._
    val sourceDataSet: DataSet[String] = environment.fromElements("hello world","spark flink","hive sqoop")
    val filterSet: DataSet[String] = sourceDataSet.filter(x => x.contains("hello"))
      .rebalance()
    filterSet.print()
    environment.execute()
  }
}
自定义分区来实现数据分区操作
第一步:自定义分区scala的class类
import org.apache.flink.api.common.functions.Partitioner

class MyPartitioner2  extends Partitioner[String]{
  override def partition(word: String, num: Int): Int = {
    println("分区个数为" +  num)
    if(word.contains("hello")){
      println("0号分区")
      0
    }else{
      println("1号分区")
      1
    }
  }
}
第二步:代码实现
import org.apache.flink.api.scala.ExecutionEnvironment

object FlinkCustomerPartition {
  def main(args: Array[String]): Unit = {
    val environment: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
      //设置我们的分区数,如果不设置,默认使用CPU核数作为分区个数
    environment.setParallelism(2)
    import  org.apache.flink.api.scala._
    //获取dataset
    val sourceDataSet: DataSet[String] = environment.fromElements("hello world","spark flink","hello world","hive hadoop")
    val result: DataSet[String] = sourceDataSet.partitionCustom(new MyPartitioner2,x => x + "")
    val value: DataSet[String] = result.map(x => {
      println("数据的key为" + x + "线程为" + Thread.currentThread().getId)
      x
    })
    value.print()
    environment.execute()
  }
}
dataSet的sink算子
1、writeAsText() / TextOutputFormat:以字符串的形式逐行写入元素。字符串是通过调用每个元素的toString()方法获得的
2、writeAsFormattedText() / TextOutputFormat:以字符串的形式逐行写入元素。字符串是通过为每个元素调用用户定义的format()方法获得的。
3、writeAsCsv(...) / CsvOutputFormat:将元组写入以逗号分隔的文件。行和字段分隔符是可配置的。每个字段的值来自对象的toString()方法。
4、print() / printToErr() / print(String msg) / printToErr(String msg) ()(注: 线上应用杜绝使用,采用抽样打印或者日志的方式)
5、write() / FileOutputFormat
6、output()/ OutputFormat:通用的输出方法,用于不基于文件的数据接收器(如将结果存储在数据库中)。
3、dataSet的参数传递
在dataSet代码当中,经常用到一些参数,我们可以通过构造器的方式传递参数,或者使用withParameters方法来进行参数传递,或者使用ExecutionConfig来进行参数传递

1、使用构造器来传递参数
object FlinkParameter {

  def main(args: Array[String]): Unit = {
    val env=ExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    val sourceSet: DataSet[String] = env.fromElements("hello world","abc test")
    val filterSet: DataSet[String] = sourceSet.filter(new MyFilterFunction("test"))
    filterSet.print()
    env.execute()
  }
}

class MyFilterFunction (parameter:String) extends FilterFunction[String]{
  override def filter(t: String): Boolean = {
    if(t.contains(parameter)){
      true
    }else{
      false
    }
  }
}





2、使用withParameters来传递参数
import org.apache.flink.api.common.functions.{FilterFunction, RichFilterFunction}
import org.apache.flink.api.scala.ExecutionEnvironment
import org.apache.flink.configuration.Configuration

object FlinkParameter {
  def main(args: Array[String]): Unit = {
    val env=ExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    val sourceSet: DataSet[String] = env.fromElements("hello world","abc test")
    val configuration = new Configuration()
    configuration.setString("parameterKey","test")
    val filterSet: DataSet[String] = sourceSet.filter(new MyFilter).withParameters(configuration)
    filterSet.print()
    env.execute()
  }
}
class MyFilter extends RichFilterFunction[String]{
  var value:String ="";
  override def open(parameters: Configuration): Unit = {
    value = parameters.getString("parameterKey","defaultValue")
  }
  override def filter(t: String): Boolean = {
    if(t.contains(value)){
      true
    }else{
      false
    }
  }
}


3、全局参数传递
import org.apache.flink.api.common.ExecutionConfig
import org.apache.flink.api.common.functions.{FilterFunction, RichFilterFunction}
import org.apache.flink.api.scala.ExecutionEnvironment
import org.apache.flink.configuration.Configuration

object FlinkParameter {
  def main(args: Array[String]): Unit = {
    val configuration = new Configuration()
    configuration.setString("parameterKey","test")

    val env=ExecutionEnvironment.getExecutionEnvironment
    env.getConfig.setGlobalJobParameters(configuration)
    import org.apache.flink.api.scala._
    val sourceSet: DataSet[String] = env.fromElements("hello world","abc test")

    val filterSet: DataSet[String] = sourceSet.filter(new MyFilter)
    filterSet.print()
    env.execute()
  }
}
class MyFilter extends RichFilterFunction[String]{
  var value:String ="";
  override def open(parameters: Configuration): Unit = {
    val parameters: ExecutionConfig.GlobalJobParameters = getRuntimeContext.getExecutionConfig.getGlobalJobParameters

    val globalConf:Configuration =  parameters.asInstanceOf[Configuration]
    value = globalConf.getString("parameterKey","test")
  }
  override def filter(t: String): Boolean = {
    if(t.contains(value)){
      true
    }else{
      false
    }
  }
}

4、Flink的dataSet connectors
1、文件系统connector
为了从文件系统读取数据,Flink内置了对以下文件系统的支持:

文件系统	Schema	备注
HDFS	hdfs://	Hdfs文件系统
S3	s3://	通过hadoop文件系统实现支持
MapR	maprfs://	需要用户添加jar
Alluxio	alluxio://	通过hadoop文件系统实现

注意:Flink允许用户使用实现org.apache.hadoop.fs.FileSystem接口的任何文件系统。例如S3、 Google Cloud Storage Connector for Hadoop、 Alluxio、 XtreemFS、 FTP等各种文件系统

Flink与Apache Hadoop MapReduce接口兼容,因此允许重用Hadoop MapReduce实现的代码:
使用Hadoop Writable data type
使用任何Hadoop InputFormat作为DataSource(flink内置HadoopInputFormat)
使用任何Hadoop OutputFormat作为DataSink(flink内置HadoopOutputFormat)
使用Hadoop Mapper作为FlatMapFunction
使用Hadoop Reducer作为GroupReduceFunction
2、Flink集成Hbase之数据读取
Flink也可以直接与hbase进行集成,将hbase作为Flink的source和sink等
第一步:创建hbase表并插入数据
create 'hbasesource','f1'
put 'hbasesource','0001','f1:name','zhangsan'
put 'hbasesource','0002','f1:age','18'

第二步:导入整合jar包
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-hadoop-compatibility_2.11</artifactId>
    <version>1.8.1</version>
</dependency>
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-shaded-hadoop2</artifactId>
<!-- 暂时没有1.8.1这个版本 -->
    <version>1.7.2</version>
</dependency>
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-hbase_2.11</artifactId>
    <version>1.8.1</version>
</dependency>
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>1.2.0-cdh5.14.2</version>
</dependency>

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-server</artifactId>
    <version>1.2.0-cdh5.14.2</version>
</dependency>
第三步:开发flink集成hbase读取hbase数据
import org.apache.flink.addons.hbase.TableInputFormat
import org.apache.flink.api.java.tuple
import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment}
import org.apache.flink.configuration.Configuration
import org.apache.hadoop.hbase.{Cell, HBaseConfiguration, HConstants, TableName}
import org.apache.hadoop.hbase.client._
import org.apache.hadoop.hbase.util.Bytes

object FlinkReadHBase {
  def main(args: Array[String]): Unit = {
    val environment: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment

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

    val hbaseData: DataSet[tuple.Tuple2[String, String]] = environment.createInput(new TableInputFormat[tuple.Tuple2[String, String]] {
      override def configure(parameters: Configuration): Unit = {
        val conf = HBaseConfiguration.create();
        conf.set(HConstants.ZOOKEEPER_QUORUM, "node01,node02,node03")
        conf.set(HConstants.ZOOKEEPER_CLIENT_PORT, "2181")
        val conn: Connection = ConnectionFactory.createConnection(conf)
        table = classOf[HTable].cast(conn.getTable(TableName.valueOf("hbasesource")))
        scan = new Scan() {
          // setStartRow(Bytes.toBytes("1001"))
          // setStopRow(Bytes.toBytes("1004"))
          addFamily(Bytes.toBytes("f1"))
        }
      }
      override def getScanner: Scan = {
        scan
      }
      override def getTableName: String = {
        "hbasesource"
      }
      override def mapResultToTuple(result: Result): tuple.Tuple2[String, String] = {
        val rowkey: String = Bytes.toString(result.getRow)
        val sb = new StringBuffer()
        for (cell: Cell <- result.rawCells()) {
          val value = Bytes.toString(cell.getValueArray, cell.getValueOffset, cell.getValueLength)
          sb.append(value).append(",")
        }
        val valueString = sb.replace(sb.length() - 1, sb.length(), "").toString
        val tuple2 = new org.apache.flink.api.java.tuple.Tuple2[String, String]
        tuple2.setField(rowkey, 0)
        tuple2.setField(valueString, 1)
        tuple2
      }


    })
    hbaseData.print()
    environment.execute()
  }
}


3、Flink读取数据,然后写入hbase
Flink也可以集成Hbase实现将数据写入到Hbase里面去
1.第一种:实现OutputFormat接口
2.第二种:继承RichSinkFunction重写父类方法

import java.util
import org.apache.flink.api.common.io.OutputFormat
import org.apache.flink.api.scala.{ExecutionEnvironment}
import org.apache.flink.configuration.Configuration
import org.apache.hadoop.hbase.{HBaseConfiguration, HConstants, TableName}
import org.apache.hadoop.hbase.client._
import org.apache.hadoop.hbase.util.Bytes

object FlinkWriteHBase {
  def main(args: Array[String]): Unit = {
    val environment: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    val sourceDataSet: DataSet[String] = environment.fromElements("01,zhangsan,28","02,lisi,30")
    sourceDataSet.output(new HBaseOutputFormat)
    environment.execute()
  }
}


class HBaseOutputFormat extends OutputFormat[String]{
  val zkServer = "node01"
  val port = "2181"
  var conn: Connection = null

  override def configure(configuration: Configuration): Unit = {

  }

  override def open(i: Int, i1: Int): Unit = {
    val config: org.apache.hadoop.conf.Configuration = HBaseConfiguration.create
    config.set(HConstants.ZOOKEEPER_QUORUM, zkServer)
    config.set(HConstants.ZOOKEEPER_CLIENT_PORT, port)
    config.setInt(HConstants.HBASE_CLIENT_OPERATION_TIMEOUT, 30000)
    config.setInt(HConstants.HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD, 30000)
    conn = ConnectionFactory.createConnection(config)
  }

  override def writeRecord(it: String): Unit = {
    val tableName: TableName = TableName.valueOf("hbasesource")
    val cf1 = "f1"
    val array: Array[String] = it.split(",")
    val put: Put = new Put(Bytes.toBytes(array(0)))
    put.addColumn(Bytes.toBytes(cf1), Bytes.toBytes("name"), Bytes.toBytes(array(1)))
    put.addColumn(Bytes.toBytes(cf1), Bytes.toBytes("age"), Bytes.toBytes(array(2)))
    val putList: util.ArrayList[Put] = new util.ArrayList[Put]
    putList.add(put)
    //设置缓存1m,当达到1m时数据会自动刷到hbase
    val params: BufferedMutatorParams = new BufferedMutatorParams(tableName)
    //设置缓存的大小
    params.writeBufferSize(1024 * 1024)
    val mutator: BufferedMutator = conn.getBufferedMutator(params)
    mutator.mutate(putList)
    mutator.flush()
    putList.clear()
  }
  override def close(): Unit = {
    if(null != conn){
      conn.close()
    }
  }
}


13、flink的广播变量,累加器,计数器以及分布式缓存
1、广播变量
广播变量主要分为两种方式:dataStream当中的广播变量以及dataSet当中的广播变量,这两个地方的广播变量还有一定的不一样的各自的特性,一句话解释,可以理解为是一个公共的共享变量,我们可以把一个dataset 数据集广播出去,然后不同的task在节点上都能够获取到,这个数据在每个节点上只会存在一份,节约内存
1、dataStream当中的广播分区
将数据广播给所有的分区,数据可能会被重复处理,一般用于某些公共的配置信息读取,不会涉及到更改的数据
将公共数据广播到所有分区
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}

object FlinkBroadCast {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    environment.setParallelism(4)
    import org.apache.flink.api.scala._
    val result: DataStream[String] = environment.fromElements("hello").setParallelism(1)
    val resultValue: DataStream[String] = result.broadcast.map(x => {
      println(x)
      x
    })
    resultValue.print()
    environment.execute()
  }
}

2、dataSet当中的广播变量
广播变量允许编程人员在每台机器上保持1个只读的缓存变量,而不是传送变量的副本给tasks
广播变量创建后,它可以运行在集群中的任何function上,而不需要多次传递给集群节点。另外需要记住,不应该修改广播变量,这样才能确保每个节点获取到的值都是一致的
一句话解释,可以理解为是一个公共的共享变量,我们可以把一个dataset 数据集广播出去,然后不同的task在节点上都能够获取到,这个数据在每个节点上只会存在一份。如果不使用broadcast,则在每个节点中的每个task中都需要拷贝一份dataset数据集,比较浪费内存(也就是一个节点中可能会存在多份dataset数据)。
用法
1:初始化数据
DataSet<Integer> toBroadcast = env.fromElements(1, 2, 3)
2:广播数据
.withBroadcastSet(toBroadcast, "broadcastSetName");
3:获取数据
Collection<Integer> broadcastSet = getRuntimeContext().getBroadcastVariable("broadcastSetName");
注意:
1:广播出去的变量存在于每个节点的内存中,所以这个数据集不能太大。因为广播出去的数据,会常驻内存,除非程序执行结束
2:广播变量在初始化广播出去以后不支持修改,这样才能保证每个节点的数据都是一致的。
需求:求取订单对应的商品
将订单和商品数据进行合并成为一条数据
注意:数据格式参见附件中的orders.txt以及product.txt,商品表当中的第1个字段表示商品id,订单表当中的第3个字段表示商品id,字段之间都是使用,进行切割
使用广播变量,将商品数据广播到每一个节点,然后通过订单数据来进行join即可
代码实现

import java.util
import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.scala.ExecutionEnvironment
import org.apache.flink.configuration.Configuration
import scala.collection.mutable

object FlinkDataSetBroadCast {
  def main(args: Array[String]): Unit = {
    val environment: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    val productData: DataSet[String] = environment.readTextFile("file:///D:\\开课吧课程资料\\Flink实时数仓\\订单与商品表\\product.txt")
    val productMap = new mutable.HashMap[String,String]()

    val prouctMapSet: DataSet[mutable.HashMap[String, String]] = productData.map(x => {
      val strings: Array[String] = x.split(",")
      productMap.put(strings(0), x)
      productMap
    })

    //获取商品数据
    val ordersDataset: DataSet[String] = environment.readTextFile("file:///D:\\开课吧课程资料\\Flink实时数仓\\订单与商品表\\orders.txt")

    //将商品数据转换成为map结构,key为商品id,value为一行数据
    val resultLine: DataSet[String] = ordersDataset.map(new RichMapFunction[String, String] {
      var listData: util.List[Map[String, String]] = null
      var allMap = Map[String, String]()

      override def open(parameters: Configuration): Unit = {
        this.listData = getRuntimeContext.getBroadcastVariable[Map[String, String]]("productBroadCast")
        val listResult: util.Iterator[Map[String, String]] = listData.iterator()
        while (listResult.hasNext) {
          allMap =  allMap.++(listResult.next())
        }
      }

      //获取到了订单数据,将订单数据与商品数据进行拼接成为一整
      override def map(eachOrder: String): String = {
        val str: String = allMap.getOrElse(eachOrder.split(",")(2),"暂时没有值")
        eachOrder + ","+str
      }
    }).withBroadcastSet(prouctMapSet, "productBroadCast")
    resultLine.print()
    environment.execute("broadCastJoin")
  }
}



2、累加器
Accumulators(累加器)是非常简单的,通过一个add操作累加最终的结果,在job执行后可以获取最终结果
最简单的累加器是counter(计数器):你可以通过Accumulator.add(V value)这个方法进行递增。在任务的最后,flink会吧所有的结果进行合并,然后把最终结果发送到client端。累加器在调试或者你想更快了解你的数据的时候是非常有用的。
Flink现在有一下内置累加器。每个累加器都实现了Accumulator接口。
需求:统计tomcat日志当中exception关键字出现了多少次
代码实现:
import org.apache.flink.api.common.accumulators.LongCounter
import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.scala.ExecutionEnvironment
import org.apache.flink.configuration.Configuration

object FlinkCounterAndAccumulator {

  def main(args: Array[String]): Unit = {
    val env=ExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    //统计tomcat日志当中exception关键字出现了多少次
    val sourceDataSet: DataSet[String] = env.readTextFile("file:///D:\\开课吧课程资料\\Flink实时数仓\\catalina.out")

    sourceDataSet.map(new RichMapFunction[String,String] {

      var counter=new LongCounter()

      override def open(parameters: Configuration): Unit = {
        getRuntimeContext.addAccumulator("my-accumulator",counter)
      }
      override def map(value: String): String = {
        if(value.toLowerCase().contains("exception")){
          counter.add(1)

        }
        value
      }
    }).setParallelism(4).writeAsText("c:\\t4")

    val job=env.execute()
    //获取累加器,并打印累加器的值
    val a=job.getAccumulatorResult[Long]("my-accumulator")
    println(a)
  }
}

3、Flink的分布式缓存DistributedCache
Flink提供了一个分布式缓存,类似于hadoop,可以使用户在并行函数中很方便的读取本地文件
此缓存的工作机制如下:程序注册一个文件或者目录(本地或者远程文件系统,例如hdfs或者s3),通过ExecutionEnvironment注册缓存文件并为它起一个名称。当程序执行,Flink自动将文件或者目录复制到所有taskmanager节点的本地文件系统,用户可以通过这个指定的名称查找文件或者目录,然后从taskmanager节点的本地文件系统访问它
用法:
1:注册一个文件
env.registerCachedFile("hdfs:///path/to/your/file", "hdfsFile")  
2:访问数据
File myFile = getRuntimeContext().getDistributedCache().getFile("hdfsFile");
代码实现:
import org.apache.commons.io.FileUtils
import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.scala.ExecutionEnvironment
import org.apache.flink.configuration.Configuration

object FlinkDistributedCache {
  def main(args: Array[String]): Unit = {
    //将缓存文件,拿到每台服务器的本地磁盘进行存储,然后需要获取的时候,直接从本地磁盘文件进行获取
    val env = ExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    //1:注册分布式缓存文件
    env.registerCachedFile("D:\\开课吧课程资料\\Flink实时数仓\\advert.csv","advert")
    val data = env.fromElements("hello","flink","spark","dataset")
    val result = data.map(new RichMapFunction[String,String] {

      override def open(parameters: Configuration): Unit = {
        super.open(parameters)
        val myFile = getRuntimeContext.getDistributedCache.getFile("advert")
        val lines = FileUtils.readLines(myFile)
        val it = lines.iterator()
        while (it.hasNext){
          val line = it.next();
          println("line:"+line)
        }
      }
      override def map(value: String) = {
        value
      }
    }).setParallelism(2)
    result.print()
    env.execute()
  }
}






14、Flink的Table以及SQL
1、Flink table以及SQL的基本介绍
Apache Flink 具有两个关系型API:Table API 和SQL,用于统一流和批处理。
Table API 是用于 Scala 和 Java 语言的查询API,允许以非常直观的方式组合关系运算符的查询,例如 select,filter 和 join。Flink SQL 的支持是基于实现了SQL标准的 Apache Calcite。无论输入是批输入(DataSet)还是流输入(DataStream),任一接口中指定的查询都具有相同的语义并指定相同的结果。
Table API和SQL接口彼此集成,Flink的DataStream和DataSet API亦是如此。我们可以轻松地在基于API构建的所有API和库之间切换。
注意,到目前最新版本为止,Table API和SQL还有很多功能正在开发中。 并非[Table API,SQL]和[stream,batch]输入的每种组合都支持所有操作

2、为什么需要SQL
Table API 是一种关系型API,类 SQL 的API,用户可以像操作表一样地操作数据, 非常的直观和方便。
SQL 作为一个"人所皆知"的语言,如果一个引擎提供 SQL,它将很容易被人们接受。这已经是业界很常见的现象了。
Table & SQL API 还有另一个职责,就是流处理和批处理统一的API层。



3、Flink Table  &  SQL编程开发
官网介绍:
https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/table/

Flink的tableAPI允许我们对流式处理以及批量处理都使用sql语句的方式来进行开发。只要我们知道了dataStream或者dataSet可以转换成为Table,那么我们就可以方便的从各个地方获取数据,然后转换成为Table,通过TableAPI或者SQL来实现我们的数据的处理等
Flink的表API和SQL程序可以连接到其他外部系统来读写批处理表和流表。Table source提供对存储在外部 系统(如数据库、键值存储、消息队列或文件系统)中的数据的访问。Table Sink将表发送到外部存储系统。


1、使用FlinkSQL实现读取CSV文件数据,并进行查询
需求:读取csv文件,文件内容参见课件当中的flinksql.csv文件,查询年龄大于18岁的人,并将结果写入到csv文件里面去
第一步:导入jar包
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-table-planner_2.11</artifactId>
    <version>1.8.1</version>
</dependency>
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-table-api-scala-bridge_2.11</artifactId>
    <version>1.8.1</version>
</dependency>
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-table-api-scala_2.11</artifactId>
    <version>1.8.1</version>
</dependency>
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-table-common</artifactId>
    <version>1.8.1</version>
</dependency>


第二步:开发代码读取csv文件并进行查询
import org.apache.flink.core.fs.FileSystem.WriteMode
import org.apache.flink.streaming.api.scala.{StreamExecutionEnvironment}
import org.apache.flink.table.api.{Table, Types}
import org.apache.flink.table.api.scala.StreamTableEnvironment
import org.apache.flink.table.sinks.{CsvTableSink}
import org.apache.flink.table.sources.CsvTableSource

object FlinkStreamSQL {
  def main(args: Array[String]): Unit = {
    //流式sql,获取运行环境
    val streamEnvironment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //流式table处理环境
    val tableEnvironment: StreamTableEnvironment = StreamTableEnvironment.create(streamEnvironment)
    //注册我们的tableSource
    val source: CsvTableSource = CsvTableSource.builder()
      .field("id", Types.INT)
      .field("name", Types.STRING)
      .field("age", Types.INT)
      .fieldDelimiter(",")
      .ignoreFirstLine()
      .ignoreParseErrors()
      .lineDelimiter("\r\n")
      .path("D:\\开课吧课程资料\\Flink实时数仓\\datas\\flinksql.csv")
      .build()
    //将tableSource注册成为我们的表
    tableEnvironment.registerTableSource("user",source)
    //查询年龄大于18岁的人
    val result: Table = tableEnvironment.scan("user").filter("age >18")
    //打印我们表的元数据信息===》也就是字段信息
    //将查询出来的结果,保存到我们的csv文件里面去
    val sink = new CsvTableSink("D:\\开课吧课程资料\\Flink实时数仓\\datas\\sink.csv","===",1,WriteMode.OVERWRITE)
    result.writeToSink(sink)
    streamEnvironment.execute()
  }
}
2、DataStream与Table的互相转换操作
DataStream转换成为Table说明:

我们也可以将dataStream,流式处理的数据处理成为一张表,然后通过sql语句进行查询数据,读取socket当中的数据,然后进行数据统计,统计年大于10的人数,并将结果保存到本地文件,socket发送的数据格式如下。
101,zhangsan,18
102,lisi,20
103,wangwu,25
104,zhaoliu,8

将DataStream转换成为Table,我们需要用到StreamExecutionEnvironment和StreamTableEnvironment这两个对象
获取StreamTableEnvironment 对象,然后调用fromDataStream或者registerDataStream就可以将我们的DataStream转换成为Table



Table转换成为DataStream说明:
或者我们也可以将我们处理完成之后的Table转换成为DataStream,将Table转换成为DataStream可以有两种模式
第一种方式:AppendMode
将表附加到流数据,表当中只能有查询或者添加操作,如果有update或者delete操作,那么就会失败
只有在动态Table仅通过INSERT更改修改时才能使用此模式,即它仅附加,并且以前发出的结果永远不会更新。如果更新或删除操作使用追加模式会失败报错 

第二种模式:RetraceMode
始终可以使用此模式。返回值是boolean类型。它用true或false来标记数据的插入和撤回,返回true代表数据插入,false代表数据的撤回
第一步:代码开发
注意:flink代码开发需要导入隐式转换包
import org.apache.flink.api.scala._
对于flink tableAPI或者SQL的开发,则需要导入隐式转换包
import org.apache.flink.table.api._

import org.apache.flink.core.fs.FileSystem.WriteMode
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.table.api._
import org.apache.flink.api.scala._
import org.apache.flink.table.api.scala.StreamTableEnvironment
import org.apache.flink.table.sinks.CsvTableSink

object FlinkStreamSQL {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    val streamSQLEnvironment: StreamTableEnvironment = StreamTableEnvironment.create(environment)
    val socketStream: DataStream[String] = environment.socketTextStream("node01",9000)
    //101,zhangsan,18
    //102,lisi,20
    //103,wangwu,25
    //104,zhaoliu,8
    val userStream: DataStream[User] = socketStream.map(x =>User(x.split(",")(0).toInt,x.split(",")(1),x.split(",")(2).toInt) )
    //将我们的流注册成为一张表
    streamSQLEnvironment.registerDataStream("userTable",userStream)
    //通过sql语句的方式来进行查询

    //通过表达式来进行查询
    //使用tableAPI来进行查询
   // val table: Table = streamSQLEnvironment.scan("userTable").filter("age > 10")
    //使用sql方式来进行查询
    val table: Table = streamSQLEnvironment.sqlQuery("select * from userTable")
    val sink3 = new CsvTableSink("D:\\开课吧课程资料\\Flink实时数仓\\datas\\sink3.csv","===",1,WriteMode.OVERWRITE)
    table.writeToSink(sink3)

    //使用append模式将Table转换成为dataStream,不能用于sum,count,avg等操作,只能用于添加数据操作
    val appendStream: DataStream[User] = streamSQLEnvironment.toAppendStream[User](table)
    //使用retract模式将Table转换成为DataStream
    val retractStream: DataStream[(Boolean, User)] = streamSQLEnvironment.toRetractStream[User](table)
    environment.execute()
  }
}
case class User(id:Int,name:String,age:Int)



第二步:socket发送数据
101,zhangsan,18
102,lisi,20
103,wangwu,25
104,zhaoliu,8


3、DataSet与Table的互相转换操作
我们也可以将我们的DataSet注册成为一张表Table,然后进行查询数据,同时我们也可以将Table转换成为DataSet
import org.apache.flink.api.scala._
import org.apache.flink.api.scala.ExecutionEnvironment
import org.apache.flink.core.fs.FileSystem.WriteMode
import org.apache.flink.table.api.scala.BatchTableEnvironment
import org.apache.flink.table.sinks.CsvTableSink

object FlinkBatchSQL {
  def main(args: Array[String]): Unit = {
    val environment: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    val batchSQL: BatchTableEnvironment = BatchTableEnvironment.create(environment)

    val sourceSet: DataSet[String] = environment.readTextFile("D:\\开课吧课程资料\\Flink实时数仓\\datas\\dataSet.csv")

    val userSet: DataSet[User2] = sourceSet.map(x => {
      println(x)
      val line: Array[String] = x.split(",")
      User2(line(0).toInt, line(1), line(2).toInt)
    })

    import org.apache.flink.table.api._

    batchSQL.registerDataSet("user",userSet)
   //val table: Table = batchSQL.scan("user").filter("age > 18")
    //注意:user关键字是flink当中的保留字段,如果用到了这些保留字段,需要转译
    val table: Table = batchSQL.sqlQuery("select id,name,age from `user` ")
    val sink = new CsvTableSink("D:\\开课吧课程资料\\Flink实时数仓\\datas\\batchSink.csv","===",1,WriteMode.OVERWRITE)
    table.writeToSink(sink)


    //将Table转换成为DataSet
     val tableSet: DataSet[User2] = batchSQL.toDataSet[User2](table)

    tableSet.map(x =>x.age).print()

    environment.execute()
  }
}
case class User2(id:Int,name:String,age:Int)

更多flink定义的保留关键字段:
https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/table/sql.html


A, ABS, ABSOLUTE, ACTION, ADA, ADD, ADMIN, AFTER, ALL, ALLOCATE, ALLOW, ALTER, ALWAYS, AND, ANY, ARE, ARRAY, AS, ASC, ASENSITIVE, ASSERTION, ASSIGNMENT, ASYMMETRIC, AT, ATOMIC, ATTRIBUTE, ATTRIBUTES, AUTHORIZATION, AVG, BEFORE, BEGIN, BERNOULLI, BETWEEN, BIGINT, BINARY, BIT, BLOB, BOOLEAN, BOTH, BREADTH, BY, C, CALL, CALLED, CARDINALITY, CASCADE, CASCADED, CASE, CAST, CATALOG, CATALOG_NAME, CEIL, CEILING, CENTURY, CHAIN, CHAR, CHARACTER, CHARACTERISTICS, CHARACTERS, CHARACTER_LENGTH, CHARACTER_SET_CATALOG, CHARACTER_SET_NAME, CHARACTER_SET_SCHEMA, CHAR_LENGTH, CHECK, CLASS_ORIGIN, CLOB, CLOSE, COALESCE, COBOL, COLLATE, COLLATION, COLLATION_CATALOG, COLLATION_NAME, COLLATION_SCHEMA, COLLECT, COLUMN, COLUMN_NAME, COMMAND_FUNCTION, COMMAND_FUNCTION_CODE, COMMIT, COMMITTED, CONDITION, CONDITION_NUMBER, CONNECT, CONNECTION, CONNECTION_NAME, CONSTRAINT, CONSTRAINTS, CONSTRAINT_CATALOG, CONSTRAINT_NAME, CONSTRAINT_SCHEMA, CONSTRUCTOR, CONTAINS, CONTINUE, CONVERT, CORR, CORRESPONDING, COUNT, COVAR_POP, COVAR_SAMP, CREATE, CROSS, CUBE, CUME_DIST, CURRENT, CURRENT_CATALOG, CURRENT_DATE, CURRENT_DEFAULT_TRANSFORM_GROUP, CURRENT_PATH, CURRENT_ROLE, CURRENT_SCHEMA, CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_TRANSFORM_GROUP_FOR_TYPE, CURRENT_USER, CURSOR, CURSOR_NAME, CYCLE, DATA, DATABASE, DATE, DATETIME_INTERVAL_CODE, DATETIME_INTERVAL_PRECISION, DAY, DEALLOCATE, DEC, DECADE, DECIMAL, DECLARE, DEFAULT, DEFAULTS, DEFERRABLE, DEFERRED, DEFINED, DEFINER, DEGREE, DELETE, DENSE_RANK, DEPTH, DEREF, DERIVED, DESC, DESCRIBE, DESCRIPTION, DESCRIPTOR, DETERMINISTIC, DIAGNOSTICS, DISALLOW, DISCONNECT, DISPATCH, DISTINCT, DOMAIN, DOUBLE, DOW, DOY, DROP, DYNAMIC, DYNAMIC_FUNCTION, DYNAMIC_FUNCTION_CODE, EACH, ELEMENT, ELSE, END, END-EXEC, EPOCH, EQUALS, ESCAPE, EVERY, EXCEPT, EXCEPTION, EXCLUDE, EXCLUDING, EXEC, EXECUTE, EXISTS, EXP, EXPLAIN, EXTEND, EXTERNAL, EXTRACT, FALSE, FETCH, FILTER, FINAL, FIRST, FIRST_VALUE, FLOAT, FLOOR, FOLLOWING, FOR, FOREIGN, FORTRAN, FOUND, FRAC_SECOND, FREE, FROM, FULL, FUNCTION, FUSION, G, GENERAL, GENERATED, GET, GLOBAL, GO, GOTO, GRANT, GRANTED, GROUP, GROUPING, HAVING, HIERARCHY, HOLD, HOUR, IDENTITY, IMMEDIATE, IMPLEMENTATION, IMPORT, IN, INCLUDING, INCREMENT, INDICATOR, INITIALLY, INNER, INOUT, INPUT, INSENSITIVE, INSERT, INSTANCE, INSTANTIABLE, INT, INTEGER, INTERSECT, INTERSECTION, INTERVAL, INTO, INVOKER, IS, ISOLATION, JAVA, JOIN, K, KEY, KEY_MEMBER, KEY_TYPE, LABEL, LANGUAGE, LARGE, LAST, LAST_VALUE, LATERAL, LEADING, LEFT, LENGTH, LEVEL, LIBRARY, LIKE, LIMIT, LN, LOCAL, LOCALTIME, LOCALTIMESTAMP, LOCATOR, LOWER, M, MAP, MATCH, MATCHED, MAX, MAXVALUE, MEMBER, MERGE, MESSAGE_LENGTH, MESSAGE_OCTET_LENGTH, MESSAGE_TEXT, METHOD, MICROSECOND, MILLENNIUM, MIN, MINUTE, MINVALUE, MOD, MODIFIES, MODULE, MONTH, MORE, MULTISET, MUMPS, NAME, NAMES, NATIONAL, NATURAL, NCHAR, NCLOB, NESTING, NEW, NEXT, NO, NONE, NORMALIZE, NORMALIZED, NOT, NULL, NULLABLE, NULLIF, NULLS, NUMBER, NUMERIC, OBJECT, OCTETS, OCTET_LENGTH, OF, OFFSET, OLD, ON, ONLY, OPEN, OPTION, OPTIONS, OR, ORDER, ORDERING, ORDINALITY, OTHERS, OUT, OUTER, OUTPUT, OVER, OVERLAPS, OVERLAY, OVERRIDING, PAD, PARAMETER, PARAMETER_MODE, PARAMETER_NAME, PARAMETER_ORDINAL_POSITION, PARAMETER_SPECIFIC_CATALOG, PARAMETER_SPECIFIC_NAME, PARAMETER_SPECIFIC_SCHEMA, PARTIAL, PARTITION, PASCAL, PASSTHROUGH, PATH, PERCENTILE_CONT, PERCENTILE_DISC, PERCENT_RANK, PLACING, PLAN, PLI, POSITION, POWER, PRECEDING, PRECISION, PREPARE, PRESERVE, PRIMARY, PRIOR, PRIVILEGES, PROCEDURE, PUBLIC, QUARTER, RANGE, RANK, READ, READS, REAL, RECURSIVE, REF, REFERENCES, REFERENCING, REGR_AVGX, REGR_AVGY, REGR_COUNT, REGR_INTERCEPT, REGR_R2, REGR_SLOPE, REGR_SXX, REGR_SXY, REGR_SYY, RELATIVE, RELEASE, REPEATABLE, RESET, RESTART, RESTRICT, RESULT, RETURN, RETURNED_CARDINALITY, RETURNED_LENGTH, RETURNED_OCTET_LENGTH, RETURNED_SQLSTATE, RETURNS, REVOKE, RIGHT, ROLE, ROLLBACK, ROLLUP, ROUTINE, ROUTINE_CATALOG, ROUTINE_NAME, ROUTINE_SCHEMA, ROW, ROWS, ROW_COUNT, ROW_NUMBER, SAVEPOINT, SCALE, SCHEMA, SCHEMA_NAME, SCOPE, SCOPE_CATALOGS, SCOPE_NAME, SCOPE_SCHEMA, SCROLL, SEARCH, SECOND, SECTION, SECURITY, SELECT, SELF, SENSITIVE, SEQUENCE, SERIALIZABLE, SERVER, SERVER_NAME, SESSION, SESSION_USER, SET, SETS, SIMILAR, SIMPLE, SIZE, SMALLINT, SOME, SOURCE, SPACE, SPECIFIC, SPECIFICTYPE, SPECIFIC_NAME, SQL, SQLEXCEPTION, SQLSTATE, SQLWARNING, SQL_TSI_DAY, SQL_TSI_FRAC_SECOND, SQL_TSI_HOUR, SQL_TSI_MICROSECOND, SQL_TSI_MINUTE, SQL_TSI_MONTH, SQL_TSI_QUARTER, SQL_TSI_SECOND, SQL_TSI_WEEK, SQL_TSI_YEAR, SQRT, START, STATE, STATEMENT, STATIC, STDDEV_POP, STDDEV_SAMP, STREAM, STRUCTURE, STYLE, SUBCLASS_ORIGIN, SUBMULTISET, SUBSTITUTE, SUBSTRING, SUM, SYMMETRIC, SYSTEM, SYSTEM_USER, TABLE, TABLESAMPLE, TABLE_NAME, TEMPORARY, THEN, TIES, TIME, TIMESTAMP, TIMESTAMPADD, TIMESTAMPDIFF, TIMEZONE_HOUR, TIMEZONE_MINUTE, TINYINT, TO, TOP_LEVEL_COUNT, TRAILING, TRANSACTION, TRANSACTIONS_ACTIVE, TRANSACTIONS_COMMITTED, TRANSACTIONS_ROLLED_BACK, TRANSFORM, TRANSFORMS, TRANSLATE, TRANSLATION, TREAT, TRIGGER, TRIGGER_CATALOG, TRIGGER_NAME, TRIGGER_SCHEMA, TRIM, TRUE, TYPE, UESCAPE, UNBOUNDED, UNCOMMITTED, UNDER, UNION, UNIQUE, UNKNOWN, UNNAMED, UNNEST, UPDATE, UPPER, UPSERT, USAGE, USER, USER_DEFINED_TYPE_CATALOG, USER_DEFINED_TYPE_CODE, USER_DEFINED_TYPE_NAME, USER_DEFINED_TYPE_SCHEMA, USING, VALUE, VALUES, VARBINARY, VARCHAR, VARYING, VAR_POP, VAR_SAMP, VERSION, VIEW, WEEK, WHEN, WHENEVER, WHERE, WIDTH_BUCKET, WINDOW, WITH, WITHIN, WITHOUT, WORK, WRAPPER, WRITE, XML, YEAR, ZONE

4、FlinkSQL处理kafka的json格式数据
Flink的SQL功能也可以让我们直接读取kafka当中的数据,然后将kafka当中的数据作为我们的数据源,直接将kafka当中的数据注册成为一张表,然后通过sql来查询kafka当中的数据即可,如果kafka当中出现的是json格式的数据,那么也没关系flink也可以与json进行集成,直接解析json格式的数据
https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/table/connect.html
第一步:导入jar包
导入jar包
<dependency>
     <groupId>org.apache.flink</groupId>
     <artifactId>flink-json</artifactId>
     <version>1.8.1</version>
 </dependency>

 <!--
 前面Flink Stream与kafka整合已经导入了kafka的包,不用再导入了
 <dependency>
     <groupId>org.apache.flink</groupId>
     <artifactId>flink-connector-kafka-0.11_2.11</artifactId>
     <version>1.8.1</version>
 </dependency>
 <dependency>
     <groupId>org.apache.kafka</groupId>
     <artifactId>kafka-clients</artifactId>
     <version>1.1.0</version>
 </dependency>

 <dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-api</artifactId>
     <version>1.7.25</version>
 </dependency>

 <dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-log4j12</artifactId>
     <version>1.7.25</version>
 </dependency>
-->

第二步:创建kafka的topic
node01执行以下命令,创建一个topic
cd /kkb/install/kafka_2.11-1.1.0
bin/kafka-topics.sh --create --topic kafka_source_table --partitions 3 --replication-factor 1 --zookeeper node01:2181,node02:2181,node03:2181
第三步:使用flink查询kafka当中的数据
import org.apache.flink.api.common.typeinfo.TypeInformation
import org.apache.flink.core.fs.FileSystem.WriteMode
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.table.api.{Table, _}
import org.apache.flink.table.api.scala.StreamTableEnvironment
import org.apache.flink.table.descriptors.{Json, Kafka, Schema}
import org.apache.flink.table.sinks.CsvTableSink
object KafkaJsonSource {
  def main(args: Array[String]): Unit = {
    val streamEnvironment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //隐式转换
    //checkpoint配置
   /* streamEnvironment.enableCheckpointing(100);
    streamEnvironment.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
    streamEnvironment.getCheckpointConfig.setMinPauseBetweenCheckpoints(500);
    streamEnvironment.getCheckpointConfig.setCheckpointTimeout(60000);
    streamEnvironment.getCheckpointConfig.setMaxConcurrentCheckpoints(1);
    streamEnvironment.getCheckpointConfig.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
*/
    val tableEnvironment: StreamTableEnvironment = StreamTableEnvironment.create(streamEnvironment)
    val kafka: Kafka = new Kafka()
      .version("0.11")
      .topic("kafka_source_table")
      .startFromLatest()
      .property("group.id", "test_group")
      .property("bootstrap.servers", "node01:9092,node02:9092,node03:9092")

    val json: Json = new Json().failOnMissingField(false).deriveSchema()
    //{"userId":1119,"day":"2017-03-02","begintime":1488326400000,"endtime":1488327000000,"data":[{"package":"com.browser","activetime":120000}]}
    val schema: Schema = new Schema()
      .field("userId", Types.INT)
      .field("day", Types.STRING)
      .field("begintime", Types.LONG)
      .field("endtime", Types.LONG)
     tableEnvironment
      .connect(kafka)
      .withFormat(json)
      .withSchema(schema)
      .inAppendMode()
      .registerTableSource("user_log")
    //使用sql来查询数据
    val table: Table = tableEnvironment.sqlQuery("select userId,`day` ,begintime,endtime  from user_log")
    table.printSchema()
    //定义sink,输出数据到哪里
    val sink = new CsvTableSink("D:\\开课吧课程资料\\Flink实时数仓\\datas\\flink_kafka.csv","====",1,WriteMode.OVERWRITE)
    //注册数据输出目的地
    tableEnvironment.registerTableSink("csvSink",
      Array[String]("f0","f1","f2","f3"),
        Array[TypeInformation[_]](Types.INT, Types.STRING, Types.LONG, Types.LONG),sink)
    //将数据插入到数据目的地
    table.insertInto("csvSink")
    streamEnvironment.execute("kafkaSource")
  }
}
第四步:kafka当中发送数据
使用kafka命令行发送数据
cd /kkb/install/kafka_2.11-1.1.0
bin/kafka-console-producer.sh  --topic kafka_source_table --broker-list node01:9092,node02:9092,node03:9092 
发送数据格式如下:
{"userId":1119,"day":"2017-03-02","begintime":1488326400000,"endtime":1488327000000}
{"userId":1120,"day":"2017-03-02","begintime":1488326400000,"endtime":1488327000000}
{"userId":1121,"day":"2017-03-02","begintime":1488326400000,"endtime":1488327000000}
{"userId":1122,"day":"2017-03-02","begintime":1488326400000,"endtime":1488327000000}
{"userId":1123,"day":"2017-03-02","begintime":1488326400000,"endtime":1488327000000}




15、基于Flink实现实时数据同步解析
为了解决公司数据统计,数据分析等各种问题,我们可以有很多手段,最常用的手段就是通过构建数据仓库的手段来实现我们的数据分析,数据挖掘等,其中,数据仓库基本上都是统计前一天的数据,或者最近一段时间的数据,这就决定了数据仓库一般都是使用离线的技术来实现,通过离线的技术手段,来实现前一天或者近一段时间的数据统计功能,为了解决数据统计的时效性问题,我们也可以通过实时的手段来构建数据仓库,通过流式API,结合flink的TableAPI或者SQL功能,即可实现我们实时的数据统计,构建实时的数据仓库

1、实时数仓架构

实时数仓主要用于处理各种数据,其中包括点击日志数据,业务库数据,爬虫竞品数据,业务库当中的数据主要可以通过canal来实现数据实时同步处理,日志数据可以通过flume等采集工具,全量导入可以通过sqoop或者maxwell来实现,通过各种数据采集手段,将我们的数据统一接入到kafka消息队列



2、mysql数据实时同步
1、mysql的binlog介绍
binlog是mysql当中的二进制日志,主要用于记录对mysql数据库当中的数据发生或潜在发生更改的SQL语句,并以二进制的形式保存在磁盘中,如果后续我们需要配置主从数据库,如果我们需要从数据库同步主数据库的内容,我们就可以通过binlog来进行同步。说白了binlog可以用于解决实时同步mysql数据库当中的数据
binlog的格式也有三种:STATEMENT、ROW、MIXED 。
?STATMENT模式:基于SQL语句的复制(statement-based replication, SBR),每一条会修改数据的sql语句会记录到binlog中。
?	优点:不需要记录每一条SQL语句与每行的数据变化,这样子binlog的日志也会比较少,减少了磁盘IO,提高性能。
?	缺点:在某些情况下会导致master-slave中的数据不一致(如sleep()函数, last_insert_id(),以及 user-defined functions(udf)等会出现问题)
?	基于行的复制(row-based replication, RBR):不记录每一条SQL语句的上下文信息,仅需记录哪条数据被修改了,修改成了什么样子了。
?	优点:不会出现某些特定情况下的存储过程或function、或trigger的调用和触发无法被正确复制的问题。
?	缺点:会产生大量的日志,尤其是alter table的时候会让日志暴涨。
?	混合模式复制(mixed-based replication, MBR):以上两种模式的混合使用,一般的复制使 用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog, MySQL会根据执行的SQL语句选择日志保存方式。
因为statement只有sql,没有数据,无法获 取原始的变更日志,所以一般建议为ROW模式)


mysql数据实时同步,我们可以通过解析mysql的bin-log的方式来实现,解析bin-log可以有多种方式,可以通过canal,或者max-well等各种方式实现。以下是各种抽取方式的对比介绍


2、max-well的基本介绍
Maxwell是一个能实时读取MySQL二进制日志binlog,并生成 JSON 格式的消息,作为生产者发送给 Kafka,Kinesis、RabbitMQ、Redis、Google Cloud Pub/Sub、文件或其它平台的应用程序。它的常见应用场景有ETL、维护缓存、收集表级别的dml指标、增量到搜索引擎、数据分区迁移、切库binlog回滚方案等。
官网(http://maxwells-daemon.io)
GitHub(https://github.com/zendesk/maxwell)

Maxwell主要提供了下列功能:

支持 SELECT * FROM table 的方式进行全量数据初始化
支持在主库发生failover后,自动恢复binlog位置(GTID)
可以对数据进行分区,解决数据倾斜问题,发送到kafka的数据支持database、table、column等级别的数据分区
工作方式是伪装为Slave,接收binlog events,然后根据schemas信息拼装,可以接受ddl、xid、row等各种event
除了Maxwell外,目前常用的MySQL Binlog解析工具主要有阿里的canal、mysql_streamer

3、开启mysql的binlog功能
服务器当中安装mysql(省略)
注意:mysql的版本尽量不要太低,也不要太高,最好使用5.6及以上版本,centos7当中安装mysql5.7版本参见
https://www.cnblogs.com/brianzhu/p/8575243.html
第一步:添加mysql普通用户maxwell
为mysql添加一个普通用户maxwell,因为maxwell这个软件默认用户使用的是maxwell这个用户,
进入mysql客户端,然后执行以下命令,进行授权
mysql -uroot  -p  
set global validate_password_policy=LOW;
set global validate_password_length=6;
CREATE USER 'maxwell'@'%' IDENTIFIED BY '123456';
GRANT ALL ON maxwell.* TO 'maxwell'@'%';
GRANT SELECT, REPLICATION CLIENT, REPLICATION SLAVE on *.* to 'maxwell'@'%'; 
flush privileges;
第二步:开启mysql的binlog机制
对于我们的mysql数据库,我们可以开启mysql的binlog功能,通过修改mysql的配置文件来实现开启,修改完配置文件之后,需要重启mysql服务
node03服务器执行以下命令,开启mysql的binlog,并指定binlog格式为ROW
sudo vim /etc/my.cnf

log-bin=mysql-bin
binlog-format=ROW
server_id=1

node03执行以下命令重启mysql服务:
sudo service mysqld restart

4、安装max-well实现实时采集mysql数据
第一步:下载max-well并上传解压
node03下载max-well安装包,下载地址:
https://github.com/zendesk/maxwell/releases/download/v1.21.1/maxwell-1.21.1.tar.gz

将下载好的安装包上传到node03服务器的/kkb/soft路径下,并进行解压

cd /kkb/soft
tar -zxf maxwell-1.21.1.tar.gz -C /kkb/install/
第二步:修改maxwell配置文件
node03修改maxwell的配置文件
cd /kkb/install/maxwell-1.21.1 
cp config.properties.example config.properties
vim config.properties

producer=kafka
kafka.bootstrap.servers=node01:9092,node02:9092,node03:9092
host=node03.kaikeba.com
user=maxwell
password=123456
producer=kafka
host=node03.kaikeba.com
port=3306
user=maxwell
password=123456
kafka.bootstrap.servers=node01:9092,node02:9092,node03:9092
kafka_topic=maxwell_kafka
一定要注意:一定要保证我们使用maxwell用户和123456密码能够连接上mysql数据库

5、启动服务
启动我们的zookeeper服务,kafka服务并创建kafka的topic,然后启动maxwell服务,测试向数据库当中插入数据,并查看kafka当中是否能够同步到mysql数据
启动zookeeper服务:省略
启动kafka服务:省略
创建kafka的topic:
node01执行以下命令创建kafka的topic
cd /kkb/install/kafka_2.11-1.1.0
bin/kafka-topics.sh  --create --topic maxwell_kafka --partitions 3 --replication-factor 2 --zookeeper node01:2181
node01执行以下命令,启动kafka的自带控制台消费者,消费kafka当中的数据,验证kafka当中是否有数据进入
cd /kkb/install/kafka_2.11-1.1.0
bin/kafka-console-consumer.sh --topic maxwell_kafka --from-beginning --bootstrap-server node01:9092,node02:9092,node03:9092
node03执行以下命令,启动maxwell服务

cd /kkb/install/maxwell-1.21.1
bin/maxwell



6、插入数据并进行测试
向mysql当中插入一条数据,并开启kafka的消费者,查看kafka是否能够接收到数据
向mysql当中创建数据库和数据库表并插入数据
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `test`;

/*Table structure for table `myuser` */

DROP TABLE IF EXISTS `myuser`;

CREATE TABLE `myuser` (
  `id` int(12) NOT NULL,
  `name` varchar(32) DEFAULT NULL,
  `age` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `myuser` */

insert  into `myuser`(`id`,`name`,`age`) values (1,'zhangsan',NULL),(2,'xxx',NULL),(3,'ggg',NULL),(5,'xxxx',NULL),(8,'skldjlskdf',NULL),(10,'ggggg',NULL),(99,'ttttt',NULL),(114,NULL,NULL),(121,'xxx',NULL);

启动kafka的消费者,验证数据是否进入kafka
node01执行以下命令消费kafka当中的数据
cd /kkb/install/kafka_2.11-1.1.0
bin/kafka-console-consumer.sh --bootstrap-server node01:9092,node02:9092,node03:9092 --topic  maxwell_kafka
3、数据库建模
创建我们的商品数据表以及订单数据表

/*
SQLyog Ultimate v8.32 
MySQL - 5.7.27-log : Database - product
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`product` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `product`;

/*Table structure for table `kaikeba_goods` */

DROP TABLE IF EXISTS `kaikeba_goods`;

CREATE TABLE `kaikeba_goods` (
  `goodsId` BIGINT(10) NOT NULL AUTO_INCREMENT,
  `goodsName` VARCHAR(256) DEFAULT NULL,  -- 商品名称
  `sellingPrice` VARCHAR(256) DEFAULT NULL,  -- 商品售价
  `productPic` VARCHAR(256) DEFAULT NULL,  -- 商品图片
  `productBrand` VARCHAR(256) DEFAULT NULL,  -- 商品品牌
  `productfbl` VARCHAR(256) DEFAULT NULL, -- 手机分片率
  `productNum` VARCHAR(256) DEFAULT NULL,  -- 商品编号
  `productUrl` VARCHAR(256) DEFAULT NULL,   -- 商品url地址
  `productFrom` VARCHAR(256) DEFAULT NULL,  -- 商品来源
  `goodsStock` INT(11) DEFAULT NULL,        -- 商品库存
  `appraiseNum` INT(11) DEFAULT NULL,       -- 商品评论数
  PRIMARY KEY (`goodsId`)
) ENGINE=INNODB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

/*Data for the table `kaikeba_goods` */

CREATE TABLE product.kaikeba_orders (
  orderId int(11) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  orderNo varchar(50) NOT NULL COMMENT '订单号',
  userId int(11) NOT NULL COMMENT '用户ID',
  goodId int(11) NOT NULL COMMENT '商品ID',
  goodsMoney decimal(11,2) NOT NULL DEFAULT '0.00' COMMENT '商品总金额',
  realTotalMoney decimal(11,2) NOT NULL DEFAULT '0.00' COMMENT '实际订单总金额',
  payFrom int(11) NOT NULL DEFAULT '0' COMMENT '支付来源(1:支付宝,2:微信)',
  province varchar(50) NOT NULL COMMENT '省份',
  createTime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`orderId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8


4、开发模拟数据生成模块
我们主要用到了订单表以及商品表,商品表数据,我们可以批量生成一批,实现我们全量拉取数据到hbase当中去,订单表我们可以模拟一直生成,通过binlog来解析实现实时同步

第一步:创建maven工程并导入jar包
创建maven工程,并导入以下jar包坐标
<repositories>
    <repository>
        <id>cloudera</id>
        <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
    </repository>
</repositories>
<dependencies>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-streaming-scala_2.11</artifactId>
        <version>1.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-scala_2.11</artifactId>
        <version>1.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>2.6.0-mr1-cdh5.14.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-common</artifactId>
        <version>2.6.0-cdh5.14.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-hdfs</artifactId>
        <version>2.6.0-cdh5.14.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-mapreduce-client-core</artifactId>
        <version>2.6.0-cdh5.14.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-connector-kafka-0.11_2.11</artifactId>
        <version>1.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>1.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.25</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.25</version>
    </dependency>
    <dependency>
        <groupId>org.apache.bahir</groupId>
        <artifactId>flink-connector-redis_2.11</artifactId>
        <version>1.0</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.38</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-statebackend-rocksdb_2.11</artifactId>
        <version>1.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-hadoop-compatibility_2.11</artifactId>
        <version>1.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-shaded-hadoop2</artifactId>
        <version>1.7.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-hbase_2.11</artifactId>
        <version>1.8.1</version>
        <exclusions>
            <exclusion>
                <artifactId>protobuf-java</artifactId>
                <groupId>com.google.protobuf</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-table-planner_2.11</artifactId>
        <version>1.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-table-api-scala-bridge_2.11</artifactId>
        <version>1.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-table-api-scala_2.11</artifactId>
        <version>1.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-table-common</artifactId>
        <version>1.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-json</artifactId>
        <version>1.8.1</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.8</version>
    </dependency>
    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
        <version>2.10.1</version>
    </dependency>

    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka_2.11</artifactId>
        <version>1.1.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.flink/flink-jdbc -->
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-jdbc_2.11</artifactId>
        <version>1.8.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.4</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.40</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <!-- 限制jdk版本插件 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
        <!-- 编译scala需要用到的插件 -->
        <plugin>
            <groupId>net.alchim31.maven</groupId>
            <artifactId>scala-maven-plugin</artifactId>
            <version>3.2.2</version>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>testCompile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <!-- 项目打包用到的插件 -->
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifest>
                        <mainClass></mainClass>
                    </manifest>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>


第二步:开发flink程序,批量导入商品表数据
读取资料当中的商品表CSV数据,并写入到商品表当中去
import org.apache.flink.api.java.io.jdbc.JDBCOutputFormat
import org.apache.flink.api.scala._
import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment}
import org.apache.flink.types.Row


object GenerateGoodsDatas {

  def main(args: Array[String]): Unit = {
    val environment: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    val fileSource: DataSet[String] = environment.readTextFile("file:///D:\\开课吧课程资料\\Flink实时数仓\\实时数仓建表以及数据\\goods.csv")
    val line: DataSet[String] = fileSource.flatMap(x => {
      x.split("\r\n")
    })
    val productSet: DataSet[Row] = line.map(x => {
      val pro: Array[String] = x.split("===")
      println(pro(1))
      Row.of(null, pro(1), pro(2), pro(3), pro(4), pro(5), pro(6), pro(7), pro(8),
        pro(9), pro(10))
    })
    productSet.output(JDBCOutputFormat.buildJDBCOutputFormat()
      .setBatchInterval(2)
      .setDBUrl("jdbc:mysql://node03:3306/product?characterEncoding=utf-8")
      .setDrivername("com.mysql.jdbc.Driver")
      .setPassword("123456")
      .setUsername("root")
      .setQuery("insert into kaikeba_goods(goodsId ,goodsName ,sellingPrice,productPic ,productBrand  ,productfbl  ,productNum ,productUrl ,productFrom,goodsStock ,appraiseNum   ) values(?,?,?,?,?,?,?,?,?,?,?)")
      .finish())
    environment.execute()
  }
}

第三步:开发订单生成程序,模拟订单持续生成
通过flink的stream流式程序,持续的生成订单数据
import java.text.{DecimalFormat, SimpleDateFormat}
import java.util.{Date, UUID}

import org.apache.flink.api.common.typeinfo.{BasicTypeInfo, TypeInformation}
import org.apache.flink.api.java.io.jdbc.JDBCAppendTableSink
import org.apache.flink.streaming.api.datastream.DataStreamSource
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment
import org.apache.flink.streaming.api.functions.source.{RichParallelSourceFunction, SourceFunction}
import org.apache.flink.types.Row

import scala.util.Random

object GenerateOrderDatas {
  def main(args: Array[String]): Unit = {
    //获取执行环境
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    //使用JDBCAppendTableSink 这个sink,将数据发送到表里面去
    val sink: JDBCAppendTableSink = JDBCAppendTableSink.builder()
      .setDrivername("com.mysql.jdbc.Driver")
      .setDBUrl("jdbc:mysql://node03:3306/product?characterEncodint=utf-8")
      .setUsername("root")
      .setPassword("123456")
      .setBatchSize(2)
      .setQuery("insert into kaikeba_orders (orderNo,userId ,goodId ,goodsMoney ,realTotalMoney ,payFrom ,province) values (?,?,?,?,?,?,?)")
      .setParameterTypes(BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO)
      .build()

   /* //定义字段的名字
     val FIELD_NAMES: Array[String] =Array[String]("orderNo", "userId", "goodId", "goodsMoney", "realTotalMoney", "payFrom", "province")

    //定义字段的类型
     val FIELD_TYPES: Array[TypeInformation[_]] = Array[TypeInformation[_]](BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO)
*/
      //types: Array[TypeInformation[_]], fieldNames: Array[String]
  //  val info = new RowTypeInfo(FIELD_TYPES,FIELD_NAMES)

    val sourceStream: DataStreamSource[Row] = environment.addSource(

      new RichParallelSourceFunction[Row] {
      var isRunning = true
      override def run(sc: SourceFunction.SourceContext[Row]): Unit = {
        while (isRunning) {
          val order: Order = generateOrder
          sc.collect(Row.of(order.orderNo, order.userId, order.goodId, order.goodsMoney
            , order.realTotalMoney, order.payFrom, order.province))
          Thread.sleep(1000)
        }
      }
      override def cancel(): Unit = {
        isRunning = false
      }
    }
      //,info  这里可以不用指定字段的名称以及字段的类型,也可以同样将数据插入到表
    )
    //将数据插入到表当中去
    sink.emitDataStream(sourceStream)

    //执行我们的程序
    environment.execute()

  }

  //随机生成订单
  def generateOrder:Order={
    val province: Array[String] = Array[String]("北京市", "天津市", "上海市", "重庆市", "河北省", "山西省", "辽宁省", "吉林省", "黑龙江省", "江苏省", "浙江省", "安徽省", "福建省", "江西省", "山东省", "河南省", "湖北省", "湖南省", "广东省", "海南省", "四川省", "贵州省", "云南省", "陕西省", "甘肃省", "青海省")
    val random = new Random()
    //订单号
    val orderNo: String = UUID.randomUUID.toString
    //用户 userId
    val userId: Int = random.nextInt(10000)
    //商品id
    val goodsId: Int = random.nextInt(1360)
    var goodsMoney: Double = 100 + random.nextDouble * 100
    //商品金额
    goodsMoney = formatDecimal(goodsMoney, 2).toDouble
    var realTotalMoney: Double = 150 + random.nextDouble * 100
    //订单付出金额
    realTotalMoney = formatDecimal(goodsMoney, 2).toDouble

    val payFrom: Int = random.nextInt(5)
    //省份id
    val provinceName: String = province(random.nextInt(province.length))
    val date = new Date
    val format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    val dateStr: String = format.format(date)

    Order(orderNo,userId+"",goodsId+"",goodsMoney+"",realTotalMoney+"",payFrom+"",provinceName)
  }

//生成金额
  def formatDecimal(d: Double, newScale: Int): String = {
    var pattern = "#."
    var i = 0
    while ( {
      i < newScale
    }) {
      pattern += "#"

      {
        i += 1; i - 1
      }
    }
    val df = new DecimalFormat(pattern)
    df.format(d)
  }


}

//定义样例类,用于封装数据
case class Order(orderNo:String
                 ,userId:String
                ,goodId:String
                ,goodsMoney:String
                ,realTotalMoney:String
                ,payFrom:String
                ,province:String
                ) extends Serializable

5、获取数据模块开发
我们获取数据主要分为两个模块获取,一个是全量拉取所有数据,一个是通过mysql的binlog来实现实时的拉取数据。
全量拉取模块我们可以通过flink去获取数据库当中的商品表数据,然后保存到hbase当中去
全量拉取数据
创建hbase表,并通过flink-jdbc组件直接读取mysql表数据,然后将数据保存到hbase里面去
第一步:创建hbase商品表
node01执行以下命令,创建hbase的命名空间以及hbase表

cd /kkb/install/hbase-1.2.0-cdh5.14.2
bin/hbase shell
create_namespace 'flink'
create 'flink:data_goods',{NAME=>'f1',BLOCKCACHE=>true,BLOOMFILTER=>'ROW',DATA_BLOCK_ENCODING => 'PREFIX_TREE', BLOCKSIZE => '65536'}
第二步:代码开发
开发代码,实现mysql数据全部同步到hbase当中来
import org.apache.flink.api.common.typeinfo.BasicTypeInfo
import org.apache.flink.api.java.io.jdbc.JDBCInputFormat
import org.apache.flink.api.java.typeutils.RowTypeInfo
import org.apache.flink.api.scala.hadoop.mapreduce.HadoopOutputFormat
import org.apache.flink.api.scala.{ ExecutionEnvironment}
import org.apache.flink.types.Row
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.client.{Mutation, Put}
import org.apache.hadoop.hbase.mapreduce.TableOutputFormat
import org.apache.hadoop.hbase.{HBaseConfiguration, HConstants}
import org.apache.hadoop.io.Text
import org.apache.hadoop.mapreduce.Job

object FullPullerGoods {
  //全量拉取商品表数据到HBase里面来
  def main(args: Array[String]): Unit = {
    val environment: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment

    import org.apache.flink.api.scala._
    val inputJdbc: JDBCInputFormat = JDBCInputFormat.buildJDBCInputFormat()
      .setDrivername("com.mysql.jdbc.Driver")
      .setDBUrl("jdbc:mysql://node03:3306/product?characterEncodint=utf-8")
      .setPassword("123456")
      .setUsername("root")
      .setFetchSize(2)
      .setQuery("select * from kaikeba_goods")
      .setRowTypeInfo(new RowTypeInfo(BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO))
      .finish()

    //读取jdbc里面的数据
    val goodsSet: DataSet[Row] = environment.createInput(inputJdbc)

    val result: DataSet[(Text, Mutation)] = goodsSet.map(x => {
      val goodsId: String = x.getField(0).toString
      val goodsName: String = x.getField(1).toString
      val sellingPrice: String = x.getField(2).toString
      val productPic: String = x.getField(3).toString
      val proudctBrand: String = x.getField(4).toString
      val proudctfbl: String = x.getField(5).toString
      val productNum: String = x.getField(6).toString
      val productUrl: String = x.getField(7).toString
      val productFrom: String = x.getField(8).toString
      val goodsStock: String = x.getField(9).toString
      val appraiseNum: String = x.getField(10).toString
      val rowkey = new Text(goodsId)
      val put = new Put(rowkey.getBytes)
      put.addColumn("f1".getBytes(), "goodsName".getBytes(), goodsName.getBytes())
      put.addColumn("f1".getBytes(), "sellingPrice".getBytes(), sellingPrice.getBytes())
      put.addColumn("f1".getBytes(), "productPic".getBytes(), productPic.getBytes())
      put.addColumn("f1".getBytes(), "proudctBrand".getBytes(), proudctBrand.getBytes())
      put.addColumn("f1".getBytes(), "proudctfbl".getBytes(), proudctfbl.getBytes())
      put.addColumn("f1".getBytes(), "productNum".getBytes(), productNum.getBytes())
      put.addColumn("f1".getBytes(), "productUrl".getBytes(), productUrl.getBytes())
      put.addColumn("f1".getBytes(), "productFrom".getBytes(), productFrom.getBytes())
      put.addColumn("f1".getBytes(), "goodsStock".getBytes(), goodsStock.getBytes())
      put.addColumn("f1".getBytes(), "appraiseNum".getBytes(), appraiseNum.getBytes())
      (rowkey, put.asInstanceOf[Mutation])

    })
    //将数据写入到hbase
    val configuration: Configuration = HBaseConfiguration.create()
    configuration.set(HConstants.ZOOKEEPER_QUORUM, "node01,node02,node03")
    configuration.set(HConstants.ZOOKEEPER_CLIENT_PORT, "2181")
    configuration.set(TableOutputFormat.OUTPUT_TABLE,"flink:data_goods")
    //mapreduce.output.fileoutputformat.outputdir
    configuration.set("mapred.output.dir","/tmp2")

    val job: Job = Job.getInstance(configuration)
    result.output(new HadoopOutputFormat[Text,Mutation](new TableOutputFormat[Text],job))
    environment.execute("FullPullerGoods")
  }
}


增量拉取数据

我们可以通过maxwell来解析mysql的binlog实现数据增量的同步到kafka集群,但是还存在一个问题,就是同一条数据,如果先添加,后修改,再修改等操作,如何保证数据处理的顺序性?因为kafka当中的数据,在每个分区内部是有序的,但是全局处理无序,所以我们需要保证同一条数据一定要进入到同一个分区里面去。为了解决数据处理的顺序性问题,我们可以通过修改maxwell数据分区的规则来实现。
第一步:创建kafka的topic以及Hbase表
node01执行以下命令创建一个kafka的topic
cd /kkb/install/kafka_2.11-1.1.0
bin/kafka-topics.sh --create --topic flink_house --replication-factor 1  --partitions 3 --zookeeper node01:2181


创建hbase表
node01执行以下命令进入hbase客户端,然后创建hbase表
cd /kkb/install/hbase-1.2.0-cdh5.14.2
bin/hbase shell
create 'flink:data_orders',{NAME=>'f1',BLOCKCACHE=>true,BLOOMFILTER=>'ROW',DATA_BLOCK_ENCODING => 'PREFIX_TREE', BLOCKSIZE => '65536'}

第二步:修改maxwell配置文件
node03执行以下命令修改maxwell的配置文件,添加以下两个配置,配置数据的分区规则
cd /kkb/install/maxwell-1.21.1
vim config.properties

producer_partition_by=primary_key
kafka_partition_hash=murmur3
kafka_topic=flink_house

第三步:启动maxwell
node03执行以下命令启动maxwell
cd /kkb/install/maxwell-1.21.1
bin/maxwell
第四步:开发我们的数据解析程序
开发数据解析程序,解析kafka当中的json格式的数据,然后入库hbase即可
定义增量数据处理程序:
import java.util.Properties

import com.alibaba.fastjson.{JSON, JSONObject}
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.contrib.streaming.state.RocksDBStateBackend
import org.apache.flink.streaming.api.CheckpointingMode
import org.apache.flink.streaming.api.environment.CheckpointConfig
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer011



object IncrementOrder {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    //隐式转换
    import org.apache.flink.api.scala._
    //checkpoint配置
    environment.enableCheckpointing(100);
    environment.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
    environment.getCheckpointConfig.setMinPauseBetweenCheckpoints(500);
    environment.getCheckpointConfig.setCheckpointTimeout(60000);
    environment.getCheckpointConfig.setMaxConcurrentCheckpoints(1);
    environment.getCheckpointConfig.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
  environment.setStateBackend(new RocksDBStateBackend("hdfs://node01:8020/flink_kafka/checkpoints",true));

    val props = new Properties
    props.put("bootstrap.servers", "node01:9092")
    props.put("zookeeper.connect", "node01:2181")
    props.put("group.id", "flinkHouseGroup")
    props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer")
    props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer")
    props.put("auto.offset.reset", "latest")
    props.put("flink.partition-discovery.interval-millis", "30000")
    val kafkaSource = new FlinkKafkaConsumer011[String]("flink_house",new SimpleStringSchema(),props)

    kafkaSource.setCommitOffsetsOnCheckpoints(true)
    //设置statebackend
      val result: DataStream[String] = environment.addSource(kafkaSource)

    val orderResult: DataStream[OrderObj] = result.map(x => {
      val jsonObj: JSONObject = JSON.parseObject(x)
      val database: AnyRef = jsonObj.get("database")
      val table: AnyRef = jsonObj.get("table")
      val `type`: AnyRef = jsonObj.get("type")
      val string: String = jsonObj.get("data").toString
      OrderObj(database.toString,table.toString,`type`.toString,string)
    })
    orderResult.addSink(new HBaseSinkFunction)
    environment.execute()
  }
}
case class OrderObj(database:String,table:String,`type`:String,data:String) extends Serializable
定义插入数据到hbase的程序

import com.alibaba.fastjson.{JSON, JSONObject}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.functions.sink.{RichSinkFunction, SinkFunction}
import org.apache.hadoop.conf
import org.apache.hadoop.hbase.{HBaseConfiguration, TableName}
import org.apache.hadoop.hbase.client._

class HBaseSinkFunction extends RichSinkFunction[OrderObj]{

  var connection:Connection = _
  var hbTable:Table  =  _

  override def open(parameters: Configuration): Unit = {
    val configuration: conf.Configuration = HBaseConfiguration.create()
    configuration.set("hbase.zookeeper.quorum", "node01,node02,node03")
    configuration.set("hbase.zookeeper.property.clientPort", "2181")
    connection = ConnectionFactory.createConnection(configuration)
    hbTable = connection.getTable(TableName.valueOf("flink:data_orders"))
  }
  override def close(): Unit = {
    if(null != hbTable){
      hbTable.close()
    }
    if(null != connection){
      connection.close()
    }


  }

  def insertHBase(hbTable: Table, orderObj: OrderObj) = {
    val database: String = orderObj.database
    val table: String = orderObj.table
    val value: String = orderObj.`type`
    val orderJson: JSONObject = JSON.parseObject(orderObj.data)

    val orderId: String = orderJson.get("orderId").toString
    val orderNo: String = orderJson.get("orderNo").toString
    val userId: String = orderJson.get("userId").toString
    val goodId: String = orderJson.get("goodId").toString
    val goodsMoney: String = orderJson.get("goodsMoney").toString
    val realTotalMoney: String = orderJson.get("realTotalMoney").toString
    val payFrom: String = orderJson.get("payFrom").toString
    val province: String = orderJson.get("province").toString
    val createTime: String = orderJson.get("createTime").toString
    val put = new Put(orderId.getBytes())
    put.addColumn("f1".getBytes(),"orderNo".getBytes(),orderNo.getBytes())
    put.addColumn("f1".getBytes(),"userId".getBytes(),userId.getBytes())
    put.addColumn("f1".getBytes(),"goodId".getBytes(),goodId.getBytes())
    put.addColumn("f1".getBytes(),"goodsMoney".getBytes(),goodsMoney.getBytes())
    put.addColumn("f1".getBytes(),"realTotalMoney".getBytes(),realTotalMoney.getBytes())
    put.addColumn("f1".getBytes(),"payFrom".getBytes(),payFrom.getBytes())
    put.addColumn("f1".getBytes(),"province".getBytes(),province.getBytes())
    put.addColumn("f1".getBytes(),"createTime".getBytes(),createTime.getBytes())
/*
*
* */
    hbTable.put(put);


  }

  def deleteHBaseData(hbTable: Table, orderObj: OrderObj) = {
    val orderJson: JSONObject = JSON.parseObject(orderObj.data)
    val orderId: String = orderJson.get("orderId").toString
    val delete = new Delete(orderId.getBytes())
    hbTable.delete(delete)

  }

  override def invoke(orderObj: OrderObj, context: SinkFunction.Context[_]): Unit = {

    val database: String = orderObj.database
    val table: String = orderObj.table
    val typeResult: String = orderObj.`type`
    if(database.equalsIgnoreCase("product")  &&  table.equalsIgnoreCase("kaikeba_orders")){
      if(typeResult.equalsIgnoreCase("insert")){
          //插入hbase数据
        insertHBase(hbTable,orderObj)
      }else if(typeResult.equalsIgnoreCase("update")){
        //更新hbase数据
        insertHBase(hbTable,orderObj)

      }else if(typeResult.equalsIgnoreCase("delete")){
        //删除hbase数据
        deleteHBaseData(hbTable,orderObj)
      }
    }
  }
}

第五步:启动订单生成程序
启动我们开发的订单生成程序,然后观察hbase数据库当中的数据


16、基于kylin的预计算实现实时数据统计
1、kylin的基本介绍
Apache Kylin 是一个开源的分布式存储引擎,最初由 eBay 开发贡献至开源 社区。它提供 Hadoop 之上的 SQL 查询接口及多维分析(OLAP)能力以支持大规 模数据,能够处理 TB 乃至 PB 级别的分析任务,能够在亚秒级查询巨大的 Hive 表,并支持高并发。

1.1、为什么要使用kylin
自从 10 年前 Hadoop 诞生以来,大数据的存储和批处理问题均得到了妥善解 决,而如何高速地分析数据也就成为了下一个挑战。于是各式各样的“SQL on Hadoop”技术应运而生,其中以 Hive 为代表,Impala、Presto、Phoenix、Drill、 SparkSQL 等紧随其后。它们的主要技术是“大规模并行处理”(Massive Parallel Processing,MPP)和“列式存储”(Columnar Storage)。 
大规模并行处理可以调动多台机器一起进行并行计算,用线性增加的资源来 换取计算时间的线性下降。列式存储则将记录按列存放,这样做不仅可以在访问 时只读取需要的列,还可以利用存储设备擅长连续读取的特点,大大提高读取的 速率。这两项关键技术使得 Hadoop 上的 SQL 查询速度从小时提高到了分钟。 然而分钟级别的查询响应仍然离交互式分析的现实需求还很远。分析师敲入 查询指令,按下回车,还需要去倒杯咖啡,静静地等待查询结果。得到结果之后 才能根据情况调整查询,再做下一轮分析。如此反复,一个具体的场景分析常常 需要几小时甚至几天才能完成,效率低下。 这是因为大规模并行处理和列式存储虽然提高了计算和存储的速度,但并没 有改变查询问题本身的时间复杂度,也没有改变查询时间与数据量成线性增长的 关系这一事实。假设查询 1 亿条记录耗时 1 分钟,那么查询 10 亿条记录就需 10分钟,100 亿条记录就至少需要 1 小时 40 分钟。 当然,可以用很多的优化技术缩短查询的时间,比如更快的存储、更高效的压缩算法,等等,但总体来说,查询性能与数据量呈线性相关这一点是无法改变 的。虽然大规模并行处理允许十倍或百倍地扩张计算集群,以期望保持分钟级别 的查询速度,但购买和部署十倍或百倍的计算集群又怎能轻易做到,更何况还有 高昂的硬件运维成本。 另外,对于分析师来说,完备的、经过验证的数据模型比分析性能更加重要, 直接访问纷繁复杂的原始数据并进行相关分析其实并不是很友好的体验,特别是 在超大规模的数据集上,分析师将更多的精力花在了等待查询结果上,而不是在 更加重要的建立领域模型上。


1.2、kylin的使用场景
(1) 假如你的数据存储于 Hadoop 的 HDFS 分布式文件系统中,并且使用 Hive 来基于 HDFS 构建数据仓库系统,并进行数据分析,但是数据量巨大, 比如 PB 级别; 
(2) 同时也使用 HBase 来进行数据的存储和利于 HBase 的行键实现数据 的快速查询; 
(3) 数据分析平台的数据量逐日累积增加; 
(4) 对于数据分析的维度大概 10 个左右。 如果类似于上述的场景,那么非常适合使用 Apache Kylin 来做大数据的多维分析。 

1.3、kylin如何解决海量数据的查询问题
Apache Kylin 的初衷就是要解决千亿条、万亿条记录的秒级查询问 题,其中的关键就是要打破查询时间随着数据量成线性增长的这个规律。仔细思 考大数据 OLAP,可以注意到两个事实。 
大数据查询要的一般是统计结果,是多条记录经过聚合函数计算后的统计 值。原始的记录则不是必需的,或者访问频率和概率都极低。 
聚合是按维度进行的,由于业务范围和分析需求是有限的,有意义的维度 聚合组合也是相对有限的,一般不会随着数据的膨胀而增长。 
基于以上两点,我们可以得到一个新的思路——“预计算”。应尽量多地预 先计算聚合结果,在查询时刻应尽量使用预算的结果得出查询结果,从而避免直 接扫描可能无限增长的原始记录。 
举例来说,使用如下的 SQL 来查询 10 月 1 日那天销量最高的商品:



用传统的方法时需要扫描所有的记录,再找到 10 月 1 日的销售记录,然后
按商品聚合销售额,最后排序返回。假如 10 月 1 日有 1 亿条交易,那么查询必
须读取并累计至少 1 亿条记录,且这个查询速度会随将来销量的增加而逐步下降。如果日交易量提高一倍到 2 亿,那么查询执行的时间可能也会增加一倍。 而使用 预 计 算 的 方 法 则 会 事 先 按 维 度 [sell_date , item] 计 算  sum
(sell_amount)并存储下来,在查询时找到  10 月 1 日的销售商品就可以直接
排序返回了。读取的记录数最大不会超过维度[sell_date,item]的组合数。显 然这个数字将远远小于实际的销售记录,比如 10 月 1 日的 1 亿条交易包含了 100
万条商品,那么预计算后就只有 100 万条记录了,是原来的百分之一。并且这些 记录已经是按商品聚合的结果,因此又省去了运行时的聚合运算。从未来的发展 来看,查询速度只会随日期和商品数目的增长而变化,与销售记录的总数不再有 直接联系。假如日交易量提高一倍到 2 亿,但只要商品的总数不变,那么预计算 的结果记录总数就不会变,查询的速度也不会变。 
“预计算”就是 Kylin 在“大规模并行处理”和“列式存储”之外,提供给大数据分析的第三个关键技术。

2、Kylin前置基础知识了解
1、数据仓库、OLAP 与 BI
数据仓库
数据仓库,英文名称 Data Warehouse,简称 DW。《数据仓库》一书中的定义 为:数据仓库就是面向主题的、集成的、相对稳定的、随时间不断变化(不同时 间)的数据集合,用以支持经营管理中的决策制定过程、数据仓库中的数据面向 主题,与传统数据库面向应用相对应。 
利用数据仓库的方式存放的资料,具有一旦存入,便不会随时间发生变动的 特性,此外,存入的资料必定包含时间属性,通常一个数据仓库中会含有大量的 历史性资料,并且它可利用特定的分析方式,从其中发掘出特定的资讯。 
OLAP
1、OLAP的基本概念
OLAP(Online Analytical Process),联机分析处理,以多维度的方式分 析数据,而且能够弹性地提供上卷(Roll-up)、下钻(Drill-down)和切片(Slice) 等操作,它是呈现集成性决策信息的方法,多用于决策支持系统、商务智能或数 据仓库。其主要的功能在于方便大规模数据分析及统计计算,可对决策提供参考 和支持。与之相区别的是联机交易处理(OLTP),联机交易处理,更侧重于基本 的、日常的事务处理,包括数据的增删改查。 
OLAP 需要以大量历史数据为基础,再配合上时间点的差异,对多维 度及汇整型的信息进行复杂的分析。 
OLAP 需要用户有主观的信息需求定义,因此系统效率较佳。 
OLAP 的概念,在实际应用中存在广义和狭义两种不同的理解方式。广义上 的理解与字面上的意思相同,泛指一切不会对数据进行更新的分析处理。但更多 的情况下 OLAP 被理解为其狭义上的含义,即与多维分析相关,基于立方体(Cube) 计算而进行的分析。 
OLAP(online analytical processing)是一种软件技术,它使分析人员能够迅速、一致、交互地从各个方面观察信息,以达到深入理解数据的目的。从各方面观察信息,也就是从不同的维度分析数据,因此OLAP也成为多维分析。


2、OLAP的类型
也可以分为ROLAP和MOLAP

3、OLAP  CUBE

4、CUBE与 Cuboid


BI
BI(Business Intelligence),即商务智能,指用现代数据仓库技术、在线 分析技术、数据挖掘和数据展现技术进行数据分析以实现商业价值。

2、事实表与维度表
事实表(Fact Table)是指存储有事实记录的表,如系统日志、销售记录等; 事实表的记录在不断地动态增长,所以它的体积通常远大于其他表。 
维度表(Dimension Table)或维表,有时也称查找表(Lookup Table),是 分析事实的一种角度,是与事实表相对应的一种表;它保存了维度的属性值,可 以跟事实表做关联;相当于将事实表上经常重复出现的属性抽取、规范出来用一 张表进行管理。常见的维度表有:日期表(存储与日期对应的周、月、季度等的 属性)、地点表(包含国家、省/州、城市等属性)等。使用维度表有诸多好处, 具体如下。 
·缩小了事实表的大小。 
·便于维度的管理和维护,增加、删除和修改维度的属性,不必对事实表的 大量记录进行改动。 
·维度表可以为多个事实表重用,以减少重复工作。 

3、维度与度量
维度是指审视数据的角度,它通常是数据记录的一个属性,例如时间、地点 等。 
度量是基于数据所计算出来的考量值;它通常是一个数值,如总销售额、不 同的用户数等。 分析人员往往要结合若干个维度来审查度量值,以便在其中找到变化规律。 在一个 SQL 查询中,Group By 的属性通常就是维度,而所计算的值则是度量。 如下面的示例: 



  在上面的这个查询中,part_dt 和 lstg_site_id 是维度,sum(price)和 
count(distinct seller_id)是度量。


4、数据仓库建模常用手段方式
星型模型:
星形模型中有一张事实表,以及零个或多个维度表;事实表与维度表通过主 键外键相关联,维度表之间没有关联,就像很多星星围绕在一个恒星周围,故取 名为星形模型。 



雪花模型:
若将星形模型中某些维度的表再做规范,抽取成更细的维度表,然后让维
度表之间也进行关联,那么这种模型称为雪花模型。 




星座模式:
星座模式是星型模式延伸而来,星型模式是基于一张事实表的,而星座模式是基于多张事实表的,而且共享维度信息。
前面介绍的两种维度建模方法都是多维表对应单事实表,但在很多时候维度空间内的事实表不止一个,而一个维表也可能被多个事实表用到。在业务发展后期,绝大部分维度建模都采用的是星座模式。



注意:Kylin 只支持星形模型的数据集


5、数据立方体
Cube(或 Data Cube),即数据立方体,是一种常用于数据分析与索引的技术;它可以对原始数据建立多维度索引。通过 Cube 对数据进行分析,可以大大 加快数据的查询效率。 
Cuboid 在 Kylin 中特指在某一种维度组合下所计算的数据。 给定一个数据模型,我们可以对其上的所有维度进行组合。对于 N 个维度来
说,组合的所有可能性共有 2 的 N 次方种。对于每一种维度的组合,将度量做 聚合运算,然后将运算的结果保存为一个物化视图,称为 Cuboid。 
所有维度组合的 Cuboid 作为一个整体,被称为 Cube。所以简单来说,一个 Cube 就是许多按维度聚合的物化视图的集合。 
下面来列举一个具体的例子。假定有一个电商的销售数据集,其中维度包括 时间(Time)、商品(Item)、地点(Location)和供应商(Supplier),度量为销 售额(GMV)。那么所有维度的组合就有 2 的 4 次方 =16 种,比如一维度(1D) 的组合有[Time]、[Item]、[Location]、[Supplier]4 种;二维度(2D)的组合 有[Time,Item]、[Time,Location]、[Time、Supplier]、[Item,Location]、 [Item,Supplier]、[Location,Supplier]6 种;三维度(3D)的组合也有 4 种; 最后零维度(0D)和四维度(4D)的组合各有 1 种,总共就有 16 种组合。




6、Kylin的工作原理
Apache Kylin 的工作原理就是对数据模型做 Cube 预计算,并利用计算的结 果加速查询,具体工作过程如下。 
1)指定数据模型,定义维度和度量。 
2)预计算 Cube,计算所有 Cuboid 并保存为物化视图。 
3)执行查询时,读取 Cuboid,运算,产生查询结果。 
由于 Kylin 的查询过程不会扫描原始记录,而是通过预计算预先完成表的关 联、聚合等复杂运算,并利用预计算的结果来执行查询,因此相比非预计算的查 询技术,其速度一般要快一到两个数量级,并且这点在超大的数据集上优势更明 显。当数据集达到千亿乃至万亿级别时,Kylin 的速度甚至可以超越其他非预计算技术 1000 倍以上。 

7、Kylin的体系架构
Apache Kylin 系统可以分为在线查询和离线构建两部分,技术架构如图所 示,在线查询的模块主要处于上半区,而离线构建则处于下半区。 


1)REST Server
REST Server是一套面向应用程序开发的入口点,旨在实现针对Kylin平台的应用开发工作。 此类应用程序可以提供查询、获取结果、触发cube构建任务、获取元数据以及获取用户权限等等。另外可以通过Restful接口实现SQL查询。
2)查询引擎(Query Engine)
当cube准备就绪后,查询引擎就能够获取并解析用户查询。它随后会与系统中的其它组件进行交互,从而向用户返回对应的结果。 
3)路由器(Routing)
在最初设计时曾考虑过将Kylin不能执行的查询引导去Hive中继续执行,但在实践后发现Hive与Kylin的速度差异过大,导致用户无法对查询的速度有一致的期望,很可能大多数查询几秒内就返回结果了,而有些查询则要等几分钟到几十分钟,因此体验非常糟糕。最后这个路由功能在发行版中默认关闭。
4)元数据管理工具(Metadata)
Kylin是一款元数据驱动型应用程序。元数据管理工具是一大关键性组件,用于对保存在Kylin当中的所有元数据进行管理,其中包括最为重要的cube元数据。其它全部组件的正常运作都需以元数据管理工具为基础。 Kylin的元数据存储在hbase中。 
5)任务引擎(Cube Build Engine)
这套引擎的设计目的在于处理所有离线任务,其中包括shell脚本、Java API以及Map Reduce任务等等。任务引擎对Kylin当中的全部任务加以管理与协调,从而确保每一项任务都能得到切实执行并解决其间出现的故障。


8、Kylin特点
Kylin的主要特点包括支持SQL接口、支持超大规模数据集、亚秒级响应、可伸缩性、高吞吐率、BI工具集成等。
1)标准SQL接口:Kylin是以标准的SQL作为对外服务的接口。
2)支持超大数据集:Kylin对于大数据的支撑能力可能是目前所有技术中最为领先的。早在2015年eBay的生产环境中就能支持百亿记录的秒级查询,之后在移动的应用场景中又有了千亿记录秒级查询的案例。
3)亚秒级响应:Kylin拥有优异的查询相应速度,这点得益于预计算,很多复杂的计算,比如连接、聚合,在离线的预计算过程中就已经完成,这大大降低了查询时刻所需的计算量,提高了响应速度。
4)可伸缩性和高吞吐率:单节点Kylin可实现每秒70个查询,还可以搭建Kylin的集群。
5)BI工具集成
Kylin可以与现有的BI工具集成,具体包括如下内容。
ODBC:与Tableau、Excel、PowerBI等工具集成
JDBC:与Saiku、BIRT等Java工具集成
RestAPI:与JavaScript、Web网页集成
Kylin开发团队还贡献了Zepplin的插件,也可以使用Zepplin来访问Kylin服务。

3、Kylin的环境安装
1)官网地址
http://kylin.apache.org/cn/
2)官方文档
http://kylin.apache.org/cn/docs/
3)下载地址
http://kylin.apache.org/cn/download/

单节点服务模式安装
kylin的运行环境分为单机模式和集群模式,单机模式只需要在任意一台机器安装一台kylin服务即可,集群模式可以在所有机器上面都安装,然后所有机器的kylin组成集群

kylin的服务安装需要依赖于 zookeeper,hdfs,yarn,hive,hbase等各种服务,在安装kylin之前需要保证我们的zookeeper,hdfs,yarn,hive以及hbase的服务都是正常的
主机名
服务	Node01	Node02	Node03
zookeeper	QuorumPeerMain	QuorumPeerMain	QuorumPeerMain
hdfs	namenode		
	secondaryNameNode		
	DataNode	DataNode	DataNode
Yarn	ResourceManager		
	NodeManager	NodeManager	NodeManager
MapReduce	JobHistoryServer		
HBase	HMaster		
	HRegionServer	HRegionServer	HRegionServer
Hive			HiveServer2
			MetaStore

第一步:下载kylin安装包上传并解压
kylin安装包下载地址为
http://mirrors.tuna.tsinghua.edu.cn/apache/kylin/apache-kylin-2.6.3/apache-kylin-2.6.3-bin-cdh57.tar.gz
将安装包上传到node03服务器的/kkb/soft路径下,并解压到/kkb/install
node03执行以下命令,进行解压
cd /kkb/soft
tar -zxf apache-kylin-2.6.3-bin-cdh57.tar.gz  -C /kkb/install/

第二步:node03服务器开发环境变量配置
node03服务器添加以下环境变量:

sudo vim /etc/profile

export JAVA_HOME=/kkb/install/jdk1.8.0_141
export PATH=:$JAVA_HOME/bin:$PATH

export HADOOP_HOME=/kkb/install/hadoop-2.6.0-cdh5.14.2
export PATH=:$HADOOP_HOME/bin:$PATH

export HBASE_HOME=/kkb/install/hbase-1.2.0-cdh5.14.2
export PATH=:$HBASE_HOME/bin:$PATH

export HIVE_HOME=/kkb/install/hive-1.1.0-cdh5.14.2
export PATH=:$HIVE_HOME/bin:$PATH

export HCAT_HOME=/kkb/install/hive-1.1.0-cdh5.14.2
export PATH=:$HCAT_HOME/hcatalog:$PATH

export KYLIN_HOME=/kkb/install/apache-kylin-2.6.3-bin-cdh57
export PATH=:$KYLIN_HOME/bin:$PATH

export dir=/kkb/install/apache-kylin-2.6.3-bin-cdh57/bin
export PATH=$dir:$PATH

更改完了环境变量,记得source /etc/profile 生效
第三步:node03启动kylin服务
node03执行以下命令启动kylin服务

cd /kkb/install/apache-kylin-2.6.3-bin-cdh57
bin/kylin.sh start
第四步:浏览器访问kylin服务
浏览器界面访问kylin服务
http://node03.kaikeba.com:7070/kylin/
用户名:ADMIN
密码:KYLIN

kylin的集群环境安装
单节点的kylin环境,主要用于我们方便测试学习,实际工作当中,我们主要还是使用kylin的集群模式来进行开发,接下来我们就来看一下kylin的集群模式该如何运行
Kylin的实例是无状态的,运行时的状态保存在Hbase的元数据中(kylin.metadata.url指定)
只要每个实例都指向读取共同的元数据就可以完成集群的部署(即元数据共享)
对于每个实例,都必须指定实例运行的模式(kylin.server.mode),共有3种模式
job 只能运行job引擎
query 只能运行查询引擎
all 既可以运行job 又可以运行query
query模式下只支持sql查询,不执行cube的构建等相关操作。 特别注意:kylin集群中只能有一个实例运行job引擎,其他必须是query模式。



集群模式重要配置参数介绍
当kylin以集群模式运行的时候,会存在多个运行实例,可以通过conf/kylin.properties中两个参数进行设置

kylin.server.cluster-servers

列出所有rest   web  Servers,使得实例之间进行同步,比如设置为:
kylin.server.cluster-servers=node01:7070,node02:7070,node03:7070


kylin.server.mode

确保一个实例配置的是all或者job,其他都必须是query模式。

第一步:将node03服务器的kylin安装包分发到其他机器
将node03服务器/kkb/install路径下的kylin的安装包分发到其他服务器上面去
node03执行以下命令停止kylin服务,然后将kylin安装包分发到其他服务器上面去
node03执行以下命令
cd /kkb/install/apache-kylin-2.6.3-bin-cdh57
bin/kylin.sh stop
cd /kkb/install/
scp -r apache-kylin-2.6.3-bin-cdh57/ node02:$PWD
scp -r apache-kylin-2.6.3-bin-cdh57/ node01:$PWD

第二步:三台机器修改kylin配置文件kylin.properties
三台服务器分别修改kylin配置文件kylin.properties

node01服务器修改配置文件

cd /kkb/install/apache-kylin-2.6.3-bin-cdh57/conf/
vim kylin.properties

kylin.metadata.url=kylin_metadata@hbase
kylin.env.hdfs-working-dir=/kylin
kylin.server.mode=query
kylin.server.cluster-servers=node01:7070,node02:7070,node03:7070
kylin.storage.url=hbase
kylin.job.retry=2
kylin.job.max-concurrent-jobs=10
kylin.engine.mr.yarn-check-interval-seconds=10
kylin.engine.mr.reduce-input-mb=500
kylin.engine.mr.max-reducer-number=500
kylin.engine.mr.mapper-input-rows=1000000

node02服务器修改配置文件

cd /kkb/install/apache-kylin-2.6.3-bin-cdh57/conf/
vim kylin.properties

kylin.metadata.url=kylin_metadata@hbase
kylin.env.hdfs-working-dir=/kylin
kylin.server.mode=query
kylin.server.cluster-servers=node01:7070,node02:7070,node03:7070
kylin.storage.url=hbase
kylin.job.retry=2
kylin.job.max-concurrent-jobs=10
kylin.engine.mr.yarn-check-interval-seconds=10
kylin.engine.mr.reduce-input-mb=500
kylin.engine.mr.max-reducer-number=500
kylin.engine.mr.mapper-input-rows=1000000


node03服务器修改配置文件
cd /kkb/install/apache-kylin-2.6.3-bin-cdh57/conf/
vim kylin.properties

kylin.metadata.url=kylin_metadata@hbase
kylin.env.hdfs-working-dir=/kylin
kylin.server.mode=all
kylin.server.cluster-servers=node01:7070,node02:7070,node03:7070
kylin.storage.url=hbase
kylin.job.retry=2
kylin.job.max-concurrent-jobs=10
kylin.engine.mr.yarn-check-interval-seconds=10
kylin.engine.mr.reduce-input-mb=500
kylin.engine.mr.max-reducer-number=500
kylin.engine.mr.mapper-input-rows=1000000

第三步:三台机器配置环境变量
三台机器编辑/etc/profile,添加环境变量
注意:需要将hive的安装文件夹,每一台机器都拷贝
sudo vim  /etc/profile

export JAVA_HOME=/kkb/install/jdk1.8.0_141
export PATH=:$JAVA_HOME/bin:$PATH

export HADOOP_HOME=/kkb/install/hadoop-2.6.0-cdh5.14.2
export PATH=:$HADOOP_HOME/bin:$PATH

export HBASE_HOME=/kkb/install/hbase-1.2.0-cdh5.14.2
export PATH=:$HBASE_HOME/bin:$PATH

export HIVE_HOME=/kkb/install/hive-1.1.0-cdh5.14.2
export PATH=:$HIVE_HOME/bin:$PATH

export HCAT_HOME=/kkb/install/hive-1.1.0-cdh5.14.2
export PATH=:$HCAT_HOME/hcatalog:$PATH

export KYLIN_HOME=/kkb/install/apache-kylin-2.6.3-bin-cdh57
export PATH=:$KYLIN_HOME/bin:$PATH

export dir=/kkb/install/apache-kylin-2.6.3-bin-cdh57/bin
export PATH=$dir:$PATH

export HBASE_CLASSPATH=/kkb/install/hbase-1.2.0-cdh5.14.2
export PATH=:$HBASE_CLASSPATH:$PATH

第四步:三台机器启动kylin服务
三台机器执行以下命令启动kylin服务
cd /kkb/soft/apache-kylin-2.6.3-bin-cdh57
bin/kylin.sh start
第五步:node02安装nginx实现请求负载均衡
注意:nginx的安装需要使用root用户来进行安装
在node02服务器上面安装nginx服务,实现请求负载均衡
将nginx的安装包上传到/kkb/soft路径下,然后解压,并对nginx的配置文件进行配置,然后启动nginx服务即可

1、解压nginx压缩吧
cd /kkb/soft/
tar -zxf nginx-1.8.1.tar.gz -C /kkb/install/

2、编译nginx

yum -y install gcc pcre-devel zlib-devel openssl openssl-devel
cd /kkb/install/nginx-1.8.1/
./configure --prefix=/usr/local/nginx 
make
make install
3、修改nginx的配置文件
node02执行以下命令修改nginx的配置文件

cd /usr/local/nginx/conf
vim nginx.conf
添加以下内容
在nginx.conf配置文件的最后一个 “}” 上面一行,添加以下内容
upstream kaikeba {
		least_conn;
		server 192.168.52.100:7070 weight=8;
		server 192.168.52.110:7070 weight=7;
		server 192.168.52.120:7070 weight=7;
	}
	server {
		listen 8066;
		server_name localhost;
		location / {
		proxy_pass http://kaikeba;
		}
	}

4、nginx的启动与停止命令
nginx的启动命令,node02执行以下命令启动nginx服务
cd /usr/local/nginx/
sbin/nginx  -c conf/nginx.conf

nginx的停止命令,node02执行以下命令停止nginx服务
cd /usr/local/nginx/
sbin/nginx -s stop
第六步:浏览器界面访问
http://node02:8066/kylin/

访问这个网址,就可以实现负载均衡
4、kylin的入门使用
我们kylin环境安装成功之后,我们就可以在hive当中创建数据库以及数据库表,然后通过kylin来实现数据的查询

第一步:创建hive数据库以及表并加载以下数据


将以上两份文件上传到node03服务器的/kkb/install路径下,然后执行以下命令,创建hive数据库以及数据库表,并加载数据

cd /kkb/install/hive-1.1.0-cdh5.14.2/
bin/beeline
创建数据库并使用该数据库
create database kylin_hive;
use kylin_hive;
(1)创建部门表
create external table if not exists kylin_hive.dept(
deptno int,
dname string,
loc int )
row format delimited fields terminated by '\t';
(2)创建员工表
create external table if not exists kylin_hive.emp(
empno int,
ename string,
job string,
mgr int,
hiredate string, 
sal double, 
comm double,
deptno int)
row format delimited fields terminated by '\t';
(3)查看创建的表
jdbc:hive2://node03:10000> show tables;
OK
tab_name
dept
emp
(4)向外部表中导入数据导入数据
load data local inpath '/kkb/install/dept.txt' into table kylin_hive.dept;
load data local inpath '/kkb/install/emp.txt' into table kylin_hive.emp;
查询结果
jdbc:hive2://node03:10000> select * from emp;
jdbc:hive2://node03:10000> select * from dept;


第二步:访问kylin浏览器界面,并创建project
直接在浏览器界面访问
http://node02:8066/kylin/login  并登录kylin,用户名  ADMIN,密码KYLIN

点击页面 + 号,来创建工程



输入工程名称以及工程描述


为工程添加数据源

添加数据源表



第三步:为kylin添加models
1、回到models页面


2、添加new models



3、填写model name之后,继续下一步


4、选择事实表
这里就选择emp作为事实表


5、添加维度表
添加我们的DEPT作为维度表,并选择我们的join方式,以及join连接字段






6、选择聚合维度信息



7、选择度量信息




8、添加分区信息及过滤条件之后“Save”



第四步:通过kylin来构建cube
前面我们已经创建了project和我们的models,接下来我们就来构建我们的cube
1、页面添加,创建一个new  cube









2、选择我们的model以及cube name




3、添加我们的自定义维度



4、添加统计维度



5、设置多个分区cube合并信息
因为我们这里是全量统计,不涉及多个分区cube进行合并,所以不用设置历史多个cube进行合并


6、高级设置
高级设置我们这里暂时也不做任何设置,后续再单独详细讲解

7、额外的其他的配置属性,这里也暂时不做配置



8、完成,保存配置



第五步:构建我们的cube
将我们的cube进行构建




第六步:对我们的数进行查询
前面构建好了我们的cube之后,接下来我们就可以对我们的数据进行分析
SELECT  DEPT.DNAME ,SUM(EMP.SAL) FROM EMP  INNER JOIN DEPT  ON DEPT.DEPTNO = EMP.DEPTNO  GROUP BY DEPT.DNAME



我们会发现,数据的查询速度非常快,马上就可以产出结果了,通过kylin的与计算,已经将我们各种可能性的结果都获取到了,我们这里直接就可以得到我们计算完成的结果,所以结果非常快就能计算出来







5、kylin的构建流程
双击下面图片,即可播放PPT浏览查看


6、cube构建算法
1、逐层构建算法

我们知道,一个N维的Cube,是由1个N维子立方体、N个(N-1)维子立方体、N*(N-1)/2个(N-2)维子立方体、......、N个1维子立方体和1个0维子立方体构成,总共有2^N个子立方体组成,在逐层算法中,按维度数逐层减少来计算,每个层级的计算(除了第一层,它是从原始数据聚合而来),是基于它上一层级的结果来计算的。比如,[Group by A, B]的结果,可以基于[Group by A, B, C]的结果,通过去掉C后聚合得来的;这样可以减少重复计算;当 0维度Cuboid计算出来的时候,整个Cube的计算也就完成了。
每一轮的计算都是一个MapReduce任务,且串行执行;一个N维的Cube,至少需要N次MapReduce Job。


算法优点:
1)此算法充分利用了MapReduce的优点,处理了中间复杂的排序和shuffle工作,故而算法代码清晰简单,易于维护;
2)受益于Hadoop的日趋成熟,此算法非常稳定,即便是集群资源紧张时,也能保证最终能够完成。
算法缺点:
1)当Cube有比较多维度的时候,所需要的MapReduce任务也相应增加;由于Hadoop的任务调度需要耗费额外资源,特别是集群较庞大的时候,反复递交任务造成的额外开销会相当可观;
2)由于Mapper逻辑中并未进行聚合操作,所以每轮MR的shuffle工作量都很大,导致效率低下。
3)对HDFS的读写操作较多:由于每一层计算的输出会用做下一层计算的输入,这些Key-Value需要写到HDFS上;当所有计算都完成后,Kylin还需要额外的一轮任务将这些文件转成HBase的HFile格式,以导入到HBase中去;
总体而言,该算法的效率较低,尤其是当Cube维度数较大的时候。


2、快速构建算法


也被称作“逐段”(By Segment) 或“逐块”(By Split) 算法,从1.5.x开始引入该算法,该算法的主要思想是,每个Mapper将其所分配到的数据块,计算成一个完整的小Cube 段(包含所有Cuboid)。每个Mapper将计算完的Cube段输出给Reducer做合并,生成大Cube,也就是最终结果。如图所示解释了此流程。




与旧算法相比,快速算法主要有两点不同:
1) Mapper会利用内存做预聚合,算出所有组合;Mapper输出的每个Key都是不同的,这样会减少输出到Hadoop MapReduce的数据量,Combiner也不再需要;
2)一轮MapReduce便会完成所有层次的计算,减少Hadoop任务的调配。


7、cube构建的优化
从之前章节的介绍可以知道,在没有采取任何优化措施的情况下,Kylin会对每一种维度的组合进行预计算,每种维度的组合的预计算结果被称为Cuboid。假设有4个维度,我们最终会有24 =16个Cuboid需要计算。
但在现实情况中,用户的维度数量一般远远大于4个。假设用户有10 个维度,那么没有经过任何优化的Cube就会存在210 =1024个Cuboid;而如果用户有20个维度,那么Cube中总共会存在220 =1048576个Cuboid。虽然每个Cuboid的大小存在很大的差异,但是单单想到Cuboid的数量就足以让人想象到这样的Cube对构建引擎、存储引擎来说压力有多么巨大。因此,在构建维度数量较多的Cube时,尤其要注意Cube的剪枝优化(即减少Cuboid的生成)。
1 使用衍生维度(derived dimension)
衍生维度用于在有效维度内将维度表上的非主键维度排除掉,并使用维度表的主键(其实是事实表上相应的外键)来替代它们。Kylin会在底层记录维度表主键与维度表其他维度之间的映射关系,以便在查询时能够动态地将维度表的主键“翻译”成这些非主键维度,并进行实时聚合。

虽然衍生维度具有非常大的吸引力,但这也并不是说所有维度表上的维度都得变成衍生维度,如果从维度表主键到某个维度表维度所需要的聚合工作量非常大,则不建议使用衍生维度。
2 使用聚合组(Aggregation group)
聚合组(Aggregation Group)是一种强大的剪枝工具。聚合组假设一个Cube的所有维度均可以根据业务需求划分成若干组(当然也可以是一个组),由于同一个组内的维度更可能同时被同一个查询用到,因此会表现出更加紧密的内在关联。每个分组的维度集合均是Cube所有维度的一个子集,不同的分组各自拥有一套维度集合,它们可能与其他分组有相同的维度,也可能没有相同的维度。每个分组各自独立地根据自身的规则贡献出一批需要被物化的Cuboid,所有分组贡献的Cuboid的并集就成为了当前Cube中所有需要物化的Cuboid的集合。不同的分组有可能会贡献出相同的Cuboid,构建引擎会察觉到这点,并且保证每一个Cuboid无论在多少个分组中出现,它都只会被物化一次。
对于每个分组内部的维度,用户可以使用如下三种可选的方式定义,它们之间的关系,具体如下。
1)强制维度(Mandatory),如果一个维度被定义为强制维度,那么这个分组产生的所有Cuboid中每一个Cuboid都会包含该维度。每个分组中都可以有0个、1个或多个强制维度。如果根据这个分组的业务逻辑,则相关的查询一定会在过滤条件或分组条件中,因此可以在该分组中把该维度设置为强制维度。

2)层级维度(Hierarchy),每个层级包含两个或更多个维度。假设一个层级中包含D1,D2…Dn这n个维度,那么在该分组产生的任何Cuboid中, 这n个维度只会以(),(D1),(D1,D2)…(D1,D2…Dn)这n+1种形式中的一种出现。每个分组中可以有0个、1个或多个层级,不同的层级之间不应当有共享的维度。如果根据这个分组的业务逻辑,则多个维度直接存在层级关系,因此可以在该分组中把这些维度设置为层级维度。

3)联合维度(Joint),每个联合中包含两个或更多个维度,如果某些列形成一个联合,那么在该分组产生的任何Cuboid中,这些联合维度要么一起出现,要么都不出现。每个分组中可以有0个或多个联合,但是不同的联合之间不应当有共享的维度(否则它们可以合并成一个联合)。如果根据这个分组的业务逻辑,多个维度在查询中总是同时出现,则可以在该分组中把这些维度设置为联合维度。

这些操作可以在Cube Designer的Advanced Setting中的Aggregation Groups区域完成,如下图所示。

聚合组的设计非常灵活,甚至可以用来描述一些极端的设计。假设我们的业务需求非常单一,只需要某些特定的Cuboid,那么可以创建多个聚合组,每个聚合组代表一个Cuboid。具体的方法是在聚合组中先包含某个Cuboid所需的所有维度,然后把这些维度都设置为强制维度。这样当前的聚合组就只能产生我们想要的那一个Cuboid了。
再比如,有的时候我们的Cube中有一些基数非常大的维度,如果不做特殊处理,它就会和其他的维度进行各种组合,从而产生一大堆包含它的Cuboid。包含高基数维度的Cuboid在行数和体积上往往非常庞大,这会导致整个Cube的膨胀率变大。如果根据业务需求知道这个高基数的维度只会与若干个维度(而不是所有维度)同时被查询到,那么就可以通过聚合组对这个高基数维度做一定的“隔离”。我们把这个高基数的维度放入一个单独的聚合组,再把所有可能会与这个高基数维度一起被查询到的其他维度也放进来。这样,这个高基数的维度就被“隔离”在一个聚合组中了,所有不会与它一起被查询到的维度都没有和它一起出现在任何一个分组中,因此也就不会有多余的Cuboid产生。这点也大大减少了包含该高基数维度的Cuboid的数量,可以有效地控制Cube的膨胀率。

3 并发粒度优化
当Segment中某一个Cuboid的大小超出一定的阈值时,系统会将该Cuboid的数据分片到多个分区中,以实现Cuboid数据读取的并行化,从而优化Cube的查询速度。具体的实现方式如下:构建引擎根据Segment估计的大小,以及参数“kylin.hbase.region.cut”的设置决定Segment在存储引擎中总共需要几个分区来存储,如果存储引擎是HBase,那么分区的数量就对应于HBase中的Region数量。kylin.hbase.region.cut的默认值是5.0,单位是GB,也就是说对于一个大小估计是50GB的Segment,构建引擎会给它分配10个分区。用户还可以通过设置kylin.hbase.region.count.min(默认为1)和kylin.hbase.region.count.max(默认为500)两个配置来决定每个Segment最少或最多被划分成多少个分区。

由于每个Cube的并发粒度控制不尽相同,因此建议在Cube Designer 的Configuration Overwrites(上图所示)中为每个Cube量身定制控制并发粒度的参数。假设将把当前Cube的kylin.hbase.region.count.min设置为2,kylin.hbase.region.count.max设置为100。这样无论Segment的大小如何变化,它的分区数量最小都不会低于2,最大都不会超过100。相应地,这个Segment背后的存储引擎(HBase)为了存储这个Segment,也不会使用小于两个或超过100个的分区。我们还调整了默认的kylin.hbase.region.cut,这样50GB的Segment基本上会被分配到50个分区,相比默认设置,我们的Cuboid可能最多会获得5倍的并发量。
4 Row Key优化
Kylin会把所有的维度按照顺序组合成一个完整的Rowkey,并且按照这个Rowkey升序排列Cuboid中所有的行。
设计良好的Rowkey将更有效地完成数据的查询过滤和定位,减少IO次数,提高查询速度,维度在rowkey中的次序,对查询性能有显著的影响。
Row key的设计原则如下:
1)被用作where过滤的维度放在前边。

2)基数大的维度放在基数小的维度前边。







5、增量cube构建
我们前面可以构建全量cube,也可以实现增量cube的构建,就是通过分区表的分区时间字段来进行怎量构建
1、更改model





2、更改cube


8、备份以及恢复kylin的元数据信息
Kylin组织它所有的元数据(包括cube descriptions and instances, projects, inverted index description and instances,jobs, tables and dictionaries)作为一个层次的文件系统。
然而,Kylin使用HBase来进行存储,而不是普通的文件系统。
我们可以从Kylin的配置文件kylin.properties中查看到:
## The metadata store in hbase
kylin.metadata.url=kylin_metadata@hbase
表示Kylin的元数据被保存在HBase的kylin_metadata表中。

Kylin自身提供了元数据的备份程序,我们可以执行程序看一下帮助信息:

bin/metastore.sh
usage: metastore.sh backup
metastore.sh fetch DATA
metastore.sh reset
metastore.sh refresh-cube-signature
metastore.sh restore PATH_TO_LOCAL_META
metastore.sh list RESOURCE_PATH
metastore.sh cat RESOURCE_PATH
metastore.sh remove RESOURCE_PATH
metastore.sh clean [--delete true]

备份元数据
bin/metastore.sh backup

恢复元数据
bin/metastore.sh reset

接着,上传备份的元数据到Kylin的元数据中
bin/metastore.sh restore $KYLIN_HOME/meta_backups/meta_xxxx_xx_xx_xx_xx_xx
等待操作成功,用户在页面点击Reload Metadata按钮对元数据缓存进行刷新,即可看到最新的元数据

9、kylin的垃圾清理
当kylin运行一段时间后,有很多数据因为不在使用就变成了垃圾数据,这些数据占据着HDFS HBase等资源,当积累到一定程度会对集群性能产生影响。

清理元数据
清理元数据指从kylin元数据中清理掉无用的资源。比如字典表的快照变得无用了。
步骤:
检查哪些资源可以清理,这一步不会删除任何东西:
bin/metastore.sh clean
这会列出所有可以被清理的资源供用户核对,并不会实际上进行删除。
在上述命令中 添加 --delete true .这样就会清理掉晚一点资源,注意操作前最好备份一下元数据
bin/metastore.sh clean --delete true
清理存储器数据
1. 检查哪些资源需要被清理,这个操作不会删除任何内容:
${KYLIN_HOME}/bin/kylin.sh org.apache.kylin.storage.hbase.util.StorageCleanupJob --delete
false
2. 根据上面的输出结果,挑选一两个资源看看是否是不再需要的。接着,在上面的命令基础上添加“–
delete true”选项,开始执行清理操作,命令执行完成后,中间的HDFS文件和HTables表就被删除了。

${KYLIN_HOME}/bin/kylin.sh org.apache.kylin.storage.hbase.util.StorageCleanupJob --delete
true

10、BI工具集成
http://kylin.apache.org/cn/docs/howto/howto_use_restapi.html
官方文档使用说明

可以与Kylin结合使用的可视化工具很多,例如:
ODBC:与Tableau、Excel、PowerBI等工具集成
JDBC:与Saiku、BIRT等Java工具集成
RestAPI:与JavaScript、Web网页集成
Kylin开发团队还贡献了Zepplin的插件,也可以使用Zepplin来访问Kylin服务。
1、JDBC
1)新建项目并导入依赖
<dependencies>
    <dependency>
        <groupId>org.apache.kylin</groupId>
        <artifactId>kylin-jdbc</artifactId>
        <version>2.5.1</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <!-- 限制jdk版本插件 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
    </plugins>
</build>

2)编码
package com.kkb.kylin;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class KylinJdbc {
    public static void main(String[] args) throws Exception {

        //Kylin_JDBC 驱动
        String KYLIN_DRIVER = "org.apache.kylin.jdbc.Driver";

        //Kylin_URL
        String KYLIN_URL = "jdbc:kylin://node02:8066/kylin_hive";

        //Kylin的用户名
        String KYLIN_USER = "ADMIN";

        //Kylin的密码
        String KYLIN_PASSWD = "KYLIN";

        //添加驱动信息
        Class.forName(KYLIN_DRIVER);

        //获取连接
        Connection connection = DriverManager.getConnection(KYLIN_URL, KYLIN_USER, KYLIN_PASSWD);

        //预编译SQL
        PreparedStatement ps = connection.prepareStatement("SELECT sum(sal) FROM emp group by deptno");

        //执行查询
        ResultSet resultSet = ps.executeQuery();

        //遍历打印
        while (resultSet.next()) {
            System.out.println(resultSet.getInt(1));
        }
    }
}

3)结果展示



11、使用kylin来分析我们Hbase当中的数据
前面我们已经通过flink将数据介入到了hbase当中去了,那么我们接下来就可以通过hive整合hbase,将hbase当中的数据映射到hive表当中来,然后通过kylin来对hive当中的数据进行预分析,实现实时数仓的统计功能
第一步:拷贝hbase的五个jar包到hive的lib目录下
将我们HBase的五个jar包拷贝到hive的lib目录下
hbase的jar包都在/kkb/install/hbase-1.2.0-cdh5.14.2/lib
我们需要拷贝五个jar包名字如下
hbase-client-1.2.0-cdh5.14.2.jar         	  
hbase-hadoop2-compat-1.2.0-cdh5.14.2.jar 
hbase-hadoop-compat-1.2.0-cdh5.14.2.jar  
hbase-it-1.2.0-cdh5.14.2.jar    
hbase-server-1.2.0-cdh5.14.2.jar
我们直接在node03执行以下命令,通过创建软连接的方式来进行jar包的依赖

ln -s /kkb/install/hbase-1.2.0-cdh5.14.2/lib/hbase-client-1.2.0-cdh5.14.2.jar              /kkb/install/hive-1.1.0-cdh5.14.2/lib/hbase-client-1.2.0-cdh5.14.2.jar             
ln -s /kkb/install/hbase-1.2.0-cdh5.14.2/lib/hbase-hadoop2-compat-1.2.0-cdh5.14.2.jar      /kkb/install/hive-1.1.0-cdh5.14.2/lib/hbase-hadoop2-compat-1.2.0-cdh5.14.2.jar             
ln -s /kkb/install/hbase-1.2.0-cdh5.14.2/lib/hbase-hadoop-compat-1.2.0-cdh5.14.2.jar       /kkb/install/hive-1.1.0-cdh5.14.2/lib/hbase-hadoop-compat-1.2.0-cdh5.14.2.jar            
ln -s /kkb/install/hbase-1.2.0-cdh5.14.2/lib/hbase-it-1.2.0-cdh5.14.2.jar     /kkb/install/hive-1.1.0-cdh5.14.2/lib/hbase-it-1.2.0-cdh5.14.2.jar               
ln -s /kkb/install/hbase-1.2.0-cdh5.14.2/lib/hbase-server-1.2.0-cdh5.14.2.jar          /kkb/install/hive-1.1.0-cdh5.14.2/lib/hbase-server-1.2.0-cdh5.14.2.jar      
第二步:修改hive的配置文件
编辑node03服务器上面的hive的配置文件hive-site.xml添加以下两行配置
cd /kkb/install/hive-1.1.0-cdh5.14.2/conf
vim hive-site.xml

<property>
                <name>hive.zookeeper.quorum</name>
                <value>node01,node02,node03</value>
        </property>

         <property>
                <name>hbase.zookeeper.quorum</name>
                <value>node01,node02,node03</value>
        </property>


第三步:修改hive-env.sh配置文件添加以下配置
cd /kkb/install/hive-1.1.0-cdh5.14.2/conf
vim hive-env.sh

export HADOOP_HOME=/kkb/install/hadoop-2.6.0-cdh5.14.2
export HBASE_HOME=/kkb/install/hbase-1.2.0-cdh5.14.2/
export HIVE_CONF_DIR=/kkb/install/hive-1.1.0-cdh5.14.2/conf

第四步:创建hive表,映射hbase当中的数据
进入hive客户端,创建hive映射表,映射hbase当中的两张表数据

create database hive_hbase;
use hive_hbase;
CREATE external TABLE hive_hbase.data_goods(goodsId int ,goodsName     string ,sellingPrice  string ,productPic    string ,productBrand  string ,productfbl    string ,productNum    string ,productUrl    string ,productFrom   string ,goodsStock    int  ,  appraiseNum   int) 
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler' WITH SERDEPROPERTIES 
("hbase.columns.mapping" = ":key,f1:goodsName    ,f1:sellingPrice ,f1:productPic   ,f1:productBrand ,f1:productfbl   ,f1:productNum   ,f1:productUrl   ,f1:productFrom  ,f1:goodsStock   ,    f1:appraiseNum") 
TBLPROPERTIES("hbase.table.name" ="flink:data_goods");

CREATE external TABLE hive_hbase.data_orders(orderId int,orderNo string ,userId int,goodId int ,goodsMoney decimal(11,2) ,realTotalMoney  decimal(11,2) ,payFrom         int           ,province        string        ,createTime      timestamp ) 
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler' WITH SERDEPROPERTIES 
("hbase.columns.mapping" = ":key,  f1:orderNo ,  f1:userId        ,   f1:goodId        ,   f1:goodsMoney    ,f1:realTotalMoney,f1:payFrom       ,f1:province,f1:createTime") 
TBLPROPERTIES("hbase.table.name" ="flink:data_orders");

第五步:在kylin当中对我们hive的数据进行多维度分析
直接登录kylin的管理界面,对我们hive当中的数据进行多维度分析
<properties>
        <flink.version>1.9.0</flink.version>
        <scala.version>2.11.8</scala.version>
    </properties>

    <dependencies>
        <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-streaming-java_2.11</artifactId>
        <version>${flink.version}</version>
        </dependency>
    </dependencies>

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.utils.ParameterTool;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;

public class WindowWordCountJava {
    public static void main(String[] args) throws Exception {
        //flink提供的工具类,获取传递的参数
        ParameterTool parameterTool = ParameterTool.fromArgs(args);
        String hostname = parameterTool.get("hostname");
        int port = parameterTool.getInt("port");
        //步骤一:获取执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //步骤二:获取数据源
        DataStream<String> dataStream = env.socketTextStream(hostname, port);
        //步骤三:执行逻辑操作
        DataStream<WordCount> wordAndOneStream = dataStream.flatMap(new FlatMapFunction<String, WordCount>() {
            public void flatMap(String line, Collector<WordCount> out) {
                String[] fields = line.split(",");
                for (String word : fields) {
                    out.collect(new WordCount(word, 1L));
                }
            }
        });

        DataStream<WordCount> resultStream = wordAndOneStream.keyBy("word")
                .timeWindow(Time.seconds(2), Time.seconds(1))//每隔1秒计算最近2秒
                .sum("count");
        //步骤四:结果打印
        resultStream.print();
        //步骤五:任务启动
        env.execute("WindowWordCountJava");
    }

    public static class WordCount{
        public String word;
        public long count;
        //记得要有这个空构建
        public WordCount(){

        }
        public WordCount(String word,long count){
            this.word = word;
            this.count = count;
        }

        @Override
        public String toString() {
            return "WordCount{" +
                    "word='" + word + '\'' +
                    ", count=" + count +
                    '}';
        }
    }
}

 <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-scala_2.11</artifactId>
            <version>${flink.version}</version>
  </dependency>
<properties>
        <flink.version>1.9.0</flink.version>
        <scala.version>2.11.8</scala.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-scala_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <!-- scala插件 -->
                <plugin>
                    <groupId>net.alchim31.maven</groupId>
                    <artifactId>scala-maven-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <!-- maven 插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.5.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>scala-compile-first</id>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>add-source</goal>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>scala-test-compile</id>
                        <phase>process-test-resources</phase>
                        <goals>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
import org.apache.flink.api.java.utils.ParameterTool
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.windowing.time.Time

/**
  * 滑动窗口
  * 每隔1秒钟统计最近2秒内的数据,打印到控制台。
  */
object WindowWordCountScala {
  def main(args: Array[String]): Unit = {
    //获取参数
    val hostname = ParameterTool.fromArgs(args).get("hostname")
    val port = ParameterTool.fromArgs(args).getInt("port")
    //TODO 导入隐式转换
    import org.apache.flink.api.scala._
    //步骤一:获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    //步骤二:获取数据源
    val textStream = env.socketTextStream(hostname,port)
    //步骤三:数据处理
    val wordCountStream = textStream.flatMap(line => line.split(","))
      .map((_, 1))
      .keyBy(0)
      .timeWindow(Time.seconds(2), Time.seconds(1))
      .sum(1)
    //步骤四:数据结果处理
    wordCountStream.print()
    //步骤六:启动程序
    env.execute("WindowWordCountScala")
  }

}

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.operators.AggregateOperator;
import org.apache.flink.api.java.operators.DataSource;
import org.apache.flink.api.java.operators.FlatMapOperator;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.util.Collector;

public class WordCount {
    public static void main(String[] args)throws  Exception {
        //步骤一:获取离线的程序入口
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        String inputPath="D:\\kkb\\flinklesson\\src\\main\\input\\hello.txt";
        //步骤二:获取数据源
        DataSource<String> dataSet = env.readTextFile(inputPath);
        //步骤三:数据处理
        FlatMapOperator<String, Tuple2<String, Integer>> wordAndOneDataSet = dataSet.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public void flatMap(String line, Collector<Tuple2<String, Integer>> collector) throws Exception {
                String[] fileds = line.split(",");
                for (String word : fileds) {
                    collector.collect(new Tuple2<String, Integer>(word, 1));
                }
            }
        });

        AggregateOperator<Tuple2<String, Integer>> result = wordAndOneDataSet.groupBy(0)
                .sum(1);
        //步骤四:数据结果处理
        result.writeAsText("D:\\kkb\\flinklesson\\src\\output\\result").setParallelism(1);
        //步骤五:启动程序
        env.execute("word count");
    }
}

public class WordCount {
    public static void main(String[] args)throws  Exception {
        //步骤一:获取离线的程序入口
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        String inputPath="D:\\kkb\\flinklesson\\src\\main\\input\\hello.txt";
        //步骤二:获取数据源
        DataSource<String> dataSet = env.readTextFile(inputPath);
        //步骤三:数据处理
        FlatMapOperator<String, Tuple2<String, Integer>> wordAndOneDataSet = dataSet.flatMap(new MySplitWordsTask());

        AggregateOperator<Tuple2<String, Integer>> result = wordAndOneDataSet.groupBy(0)
                .sum(1);
        //步骤四:数据结果处理
        result.writeAsText("D:\\kkb\\flinklesson\\src\\output\\result1").setParallelism(1);
        //步骤五:启动程序
        env.execute("word count");
    }


    public static class MySplitWordsTask implements   FlatMapFunction<String,Tuple2<String,Integer>>{
        @Override
        public void flatMap(String line, Collector<Tuple2<String, Integer>> collector) throws Exception {
            String[] fileds = line.split(",");
            for (String word : fileds) {
                collector.collect(new Tuple2<String, Integer>(word, 1));
            }
        }
    }
}




#### 4.1 Flink简介

**Apache Flink? — Stateful Computations over Data Streams**

![flink](assets/flink.png)

Apache Flink 是一个框架和分布式处理引擎,用于在*无边界和有边界*数据流上进行有状态的计算。Flink 能在所有常见集群环境中运行,并能以内存速度和任意规模进行计算。

##### 4.1.1 处理无界和有界数据

任何类型的数据都可以形成一种事件流。信用卡交易、传感器测量、机器日志、网站或移动应用程序上的用户交互记录,所有这些数据都形成一种流。

数据可以被作为 *无界* 或者 *有界* 流来处理。

1. **无界流** 有定义流的开始,但没有定义流的结束。它们会无休止地产生数据。无界流的数据必须持续处理,即数据被摄取后需要立刻处理。我们不能等到所有数据都到达再处理,因为输入是无限的,在任何时候输入都不会完成。处理无界数据通常要求以特定顺序摄取事件,例如事件发生的顺序,以便能够推断结果的完整性。

2. **有界流** 有定义流的开始,也有定义流的结束。有界流可以在摄取所有数据后再进行计算。有界流所有数据可以被排序,所以并不需要有序摄取。有界流处理通常被称为批处理

   ![bounded-unbounded](assets/bounded-unbounded.png)

**Apache Flink 擅长处理无界和有界数据集** 精确的时间控制和状态化使得 Flink 的运行时(runtime)能够运行任何处理无界流的应用。有界流则由一些专为固定大小数据集特殊设计的算法和数据结构进行内部处理,产生了出色的性能。

##### 4.1.2 部署应用到任意地方

Apache Flink 是一个分布式系统,它需要计算资源来执行应用程序。Flink 集成了所有常见的集群资源管理器,例如 [Hadoop YARN](https://hadoop.apache.org/docs/stable/hadoop-yarn/hadoop-yarn-site/YARN.html)、 [Apache Mesos](https://mesos.apache.org/) 和 [Kubernetes](https://kubernetes.io/),但同时也可以作为独立集群运行。
Flink 被设计为能够很好地工作在上述每个资源管理器中,这是通过资源管理器特定(resource-manager-specific)的部署模式实现的。Flink 可以采用与当前资源管理器相适应的方式进行交互。
部署 Flink 应用程序时,Flink 会根据应用程序配置的并行性自动标识所需的资源,并从资源管理器请求这些资源。在发生故障的情况下,Flink 通过请求新资源来替换发生故障的容器。提交或控制应用程序的所有通信都是通过 REST 调用进行的,这可以简化 Flink 与各种环境中的集成

##### 4.1.3 运行任意规模应用

Flink 旨在任意规模上运行有状态流式应用。因此,应用程序被并行化为可能数千个任务,这些任务分布在集群中并发执行。所以应用程序能够充分利用无尽的 CPU、内存、磁盘和网络 IO。而且 Flink 很容易维护非常大的应用程序状态。其异步和增量的检查点算法对处理延迟产生最小的影响,同时保证精确一次状态的一致性。
Flink 用户报告了其生产环境中一些令人印象深刻的扩展性数字:
	每天处理数万亿的事件
	可以维护几TB大小的状态
	可以部署上千个节点的集群

##### 4.1.4 利用内存性能

有状态的 Flink 程序针对本地状态访问进行了优化。任务的状态始终保留在内存中,如果状态大小超过可用内存,则会保存在能高效访问的磁盘数据结构中。任务通过访问本地(通常在内存中)状态来进行所有的计算,从而产生非常低的处理延迟。Flink 通过定期和异步地对本地状态进行持久化存储来保证故障场景下精确一次的状态一致性。

![local-state](assets/local-state.png)

### 4.2 Flink架构图

![1563615522100](assets/1563615522100.png)



### 4.3 入门案例演示

##### 4.3.1 实时需求分析

实时统计每隔1秒统计最近2秒单词出现的次数

##### 4.3.2 开发环境部署

1. 官网建议使用IDEA,IDEA默认集成了scala和maven,使用起来方便

2. 本次课使用了当前最新的flink版本

   pom依赖如下:

~~~java
    <properties>
        <flink.version>1.9.0</flink.version>
        <scala.version>2.11.8</scala.version>
    </properties>

    <dependencies>
        <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-streaming-java_2.11</artifactId>
        <version>${flink.version}</version>
        </dependency>
    </dependencies>
~~~

##### 4.3.3 实时代码开发(java)

~~~java
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.utils.ParameterTool;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;

public class WindowWordCountJava {
    public static void main(String[] args) throws Exception {
        //flink提供的工具类,获取传递的参数
        ParameterTool parameterTool = ParameterTool.fromArgs(args);
        String hostname = parameterTool.get("hostname");
        int port = parameterTool.getInt("port");
        //步骤一:获取执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //步骤二:获取数据源
        DataStream<String> dataStream = env.socketTextStream(hostname, port);
        //步骤三:执行逻辑操作
        DataStream<WordCount> wordAndOneStream = dataStream.flatMap(new FlatMapFunction<String, WordCount>() {
            public void flatMap(String line, Collector<WordCount> out) {
                String[] fields = line.split(",");
                for (String word : fields) {
                    out.collect(new WordCount(word, 1L));
                }
            }
        });

        DataStream<WordCount> resultStream = wordAndOneStream.keyBy("word")
                .timeWindow(Time.seconds(2), Time.seconds(1))//每隔1秒计算最近2秒
                .sum("count");
        //步骤四:结果打印
        resultStream.print();
        //步骤五:任务启动
        env.execute("WindowWordCountJava");
    }

    public static class WordCount{
        public String word;
        public long count;
        //记得要有这个空构建
        public WordCount(){

        }
        public WordCount(String word,long count){
            this.word = word;
            this.count = count;
        }

        @Override
        public String toString() {
            return "WordCount{" +
                    "word='" + word + '\'' +
                    ", count=" + count +
                    '}';
        }
    }
}

~~~

#####  4.3.4 实时代码开发(scala)

添加依赖

~~~java
  <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-scala_2.11</artifactId>
            <version>${flink.version}</version>
  </dependency>
~~~



scala开发依赖和编译插件

~~~java
<properties>
        <flink.version>1.9.0</flink.version>
        <scala.version>2.11.8</scala.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-scala_2.11</artifactId>
            <version>${flink.version}</version>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <!-- scala插件 -->
                <plugin>
                    <groupId>net.alchim31.maven</groupId>
                    <artifactId>scala-maven-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <!-- maven 插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.5.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>scala-compile-first</id>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>add-source</goal>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>scala-test-compile</id>
                        <phase>process-test-resources</phase>
                        <goals>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
~~~

代码开发

~~~scala
import org.apache.flink.api.java.utils.ParameterTool
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.windowing.time.Time

/**
  * 滑动窗口
  * 每隔1秒钟统计最近2秒内的数据,打印到控制台。
  */
object WindowWordCountScala {
  def main(args: Array[String]): Unit = {
    //获取参数
    val hostname = ParameterTool.fromArgs(args).get("hostname")
    val port = ParameterTool.fromArgs(args).getInt("port")
    //TODO 导入隐式转换
    import org.apache.flink.api.scala._
    //步骤一:获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    //步骤二:获取数据源
    val textStream = env.socketTextStream(hostname,port)
    //步骤三:数据处理
    val wordCountStream = textStream.flatMap(line => line.split(","))
      .map((_, 1))
      .keyBy(0)
      .timeWindow(Time.seconds(2), Time.seconds(1))
      .sum(1)
    //步骤四:数据结果处理
    wordCountStream.print()
    //步骤六:启动程序
    env.execute("WindowWordCountScala")
  }

}
~~~

##### 4.3.5 需求分析

对文件进行单词计数

##### 4.3.6 离线代码开发(java)

~~~java
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.operators.AggregateOperator;
import org.apache.flink.api.java.operators.DataSource;
import org.apache.flink.api.java.operators.FlatMapOperator;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.util.Collector;

public class WordCount {
    public static void main(String[] args)throws  Exception {
        //步骤一:获取离线的程序入口
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        String inputPath="D:\\kkb\\flinklesson\\src\\main\\input\\hello.txt";
        //步骤二:获取数据源
        DataSource<String> dataSet = env.readTextFile(inputPath);
        //步骤三:数据处理
        FlatMapOperator<String, Tuple2<String, Integer>> wordAndOneDataSet = dataSet.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public void flatMap(String line, Collector<Tuple2<String, Integer>> collector) throws Exception {
                String[] fileds = line.split(",");
                for (String word : fileds) {
                    collector.collect(new Tuple2<String, Integer>(word, 1));
                }
            }
        });

        AggregateOperator<Tuple2<String, Integer>> result = wordAndOneDataSet.groupBy(0)
                .sum(1);
        //步骤四:数据结果处理
        result.writeAsText("D:\\kkb\\flinklesson\\src\\output\\result").setParallelism(1);
        //步骤五:启动程序
        env.execute("word count");
    }
}

~~~

换一种写法

~~~java
public class WordCount {
    public static void main(String[] args)throws  Exception {
        //步骤一:获取离线的程序入口
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        String inputPath="D:\\kkb\\flinklesson\\src\\main\\input\\hello.txt";
        //步骤二:获取数据源
        DataSource<String> dataSet = env.readTextFile(inputPath);
        //步骤三:数据处理
        FlatMapOperator<String, Tuple2<String, Integer>> wordAndOneDataSet = dataSet.flatMap(new MySplitWordsTask());

        AggregateOperator<Tuple2<String, Integer>> result = wordAndOneDataSet.groupBy(0)
                .sum(1);
        //步骤四:数据结果处理
        result.writeAsText("D:\\kkb\\flinklesson\\src\\output\\result1").setParallelism(1);
        //步骤五:启动程序
        env.execute("word count");
    }


    public static class MySplitWordsTask implements   FlatMapFunction<String,Tuple2<String,Integer>>{
        @Override
        public void flatMap(String line, Collector<Tuple2<String, Integer>> collector) throws Exception {
            String[] fileds = line.split(",");
            for (String word : fileds) {
                collector.collect(new Tuple2<String, Integer>(word, 1));
            }
        }
    }
}
~~~

总结:上面这种写法,我们把flink的算子抽离出来,代码看起来会更清晰。

### 4.4 local模式安装

#### 4.4.1Local模式安装

(1)安装jdk,配置JAVA_HOME,建议使用jdk1.8以上

(2)安装包下载地址:http://mirrors.tuna.tsinghua.edu.cn/apache/flink/flink-1.9.1/flink-1.9.1-bin-scala_2.11.tgz

 (3)直接上传安装包到服务器

?          解压安装包:tar -zxvf  flink-1.9.1-bin-scala_2.11.tgz

?          创建快捷方式: ln -s flink-1.9.1-bin-scala_2.11.tgz flink

?          配置FLINK_HOEM环境变量

 (4)启动服务

?           local模式,什么配置项都不需要配,直接启动服务器即可

?          cd $FLIKE_HOME

?          ./bin/start-cluster.sh 启动服务

?          ./bin/stop-cluster.sh 停止服务

  (5)Web页面浏览

?      localhost:8081

![1563630933663](assets/1563630933663.png)

#### 4.4.2 在集群上提交任务

(1)编译,打包

?          mvn clean package

?         pom文件里面的依赖上添加上:

?          <scope>provided</scope>

?          代表这个依赖打包的时候不打到jar包里面,因为我们这些依赖包flink已经自带了,所以我们打包的时候不用把打包进去。

![1563632280672](assets/1563632280672.png)

 (2) 运行

?       在hadoop01 上执行 nc -lk 9999

?      flink run -c stream.lesson01.WindowWordCountJava flinklesson-1.0-SNAPSHOT.jar -port 9999





![1563632969308](assets/1563632969308.png)



  查看任务

![1563633009046](assets/1563633009046.png)

查看日志:

tail -F $FLINK_HOME/log/flink-work-taskexecutor-0-tjtx-98-197.58os.org.out

![1563633150841](assets/1563633150841.png)



(3) 停止任务

方式一:页面上停止

![1563633479119](assets/1563633479119.png)

方式二:命令停止

flink cancel job-id

### 4.5 Standalone模式安装

**(1)集群规划 **

| 主机名   | JobManager | TaskManager |
| :------- | ---------: | :---------: |
| hadoop01 |         是 |             |
| hadoop02 |            |     是      |
| hadoop03 |            |     是      |

**(2)依赖 **

 jdk1.8以上,配置JAVA_HOME

主机之间免密码

flink-1.9.1-bin-scala_2.11.tgz

**(3)安装步骤 **

(a) 修改conf/flink-conf.yaml
jobmanager.rpc.address: hadoop01
(b) 修改conf/slaves
hadoop02
hadoop03
? 拷贝到其他节点
scp -rq /usr/local/flink-1.9.1 hadoop02:/usr/local
scp -rq /usr/local/flink-1.9.1 hadoop03:/usr/local
(d):在hadoop01(JobMananger)节点启动
start-cluster.sh
(e):访问http://hadoop01:8081


 

**(4) StandAlone模式需要考虑的参数 **

jobmanager.heap.mb:jobmanager节点可用的内存大小

taskmanager.heap.mb:taskmanager节点可用的内存大小

taskmanager.numberOfTaskSlots:每台机器可用的cpu数量

parallelism.default:默认情况下任务的并行度

taskmanager.tmp.dirs:taskmanager的临时数据存储目录

### 4.6 Flink on Yarn模式安装

flink on yarn有两种方式:

#### 4.6.1 第一种方式 

在YARN里面启动一个flink集群,然后我们再往flink集群提交任务,除非把Flink集群停了,不然资源不会释放

![1563634783626](assets/1563634783626.png)



#### 4.6.2 第二种方式

没提交一个任务就在yarn上面启动一个flink小集群(推荐使用)

任务运行完了资源就自动释放

![1563634942092](assets/1563634942092.png)

####  4.6.3 不同模式的任务提交

第一种【yarn-session.sh(开辟资源)+flink run(提交任务)】

启动一个一直运行的flink集群

/bin/yarn-session.sh -n 2 -jm 1024 -tm 1024 [-d]

把任务附着到一个已存在的flink yarn session

?./bin/yarn-session.sh -id application_1463870264508_0029

?执行任务

?./bin/flink run ./examples/batch/WordCount.jar -input hdfs://hadoop100:9000/LICENSE -output hdfs://hadoop100:9000/wordcount-result.txt

停止任务 【web界面或者命令行执行cancel命令】

第二种【flink run -m yarn-cluster(开辟资源+提交任务)】

?启动集群,执行任务

?./bin/flink run -m yarn-cluster -yn 2 -yjm 1024 -ytm 1024 ./examples/batch/WordCount.jar

注意:client端必须要设置YARN_CONF_DIR或者HADOOP_CONF_DIR或者HADOOP_HOME环境变量,通过这个环境变量来读取YARN和HDFS的配置信息,否则启动会失败


**help信息**

yarn-session.sh

用法:
必选
-n,–container 分配多少个yarn容器 (=taskmanager的数量)
可选
-D 动态属性
-d,–detached 独立运行
-jm,–jobManagerMemory JobManager的内存 [in MB]
-nm,–name 在YARN上为一个自定义的应用设置一个名字
-q,–query 显示yarn中可用的资源 (内存, cpu核数)
-qu,–queue 指定YARN队列.
-s,–slots 每个TaskManager使用的slots数量
-tm,–taskManagerMemory 每个TaskManager的内存 [in MB]
-z,–zookeeperNamespace 针对HA模式在zookeeper上创建NameSpace
-id,–applicationId YARN集群上的任务id,附着到一个后台运行的yarn session中




flink run

run [OPTIONS]
“run” 操作参数:
-c,–class 如果没有在jar包中指定入口类,则需要在这里通过这个参数指定
-m,–jobmanager host:port 指定需要连接的jobmanager(主节点)地址,使用这个参数可以指定一个不同于配置文件中的jobmanager
-p,–parallelism 指定程序的并行度。可以覆盖配置文件中的默认值。

默认查找当前yarn集群中已有的yarn-session信息中的jobmanager【/tmp/.yarn-properties-root】:
./bin/flink run ./examples/batch/WordCount.jar -input hdfs://hostname:port/hello.txt -output hdfs://hostname:port/result1
连接指定host和port的jobmanager:
./bin/flink run -m hadoop100:1234 ./examples/batch/WordCount.jar -input hdfs://hostname:port/hello.txt -output hdfs://hostname:port/result1
启动一个新的yarn-session:
./bin/flink run -m yarn-cluster -yn 2 ./examples/batch/WordCount.jar -input hdfs://hostname:port/hello.txt -output hdfs://hostname:port/result1
注意:yarn session命令行的选项也可以使用./bin/flink 工具获得。它们都有一个y或者yarn的前缀
例如:./bin/flink run -m yarn-cluster -yn 2 ./examples/batch/WordCount.jar


#### 4.6.4 Flink on YARN集群部署



(1) flink on yarn运行原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0vFMNcP8-1638889703776)(assets/flink运行原理.png)]



[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zXwT5cNd-1638889703778)(assets/FlinkOnYarn.png)]

其实Flink on YARN部署很简单,就是只要部署好hadoop集群即可,我们只需要部署一个Flink客户端,然后从flink客户端提交Flink任务即可。

### 4.7 Flink Shell使用

针对初学者,开发的时候容易出错,如果每次都打包进行调试,比较麻烦,并且也不好定位问题,可以在scala shell命令行下进行调试

scala shell方式支持流处理和批处理。当启动shell命令行之后,两个不同的ExecutionEnvironments会被自动创建。使用senv(Stream)和benv(Batch)分别去处理流处理和批处理程序。(类似于spark-shell中sc变量)

bin/start-scala-shell.sh [local|remote|yarn] [options] <args>

### 4.8 Flink之数据源

#### 4.8.1 source简介 

source是程序的数据源输入,你可以通过StreamExecutionEnvironment.addSource(sourceFunction)来为你的程序添加一个source。

flink提供了大量的已经实现好的source方法,你也可以自定义source:

(1)通过实现sourceFunction接口来自定义无并行度的source

(2)通过实现ParallelSourceFunction 接口 or 继承RichParallelSourceFunction 来自定义有并行度的source

不过大多数情况下,我们使用自带的source即可。

**获取source的方式**

(1)基于文件

?          readTextFile(path)

?         读取文本文件,文件遵循TextInputFormat 读取规则,逐行读取并返回。

(2)基于socket

?         socketTextStream
?         从socker中读取数据,元素可以通过一个分隔符切开。

(3)基于集合

?          fromCollection(Collection)

?         通过java 的collection集合创建一个数据流,集合中的所有元素必须是相同类型的。

(4)自定义输入

?         addSource 可以实现读取第三方数据源的数据

?        系统内置提供了一批connectors,连接器会提供对应的source支持【kafka】

**自带的connectors**

- [Apache Kafka](https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/connectors/kafka.html) (source/sink)  **后面重点分析**
- [Apache Cassandra](https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/connectors/cassandra.html) (sink)
- [Amazon Kinesis Streams](https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/connectors/kinesis.html) (source/sink)
- [Elasticsearch](https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/connectors/elasticsearch.html) (sink)
- [Hadoop FileSystem](https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/connectors/filesystem_sink.html) (sink)
- [RabbitMQ](https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/connectors/rabbitmq.html) (source/sink)
- [Apache NiFi](https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/connectors/nifi.html) (source/sink)
- [Twitter Streaming API](https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/connectors/twitter.html) (source)

#### 4.8.2 数据源之collection

~~~java
public class StreamingSourceFromCollection {
    public static void main(String[] args) throws Exception {
        //步骤一:获取环境变量
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //步骤二:模拟数据
        ArrayList<String> data = new ArrayList<String>();
        data.add("hadoop");
        data.add("spark");
        data.add("flink");
        //步骤三:获取数据源
        DataStreamSource<String> dataStream = env.fromCollection(data);
        //步骤四:transformation操作
        SingleOutputStreamOperator<String> addPreStream = dataStream.map(new MapFunction<String, String>() {
            @Override
            public String map(String word) throws Exception {
                return "kaikeba_" + word;
            }
        });
        //步骤五:对结果进行处理(打印)
        addPreStream.print().setParallelism(1);
        //步骤六:启动程序
        env.execute("StreamingSourceFromCollection");

    }
}
~~~

#### 4.8.3 自定义单并行度数据源

~~~java
/**
 * 注意:指定数据类型
 * 功能:每秒产生一条数据
 */
public class MyNoParalleSource implements SourceFunction<Long> {
    private long number = 1L;
    private boolean isRunning = true;
    @Override
    public void run(SourceContext<Long> sct) throws Exception {
        while (isRunning){
         sct.collect(number);
         number++;
         //每秒生成一条数据
         Thread.sleep(1000);
        }

    }

    @Override
    public void cancel() {
        isRunning=false;
    }
}
~~~

~~~java
/**
 * 功能:从自定义的数据数据源里面获取数据,然后过滤出偶数
 */
public class StreamingDemoWithMyNoPralalleSource {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<Long> numberStream = env.addSource(new MyNoParalleSource()).setParallelism(1);
        SingleOutputStreamOperator<Long> dataStream = numberStream.map(new MapFunction<Long, Long>() {
            @Override
            public Long map(Long value) throws Exception {
                System.out.println("接受到了数据:"+value);
                return value;
            }
        });
        SingleOutputStreamOperator<Long> filterDataStream = dataStream.filter(new FilterFunction<Long>() {
            @Override
            public boolean filter(Long number) throws Exception {
                return number % 2 == 0;
            }
        });

        filterDataStream.print().setParallelism(1);
        env.execute("StreamingDemoWithMyNoPralalleSource");
    }
}
~~~



运行结果:

~~~java
接受到了数据:1
接受到了数据:2
2
接受到了数据:3
接受到了数据:4
4
接受到了数据:5
接受到了数据:6
6
接受到了数据:7
接受到了数据:8
8
~~~

#### 4.8.4 自定义多并行度数据源

~~~java
/**
 * 功能:自定义支持并行度的数据源
 * 每秒产生一条数据
 */
public class MyParalleSource implements ParallelSourceFunction<Long> {
    private long number = 1L;
    private boolean isRunning = true;
    @Override
    public void run(SourceContext<Long> sct) throws Exception {
        while (isRunning){
            sct.collect(number);
            number++;
            //每秒生成一条数据
            Thread.sleep(1000);
        }

    }

    @Override
    public void cancel() {
        isRunning=false;
    }
}
~~~

~~~java
public class StreamingDemoWithMyPralalleSource {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<Long> numberStream = env.addSource(new MyParalleSource()).setParallelism(2);
        SingleOutputStreamOperator<Long> dataStream = numberStream.map(new MapFunction<Long, Long>() {
            @Override
            public Long map(Long value) throws Exception {
                System.out.println("接受到了数据:"+value);
                return value;
            }
        });
        SingleOutputStreamOperator<Long> filterDataStream = dataStream.filter(new FilterFunction<Long>() {
            @Override
            public boolean filter(Long number) throws Exception {
                return number % 2 == 0;
            }
        });

        filterDataStream.print().setParallelism(1);
        env.execute("StreamingDemoWithMyNoPralalleSource");
    }
}
~~~

运行结果:

~~~java
接受到了数据:1
接受到了数据:1
接受到了数据:2
接受到了数据:2
2
2
接受到了数据:3
接受到了数据:3
接受到了数据:4
4
接受到了数据:4
4
接受到了数据:5
接受到了数据:5
接受到了数据:6
接受到了数据:6
~~~




### 4.1 常见Transformation操作

####  4.1.1 map和filter

~~~java
/**
 * 数据源:1 2 3 4 5.....源源不断过来
 * 通过map打印一下接受到数据
 * 通过filter过滤一下数据,我们只需要偶数
 */
public class MapDemo {
        public static void main(String[] args) throws Exception {
            StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
            DataStreamSource<Long> numberStream = env.addSource(new MyNoParalleSource()).setParallelism(1);
            SingleOutputStreamOperator<Long> dataStream = numberStream.map(new MapFunction<Long, Long>() {
                @Override
                public Long map(Long value) throws Exception {
                    System.out.println("接受到了数据:"+value);
                    return value;
                }
            });
            SingleOutputStreamOperator<Long> filterDataStream = dataStream.filter(new FilterFunction<Long>() {
                @Override
                public boolean filter(Long number) throws Exception {
                    return number % 2 == 0;
                }
            });

            filterDataStream.print().setParallelism(1);
            env.execute("StreamingDemoWithMyNoPralalleSource");
        }


}
~~~

#### 4.1.2 flatMap,keyBy和sum

~~~java
/**
 * 滑动窗口实现单词计数
 * 数据源:socket
 * 需求:每隔1秒计算最近2秒单词出现的次数
 *
 * 练习算子:
 * flatMap
 * keyBy:
 *    dataStream.keyBy("someKey") // 指定对象中的 "someKey"字段作为分组key
 *    dataStream.keyBy(0) //指定Tuple中的第一个元素作为分组key
 * sum
 */
public class WindowWordCountJava {
    public static void main(String[] args) throws  Exception {
        int port;
        try{
            ParameterTool parameterTool = ParameterTool.fromArgs(args);
            port = parameterTool.getInt("port");
        }catch (Exception e){
            System.err.println("no port set,user default port 9988");
            port=9988;
        }
        //步骤一:获取flink运行环境(stream)
        StreamExecutionEnvironment env= StreamExecutionEnvironment.getExecutionEnvironment();
        String hostname="10.126.88.226";
        String delimiter="\n";
        //步骤二:获取数据源
        DataStreamSource<String> textStream = env.socketTextStream(hostname, port, delimiter);
        //步骤三:执行transformation操作
        SingleOutputStreamOperator<WordCount> wordCountStream = textStream.flatMap(new FlatMapFunction<String, WordCount>() {
            public void flatMap(String line, Collector<WordCount> out) throws Exception {
                String[] fields = line.split("\t");
                for (String word : fields) {
                    out.collect(new WordCount(word, 1L));
                }
            }
        }).keyBy("word")
                .timeWindow(Time.seconds(2), Time.seconds(1))//每隔1秒计算最近2秒
                .sum("count");

        wordCountStream.print().setParallelism(1);//打印并设置并行度
        //步骤四:运行程序
        env.execute("socket word count");


    }

    public static class WordCount{
        public String word;
        public long count;
        public WordCount(){

        }
        public WordCount(String word,long count){
            this.word=word;
            this.count=count;
        }

        @Override
        public String toString() {
            return "WordCount{" +
                    "word='" + word + '\'' +
                    ", count=" + count +
                    '}';
        }
    }
}
~~~

#### 4.1.3 union

~~~java
/**
 * 合并多个流,新的流会包含所有流中的数据,但是union是一个限制,就是所有合并的流类型必须是一致的
 */
public class unionDemo {
    public static void main(String[] args) throws Exception {
        //获取Flink的运行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //获取数据源
        DataStreamSource<Long> text1 = env.addSource(new MyNoParalleSource()).setParallelism(1);//注意:针对此source,并行度只能设置为1
        DataStreamSource<Long> text2 = env.addSource(new MyNoParalleSource()).setParallelism(1);

        //把text1和text2组装到一起
        DataStream<Long> text = text1.union(text2);

        DataStream<Long> num = text.map(new MapFunction<Long, Long>() {
            @Override
            public Long map(Long value) throws Exception {
                System.out.println("原始接收到数据:" + value);
                return value;
            }
        });
        //每2秒钟处理一次数据
        DataStream<Long> sum = num.timeWindowAll(Time.seconds(2)).sum(0);
        //打印结果
        sum.print().setParallelism(1);
        String jobName = unionDemo.class.getSimpleName();
        env.execute(jobName);
    }
}
~~~

#### 4.1.4 connect,conMap和conFlatMap

~~~java
/**
 * 和union类似,但是只能连接两个流,两个流的数据类型可以不同,会对两个流中的数据应用不同的处理方法
 */
public class ConnectionDemo {
    public static void main(String[] args) throws Exception {
        //获取Flink的运行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        //获取数据源
        DataStreamSource<Long> text1 = env.addSource(new MyNoParalleSource()).setParallelism(1);//注意:针对此source,并行度只能设置为1

        DataStreamSource<Long> text2 = env.addSource(new MyNoParalleSource()).setParallelism(1);
        SingleOutputStreamOperator<String> text2_str = text2.map(new MapFunction<Long, String>() {
            @Override
            public String map(Long value) throws Exception {
                return "str_" + value;
            }
        });

        ConnectedStreams<Long, String> connectStream = text1.connect(text2_str);

        SingleOutputStreamOperator<Object> result = connectStream.map(new CoMapFunction<Long, String, Object>() {
            @Override
            public Object map1(Long value) throws Exception {
                return value;
            }

            @Override
            public Object map2(String value) throws Exception {
                return value;
            }
        });

        //打印结果
        result.print().setParallelism(1);
        String jobName = ConnectionDemo.class.getSimpleName();
        env.execute(jobName);
    }
}
~~~

#### 4.1.5 Split和Select

~~~java
/**
 *  根据规则把一个数据流切分为多个流
 应用场景:
 * 可能在实际工作中,源数据流中混合了多种类似的数据,多种类型的数据处理规则不一样,所以就可以在根据一定的规则,
 * 把一个数据流切分成多个数据流,这样每个数据流就可以使用不用的处理逻辑了
 */
public class SplitDemo {
    public static void main(String[] args) throws  Exception {
        //获取Flink的运行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //获取数据源
        DataStreamSource<Long> text = env.addSource(new MyNoParalleSource()).setParallelism(1);//注意:针对此source,并行度只能设置为1
        //对流进行切分,按照数据的奇偶性进行区分
        SplitStream<Long> splitStream = text.split(new OutputSelector<Long>() {
            @Override
            public Iterable<String> select(Long value) {
                ArrayList<String> outPut = new ArrayList<>();
                if (value % 2 == 0) {
                    outPut.add("even");//偶数
                } else {
                    outPut.add("odd");//奇数
                }
                return outPut;
            }
        });

        //选择一个或者多个切分后的流
        DataStream<Long> evenStream = splitStream.select("even");
        DataStream<Long> oddStream = splitStream.select("odd");
        DataStream<Long> moreStream = splitStream.select("odd","even");

        //打印结果
        evenStream.print().setParallelism(1);
        String jobName = SplitDemo.class.getSimpleName();
        env.execute(jobName);

    }
}
~~~

### 4.2常见sink操作

#### 4.2.1 print() / printToErr() 

打印每个元素的toString()方法的值到标准输出或者标准错误输出流中

#### 4.2.2 writeAsText()

~~~java 
/**
 * 数据源:1 2 3 4 5.....源源不断过来
 * 通过map打印一下接受到数据
 * 通过filter过滤一下数据,我们只需要偶数
 */
public class WriteTextDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<Long> numberStream = env.addSource(new MyNoParalleSource()).setParallelism(1);
        SingleOutputStreamOperator<Long> dataStream = numberStream.map(new MapFunction<Long, Long>() {
            @Override
            public Long map(Long value) throws Exception {
                System.out.println("接受到了数据:"+value);
                return value;
            }
        });
        SingleOutputStreamOperator<Long> filterDataStream = dataStream.filter(new FilterFunction<Long>() {
            @Override
            public boolean filter(Long number) throws Exception {
                return number % 2 == 0;
            }
        });
   filterDataStream.writeAsText("D:\\kkb\\flinklesson\\src\\output\\test").setParallelism(1);
        env.execute("StreamingDemoWithMyNoPralalleSource");
    }
}
~~~

#### 4.2.3 Flink提供的sink

- [Apache Kafka](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/kafka.html) (source/sink)
- [Apache Cassandra](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/cassandra.html) (sink)
- [Amazon Kinesis Streams](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/kinesis.html) (source/sink)
- [Elasticsearch](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/elasticsearch.html) (sink)
- [Hadoop FileSystem](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/filesystem_sink.html) (sink)
- [RabbitMQ](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/rabbitmq.html) (source/sink)
- [Apache NiFi](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/nifi.html) (source/sink)
- [Twitter Streaming API](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/twitter.html) (source)
- [Google PubSub](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/pubsub.html) (source/sink)

####  4.2.4 自定义sink

~~~java
     <dependency>
            <groupId>org.apache.bahir</groupId>
            <artifactId>flink-connector-redis_2.11</artifactId>
            <version>1.0</version>
     </dependency>
~~~

自定义redis sink

~~~java
/**
 * 把数据写入redis
 */
public class SinkForRedisDemo {
    public static void main(String[] args) throws  Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> text = env.socketTextStream("hadoop100", 9000, "\n");
        //lpsuh l_words word
        //对数据进行组装,把string转化为tuple2<String,String>
        DataStream<Tuple2<String, String>> l_wordsData = text.map(new MapFunction<String, Tuple2<String, String>>() {
            @Override
            public Tuple2<String, String> map(String value) throws Exception {
                return new Tuple2<>("l_words", value);
            }
        });
        //创建redis的配置
        FlinkJedisPoolConfig conf = new FlinkJedisPoolConfig.Builder().setHost("hadoop110").setPort(6379).build();

        //创建redissink
        RedisSink<Tuple2<String, String>> redisSink = new RedisSink<>(conf, new MyRedisMapper());
        l_wordsData.addSink(redisSink);
        env.execute("StreamingDemoToRedis");

    }

    public static class MyRedisMapper implements RedisMapper<Tuple2<String, String>> {
        //表示从接收的数据中获取需要操作的redis key
        @Override
        public String getKeyFromData(Tuple2<String, String> data) {
            return data.f0;
        }
        //表示从接收的数据中获取需要操作的redis value
        @Override
        public String getValueFromData(Tuple2<String, String> data) {
            return data.f1;
        }

        @Override
        public RedisCommandDescription getCommandDescription() {
            return new RedisCommandDescription(RedisCommand.LPUSH);
        }
    }
}
~~~

### 4.3 DataSet算子操作

#### 4.3.1 source

基于文件
readTextFile(path)
基于集合
fromCollection(Collection)

#### 4.3.2 transform

##### 算子概览

Map:输入一个元素,然后返回一个元素,中间可以做一些清洗转换等操作
FlatMap:输入一个元素,可以返回零个,一个或者多个元素
<span style="color:red">MapPartition</span>>:类似map,一次处理一个分区的数据【如果在进行map处理的时候需要获取第三方资源链接,建议使用MapPartition】
Filter:过滤函数,对传入的数据进行判断,符合条件的数据会被留下
Reduce:对数据进行聚合操作,结合当前元素和上一次reduce返回的值进行聚合操作,然后返回一个新的值
Aggregate:sum、max、min等
<span style="color:red">Distinct</span>:返回一个数据集中去重之后的元素,data.distinct()
<span style="color:red">Join</span>:内连接
<span style="color:red">OuterJoin</span>:外链接

<span style="color:red">Cross</span>:获取两个数据集的笛卡尔积
Union:返回两个数据集的总和,数据类型需要一致
<span style="color:red">First-n</span>:获取集合中的前N个元素
<span style="color:red">Sort Partition</span>:在本地对数据集的所有分区进行排序,通过sortPartition()的链接调用来完成对多个字段的排序

##### MapPartition

~~~java
public class MapPartitionDemo {
    public static void main(String[] args) throws Exception{

        //获取运行环境
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        ArrayList<String> data = new ArrayList<>();
        data.add("hello you");
        data.add("hello me");
        DataSource<String> text = env.fromCollection(data);

        /*text.map(new MapFunction<String, String>() {
            @Override
            public String map(String value) throws Exception {
                //获取数据库连接--注意,此时是每过来一条数据就获取一次链接
                //处理数据
                //关闭连接
                return value;
            }
        });*/
        DataSet<String> mapPartitionData = text.mapPartition(new MapPartitionFunction<String, String>() {
            @Override
            public void mapPartition(Iterable<String> values, Collector<String> out) throws Exception {
                //获取数据库连接--注意,此时是一个分区的数据获取一次连接【优点,每个分区获取一次链接】
                //values中保存了一个分区的数据
                //处理数据
                Iterator<String> it = values.iterator();
                while (it.hasNext()) {
                    String next = it.next();
                    String[] split = next.split("\\W+");
                    for (String word : split) {
                        out.collect(word);
                    }
                }
                //关闭链接
            }
        });
        mapPartitionData.print();
    }
}
~~~

##### distinct

~~~java
/**
 * 对数据进行去重
 */
public class DistinctDemo {
    public static void main(String[] args) throws Exception{

        //获取运行环境
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        ArrayList<String> data = new ArrayList<>();
        data.add("you jump");
        data.add("i jump");
        DataSource<String> text = env.fromCollection(data);
        FlatMapOperator<String, String> flatMapData = text.flatMap(new FlatMapFunction<String, String>() {
            @Override
            public void flatMap(String value, Collector<String> out) throws Exception {
                String[] split = value.toLowerCase().split("\\W+");
                for (String word : split) {
                    System.out.println("单词:"+word);
                    out.collect(word);
                }
            }
        });

        flatMapData.distinct()// 对数据进行整体去重
                .print();


    }

}
~~~

##### join

~~~java
/**
 * 对数据进行join
 */
public class JoinDemo {
    public static void main(String[] args) throws Exception{

        //获取运行环境
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

        //tuple2<用户id,用户姓名>
        ArrayList<Tuple2<Integer, String>> data1 = new ArrayList<>();
        data1.add(new Tuple2<>(1,"zs"));
        data1.add(new Tuple2<>(2,"ls"));
        data1.add(new Tuple2<>(3,"ww"));


        //tuple2<用户id,用户所在城市>
        ArrayList<Tuple2<Integer, String>> data2 = new ArrayList<>();
        data2.add(new Tuple2<>(1,"beijing"));
        data2.add(new Tuple2<>(2,"shanghai"));
        data2.add(new Tuple2<>(3,"guangzhou"));


        DataSource<Tuple2<Integer, String>> text1 = env.fromCollection(data1);
        DataSource<Tuple2<Integer, String>> text2 = env.fromCollection(data2);


        text1.join(text2).where(0)//指定第一个数据集中需要进行比较的元素角标
                .equalTo(0)//指定第二个数据集中需要进行比较的元素角标
                .with(new JoinFunction<Tuple2<Integer,String>, Tuple2<Integer,String>, Tuple3<Integer,String,String>>() {
                    @Override
                    public Tuple3<Integer, String, String> join(Tuple2<Integer, String> first, Tuple2<Integer, String> second)
                            throws Exception {
                        return new Tuple3<>(first.f0,first.f1,second.f1);
                    }
                }).print();

        System.out.println("==================================");

        //注意,这里用map和上面使用的with最终效果是一致的。
        /*text1.join(text2).where(0)//指定第一个数据集中需要进行比较的元素角标
                .equalTo(0)//指定第二个数据集中需要进行比较的元素角标
                .map(new MapFunction<Tuple2<Tuple2<Integer,String>,Tuple2<Integer,String>>, Tuple3<Integer,String,String>>() {
                    @Override
                    public Tuple3<Integer, String, String> map(Tuple2<Tuple2<Integer, String>, Tuple2<Integer, String>> value) throws Exception {
                        return new Tuple3<>(value.f0.f0,value.f0.f1,value.f1.f1);
                    }
                }).print();*/
    }

}
~~~

##### OutJoin

~~~java 
/**
 * 外连接:
 *      左外连接
 *      右外连接
 *      全外连接
 */
public class OuterJoinDemo {
    public static void main(String[] args) throws Exception{

        //获取运行环境
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        //tuple2<用户id,用户姓名>
        ArrayList<Tuple2<Integer, String>> data1 = new ArrayList<>();
        data1.add(new Tuple2<>(1,"zs"));
        data1.add(new Tuple2<>(2,"ls"));
        data1.add(new Tuple2<>(3,"ww"));
        //tuple2<用户id,用户所在城市>
        ArrayList<Tuple2<Integer, String>> data2 = new ArrayList<>();
        data2.add(new Tuple2<>(1,"beijing"));
        data2.add(new Tuple2<>(2,"shanghai"));
        data2.add(new Tuple2<>(4,"guangzhou"));
        DataSource<Tuple2<Integer, String>> text1 = env.fromCollection(data1);
        DataSource<Tuple2<Integer, String>> text2 = env.fromCollection(data2);
        /**
         * 左外连接
         *
         * 注意:second这个tuple中的元素可能为null
         *
         */
        text1.leftOuterJoin(text2)
                .where(0)
                .equalTo(0)
                .with(new JoinFunction<Tuple2<Integer,String>, Tuple2<Integer,String>, Tuple3<Integer,String,String>>() {
                    @Override
                    public Tuple3<Integer, String, String> join(Tuple2<Integer, String> first, Tuple2<Integer, String> second) throws Exception {
                        if(second==null){
                            return new Tuple3<>(first.f0,first.f1,"null");
                        }else{
                            return new Tuple3<>(first.f0,first.f1,second.f1);
                        }

                    }
                }).print();
        System.out.println("=============================================================================");
        /**
         * 右外连接
         *
         * 注意:first这个tuple中的数据可能为null
         *
         */
        text1.rightOuterJoin(text2)
                .where(0)
                .equalTo(0)
                .with(new JoinFunction<Tuple2<Integer,String>, Tuple2<Integer,String>, Tuple3<Integer,String,String>>() {
                    @Override
                    public Tuple3<Integer, String, String> join(Tuple2<Integer, String> first, Tuple2<Integer, String> second) throws Exception {
                        if(first==null){
                            return new Tuple3<>(second.f0,"null",second.f1);
                        }
                        return new Tuple3<>(first.f0,first.f1,second.f1);
                    }
                }).print();



        System.out.println("=============================================================================");

        /**
         * 全外连接
         *
         * 注意:first和second这两个tuple都有可能为null
         *
         */

        text1.fullOuterJoin(text2)
                .where(0)
                .equalTo(0)
                .with(new JoinFunction<Tuple2<Integer,String>, Tuple2<Integer,String>, Tuple3<Integer,String,String>>() {
                    @Override
                    public Tuple3<Integer, String, String> join(Tuple2<Integer, String> first, Tuple2<Integer, String> second) throws Exception {
                        if(first==null){
                            return new Tuple3<>(second.f0,"null",second.f1);
                        }else if(second == null){
                            return new Tuple3<>(first.f0,first.f1,"null");
                        }else{
                            return new Tuple3<>(first.f0,first.f1,second.f1);
                        }
                    }
                }).print();


    }
}
~~~

##### Cross

~~~java 
/**
 * 笛卡尔积
 */
public class CrossDemo {
    public static void main(String[] args) throws Exception{

        //获取运行环境
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        //tuple2<用户id,用户姓名>
        ArrayList<String> data1 = new ArrayList<>();
        data1.add("zs");
        data1.add("ww");
        //tuple2<用户id,用户所在城市>
        ArrayList<Integer> data2 = new ArrayList<>();
        data2.add(1);
        data2.add(2);
        DataSource<String> text1 = env.fromCollection(data1);
        DataSource<Integer> text2 = env.fromCollection(data2);
        CrossOperator.DefaultCross<String, Integer> cross = text1.cross(text2);
        cross.print();


    }
}
~~~

##### First-n 和 SortPartition

```java
/**
 * TopN
 */
import java.util.ArrayList;

public class FirstNDemo {
    public static void main(String[] args) throws Exception{

        //获取运行环境
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

        ArrayList<Tuple2<Integer, String>> data = new ArrayList<>();
        data.add(new Tuple2<>(2,"zs"));
        data.add(new Tuple2<>(4,"ls"));
        data.add(new Tuple2<>(3,"ww"));
        data.add(new Tuple2<>(1,"xw"));
        data.add(new Tuple2<>(1,"aw"));
        data.add(new Tuple2<>(1,"mw"));


        DataSource<Tuple2<Integer, String>> text = env.fromCollection(data);


        //获取前3条数据,按照数据插入的顺序
        text.first(3).print();
        System.out.println("==============================");

        //根据数据中的第一列进行分组,获取每组的前2个元素
        text.groupBy(0).first(2).print();
        System.out.println("==============================");

        //根据数据中的第一列分组,再根据第二列进行组内排序[升序],获取每组的前2个元素
        text.groupBy(0).sortGroup(1, Order.ASCENDING).first(2).print();
        System.out.println("==============================");

        //不分组,全局排序获取集合中的前3个元素,针对第一个元素升序,第二个元素倒序
        text.sortPartition(0,Order.ASCENDING).sortPartition(1,Order.DESCENDING).first(3).print();

    }
}

partition
/**
 * HashPartition
 *
 * RangePartition
 */
public class HashRangePartitionDemo {

    public static void main(String[] args) throws Exception{

        //获取运行环境
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

        ArrayList<Tuple2<Integer, String>> data = new ArrayList<>();
        data.add(new Tuple2<>(1,"hello1"));
        data.add(new Tuple2<>(2,"hello2"));
        data.add(new Tuple2<>(2,"hello3"));
        data.add(new Tuple2<>(3,"hello4"));
        data.add(new Tuple2<>(3,"hello5"));
        data.add(new Tuple2<>(3,"hello6"));
        data.add(new Tuple2<>(4,"hello7"));
        data.add(new Tuple2<>(4,"hello8"));
        data.add(new Tuple2<>(4,"hello9"));
        data.add(new Tuple2<>(4,"hello10"));
        data.add(new Tuple2<>(5,"hello11"));
        data.add(new Tuple2<>(5,"hello12"));
        data.add(new Tuple2<>(5,"hello13"));
        data.add(new Tuple2<>(5,"hello14"));
        data.add(new Tuple2<>(5,"hello15"));
        data.add(new Tuple2<>(6,"hello16"));
        data.add(new Tuple2<>(6,"hello17"));
        data.add(new Tuple2<>(6,"hello18"));
        data.add(new Tuple2<>(6,"hello19"));
        data.add(new Tuple2<>(6,"hello20"));
        data.add(new Tuple2<>(6,"hello21"));


        DataSource<Tuple2<Integer, String>> text = env.fromCollection(data);

        /*text.partitionByHash(0).mapPartition(new MapPartitionFunction<Tuple2<Integer,String>, Tuple2<Integer,String>>() {
            @Override
            public void mapPartition(Iterable<Tuple2<Integer, String>> values, Collector<Tuple2<Integer, String>> out) throws Exception {
                Iterator<Tuple2<Integer, String>> it = values.iterator();
                while (it.hasNext()){
                    Tuple2<Integer, String> next = it.next();
                    System.out.println("当前线程id:"+Thread.currentThread().getId()+","+next);
                }
            }
        }).print();*/


        text.partitionByRange(0).mapPartition(new MapPartitionFunction<Tuple2<Integer,String>, Tuple2<Integer,String>>() {
            @Override
            public void mapPartition(Iterable<Tuple2<Integer, String>> values, Collector<Tuple2<Integer, String>> out) throws Exception {
                Iterator<Tuple2<Integer, String>> it = values.iterator();
                while (it.hasNext()){
                    Tuple2<Integer, String> next = it.next();
                    System.out.println("当前线程id:"+Thread.currentThread().getId()+","+next);
                }

            }
        }).print();


    }
}
自定义partition

见 DataStream定义一partition

4.3.3 sink

writeAsText():将元素以字符串形式逐行写入,这些字符串通过调用每个元素的toString()方法来获取
writeAsCsv():将元组以逗号分隔写入文件中,行及字段之间的分隔是可配置的。每个字段的值来自对象的toString()方法
print():打印每个元素的toString()方法的值到标准输出或者标准错误输出流中

4.3.4 Flink之广播变量

广播变量允许编程人员在每台机器上保持1个只读的缓存变量,而不是传送变量的副本给tasks
广播变量创建后,它可以运行在集群中的任何function上,而不需要多次传递给集群节点。另外需要记住,不应该修改广播变量,这样才能确保每个节点获取到的值都是一致的
一句话解释,可以理解为是一个公共的共享变量,我们可以把一个dataset 数据集广播出去,然后不同的task在节点上都能够获取到,这个数据在每个节点上只会存在一份。如果不使用broadcast,则在每个节点中的每个task中都需要拷贝一份dataset数据集,比较浪费内存(也就是一个节点中可能会存在多份dataset数据)。
用法
1:初始化数据
DataSet toBroadcast = env.fromElements(1, 2, 3)
2:广播数据
.withBroadcastSet(toBroadcast, “broadcastSetName”);
3:获取数据
Collection broadcastSet = getRuntimeContext().getBroadcastVariable(“broadcastSetName”);
注意:
1:广播出去的变量存在于每个节点的内存中,所以这个数据集不能太大。因为广播出去的数据,会常驻内存,除非程序执行结束
2:广播变量在初始化广播出去以后不支持修改,这样才能保证每个节点的数据都是一致的。

/**
 * broadcast广播变量
 * 需求:
 *  flink会从数据源中获取到用户的姓名
 *  最终需要把用户的姓名和年龄信息打印出来
 *  分析:
 *  所以就需要在中间的map处理的时候获取用户的年龄信息
 *  建议吧用户的关系数据集使用广播变量进行处理
 *
 */
public class BroadCastDemo {
    public static void main(String[] args) throws Exception{

        //获取运行环境
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        //1:准备需要广播的数据
        ArrayList<Tuple2<String, Integer>> broadData = new ArrayList<>();
        broadData.add(new Tuple2<>("zs",18));
        broadData.add(new Tuple2<>("ls",20));
        broadData.add(new Tuple2<>("ww",17));
        DataSet<Tuple2<String, Integer>> tupleData = env.fromCollection(broadData);

        //1.1:处理需要广播的数据,把数据集转换成map类型,map中的key就是用户姓名,value就是用户年龄
        DataSet<HashMap<String, Integer>> toBroadcast = tupleData.map(new MapFunction<Tuple2<String, Integer>, HashMap<String, Integer>>() {
            @Override
            public HashMap<String, Integer> map(Tuple2<String, Integer> value) throws Exception {
                HashMap<String, Integer> res = new HashMap<>();
                res.put(value.f0, value.f1);
                return res;
            }
        });
        //源数据
        DataSource<String> data = env.fromElements("zs", "ls", "ww");
        //注意:在这里需要使用到RichMapFunction获取广播变量
        DataSet<String> result = data.map(new RichMapFunction<String, String>() {
            List<HashMap<String, Integer>> broadCastMap = new ArrayList<HashMap<String, Integer>>();
            HashMap<String, Integer> allMap = new HashMap<String, Integer>();

            /**
             * 这个方法只会执行一次
             * 可以在这里实现一些初始化的功能
             * 所以,就可以在open方法中获取广播变量数据
             */
            @Override
            public void open(Configuration parameters) throws Exception {
                super.open(parameters);
                //3:获取广播数据
                this.broadCastMap = getRuntimeContext().getBroadcastVariable("broadCastMapName");
                for (HashMap map : broadCastMap) {
                    allMap.putAll(map);
                }
            }
            @Override
            public String map(String value) throws Exception {
                Integer age = allMap.get(value);
                return value + "," + age;
            }
        }).withBroadcastSet(toBroadcast, "broadCastMapName");//2:执行广播数据的操作
        result.print();
    }

}

Flink之Counter(计数器)

Accumulator即累加器,与Mapreduce counter的应用场景差不多,都能很好地观察task在运行期间的数据变化
可以在Flink job任务中的算子函数中操作累加器,但是只能在任务执行结束之后才能获得累加器的最终结果。
Counter是一个具体的累加器(Accumulator)实现
IntCounter, LongCounterDoubleCounter
用法
1:创建累加器
private IntCounter numLines = new IntCounter(); 
2:注册累加器
getRuntimeContext().addAccumulator("num-lines", this.numLines);
3:使用累加器
this.numLines.add(1); 
4:获取累加器的结果
myJobExecutionResult.getAccumulatorResult("num-lines")

/**
 * 计数器
 */
public class CounterDemo {
    public static void main(String[] args) throws Exception{
        //获取运行环境
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        DataSource<String> data = env.fromElements("a", "b", "c", "d");
        DataSet<String> result = data.map(new RichMapFunction<String, String>() {

            //1:创建累加器
            private IntCounter numLines = new IntCounter();
            @Override
            public void open(Configuration parameters) throws Exception {
                super.open(parameters);
                //2:注册累加器
                getRuntimeContext().addAccumulator("num-lines",this.numLines);

            }
            //int sum = 0;
            @Override
            public String map(String value) throws Exception {
                //如果并行度为1,使用普通的累加求和即可,但是设置多个并行度,则普通的累加求和结果就不准了
                //sum++;
                //System.out.println("sum:"+sum);
                this.numLines.add(1);
                return value;
            }
        }).setParallelism(8);
        //如果要获取counter的值,只能是任务
        //result.print();
        result.writeAsText("d:\\data\\mycounter");
        JobExecutionResult jobResult = env.execute("counter");
        //3:获取累加器
        int num = jobResult.getAccumulatorResult("num-lines");
        System.out.println("num:"+num);

    }
}

4.1 常见Transformation操作

4.1.1 map和filter

/**
 * 数据源:1 2 3 4 5.....源源不断过来
 * 通过map打印一下接受到数据
 * 通过filter过滤一下数据,我们只需要偶数
 */
public class MapDemo {
        public static void main(String[] args) throws Exception {
            StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
            DataStreamSource<Long> numberStream = env.addSource(new MyNoParalleSource()).setParallelism(1);
            SingleOutputStreamOperator<Long> dataStream = numberStream.map(new MapFunction<Long, Long>() {
                @Override
                public Long map(Long value) throws Exception {
                    System.out.println("接受到了数据:"+value);
                    return value;
                }
            });
            SingleOutputStreamOperator<Long> filterDataStream = dataStream.filter(new FilterFunction<Long>() {
                @Override
                public boolean filter(Long number) throws Exception {
                    return number % 2 == 0;
                }
            });

            filterDataStream.print().setParallelism(1);
            env.execute("StreamingDemoWithMyNoPralalleSource");
        }


}

4.1.2 flatMap,keyBy和sum

/**
 * 滑动窗口实现单词计数
 * 数据源:socket
 * 需求:每隔1秒计算最近2秒单词出现的次数
 *
 * 练习算子:
 * flatMap
 * keyBy:
 *    dataStream.keyBy("someKey") // 指定对象中的 "someKey"字段作为分组key
 *    dataStream.keyBy(0) //指定Tuple中的第一个元素作为分组key
 * sum
 */
public class WindowWordCountJava {
    public static void main(String[] args) throws  Exception {
        int port;
        try{
            ParameterTool parameterTool = ParameterTool.fromArgs(args);
            port = parameterTool.getInt("port");
        }catch (Exception e){
            System.err.println("no port set,user default port 9988");
            port=9988;
        }
        //步骤一:获取flink运行环境(stream)
        StreamExecutionEnvironment env= StreamExecutionEnvironment.getExecutionEnvironment();
        String hostname="10.126.88.226";
        String delimiter="\n";
        //步骤二:获取数据源
        DataStreamSource<String> textStream = env.socketTextStream(hostname, port, delimiter);
        //步骤三:执行transformation操作
        SingleOutputStreamOperator<WordCount> wordCountStream = textStream.flatMap(new FlatMapFunction<String, WordCount>() {
            public void flatMap(String line, Collector<WordCount> out) throws Exception {
                String[] fields = line.split("\t");
                for (String word : fields) {
                    out.collect(new WordCount(word, 1L));
                }
            }
        }).keyBy("word")
                .timeWindow(Time.seconds(2), Time.seconds(1))//每隔1秒计算最近2秒
                .sum("count");

        wordCountStream.print().setParallelism(1);//打印并设置并行度
        //步骤四:运行程序
        env.execute("socket word count");


    }

    public static class WordCount{
        public String word;
        public long count;
        public WordCount(){

        }
        public WordCount(String word,long count){
            this.word=word;
            this.count=count;
        }

        @Override
        public String toString() {
            return "WordCount{" +
                    "word='" + word + '\'' +
                    ", count=" + count +
                    '}';
        }
    }
}

4.1.3 union

/**
 * 合并多个流,新的流会包含所有流中的数据,但是union是一个限制,就是所有合并的流类型必须是一致的
 */
public class unionDemo {
    public static void main(String[] args) throws Exception {
        //获取Flink的运行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //获取数据源
        DataStreamSource<Long> text1 = env.addSource(new MyNoParalleSource()).setParallelism(1);//注意:针对此source,并行度只能设置为1
        DataStreamSource<Long> text2 = env.addSource(new MyNoParalleSource()).setParallelism(1);

        //把text1和text2组装到一起
        DataStream<Long> text = text1.union(text2);

        DataStream<Long> num = text.map(new MapFunction<Long, Long>() {
            @Override
            public Long map(Long value) throws Exception {
                System.out.println("原始接收到数据:" + value);
                return value;
            }
        });
        //每2秒钟处理一次数据
        DataStream<Long> sum = num.timeWindowAll(Time.seconds(2)).sum(0);
        //打印结果
        sum.print().setParallelism(1);
        String jobName = unionDemo.class.getSimpleName();
        env.execute(jobName);
    }
}

4.1.4 connect,conMap和conFlatMap

/**
 * 和union类似,但是只能连接两个流,两个流的数据类型可以不同,会对两个流中的数据应用不同的处理方法
 */
public class ConnectionDemo {
    public static void main(String[] args) throws Exception {
        //获取Flink的运行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        //获取数据源
        DataStreamSource<Long> text1 = env.addSource(new MyNoParalleSource()).setParallelism(1);//注意:针对此source,并行度只能设置为1

        DataStreamSource<Long> text2 = env.addSource(new MyNoParalleSource()).setParallelism(1);
        SingleOutputStreamOperator<String> text2_str = text2.map(new MapFunction<Long, String>() {
            @Override
            public String map(Long value) throws Exception {
                return "str_" + value;
            }
        });

        ConnectedStreams<Long, String> connectStream = text1.connect(text2_str);

        SingleOutputStreamOperator<Object> result = connectStream.map(new CoMapFunction<Long, String, Object>() {
            @Override
            public Object map1(Long value) throws Exception {
                return value;
            }

            @Override
            public Object map2(String value) throws Exception {
                return value;
            }
        });

        //打印结果
        result.print().setParallelism(1);
        String jobName = ConnectionDemo.class.getSimpleName();
        env.execute(jobName);
    }
}

4.1.5 Split和Select

/**
 *  根据规则把一个数据流切分为多个流
 应用场景:
 * 可能在实际工作中,源数据流中混合了多种类似的数据,多种类型的数据处理规则不一样,所以就可以在根据一定的规则,
 * 把一个数据流切分成多个数据流,这样每个数据流就可以使用不用的处理逻辑了
 */
public class SplitDemo {
    public static void main(String[] args) throws  Exception {
        //获取Flink的运行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //获取数据源
        DataStreamSource<Long> text = env.addSource(new MyNoParalleSource()).setParallelism(1);//注意:针对此source,并行度只能设置为1
        //对流进行切分,按照数据的奇偶性进行区分
        SplitStream<Long> splitStream = text.split(new OutputSelector<Long>() {
            @Override
            public Iterable<String> select(Long value) {
                ArrayList<String> outPut = new ArrayList<>();
                if (value % 2 == 0) {
                    outPut.add("even");//偶数
                } else {
                    outPut.add("odd");//奇数
                }
                return outPut;
            }
        });

        //选择一个或者多个切分后的流
        DataStream<Long> evenStream = splitStream.select("even");
        DataStream<Long> oddStream = splitStream.select("odd");
        DataStream<Long> moreStream = splitStream.select("odd","even");

        //打印结果
        evenStream.print().setParallelism(1);
        String jobName = SplitDemo.class.getSimpleName();
        env.execute(jobName);

    }
}

4.2常见sink操作

4.2.1 print() / printToErr()

打印每个元素的toString()方法的值到标准输出或者标准错误输出流中

4.2.2 writeAsText()

/**
 * 数据源:1 2 3 4 5.....源源不断过来
 * 通过map打印一下接受到数据
 * 通过filter过滤一下数据,我们只需要偶数
 */
public class WriteTextDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<Long> numberStream = env.addSource(new MyNoParalleSource()).setParallelism(1);
        SingleOutputStreamOperator<Long> dataStream = numberStream.map(new MapFunction<Long, Long>() {
            @Override
            public Long map(Long value) throws Exception {
                System.out.println("接受到了数据:"+value);
                return value;
            }
        });
        SingleOutputStreamOperator<Long> filterDataStream = dataStream.filter(new FilterFunction<Long>() {
            @Override
            public boolean filter(Long number) throws Exception {
                return number % 2 == 0;
            }
        });
   filterDataStream.writeAsText("D:\\\\flinklesson\\src\\output\\test").setParallelis
  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-12-08 13:53:00  更:2021-12-08 13:53:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:57:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码