Flink的使用

Flink 的核心概念

核心概念概述

      Flink 程序是实现分布式集合转换操作(如:过滤、映射、更改状态、join、分组、定义窗口、聚合等)的有规律的程序。集合最初是由 sources(数据源) (例如: 从文件中读取、kafka topic、或者来自本地、内存中的集合等)创建的, 结果通过 sink 输出,可能是将数据写入文件中,或者以标准输出的形式输出(例如:输出到控制台)。


      Flink 程序可以以不同的形式运行——以独立的形式运行或者嵌入到其他程序中执行。执行的动作可以发生在本地,也可以发生在多台机器构成的集群中。


      根据数据源类型的不同,可以是有界的或者无界的,你可以写批处理程序或者流处理程序,其中 DataSet API 是提供给批处理程序用的,而 DataStream API 是提供给流处理程序用的。


      根据数据源类型的不同,可以是有界的或者无界的,你可以写批处理程序或者流处理程序,其中 DataSet API 是提供给批处理程序用的,而 DataStream API 是提供给流处理程序用的。


在这里插入图片描述
                                                      Flink的架构
  1. Deploy: 本地,集群,谷歌和亚马逊的云服务器上。
  2. Core:自身的runtime 环境和基础环境通信。
  3. API:DataSet API(批处理) DataStream API(流处理)
  4. Libraries:CEP 复杂事件处理,Table/Sql:结构化数据分析,FlinkM1 机器学习,Gelly图数据库。

    DataSet 和 DataStream

      Flink 使用 DataSet 和 DataStream 这两个特殊的类来表示程序中的数据,你可以将它们想象成一个包含重复数据的不可变数据集合,其中 DataSet 的数据是有限的而 DataStream 中的数据个数则是无限的。DataSet 是批处理的数据抽象,DataStream 是流处理面的数据抽象。

几种形式的对比:

在这里插入图片描述                                             Flink DataFlow 基本形式
在这里插入图片描述

Flink 的编程步骤

  1. 获取数据
  2. Transfiramtion 操作
  3. Sink 的位置(print)
  4. 触发执行
//A.获取执行环境
val env = ExecutionEnvironment.createLocalEnvironment(1)
//B.获取数据
val text = env.readTextFile("D:/hello.txt")
//引入隐式转换
import org.apache.flink.api.scala._
//C.Transforamtion 操作
val word = text.flatMap(_.split(" ")).map((_, 1)).groupBy(0).sum(1)
//D.Sink的位置(print)
word.print()
word.writeAsText("D:/result.txt")
//E.触发执行
env.execute("flink wordcount demo")

延迟执行(懒加载)

      所有的 Flink 的执行都是延迟执行的,这个 Spark 中的 action 的时候才会执行,Flink 和 Spark 是一样的道理,当遇到显示的触发的时候才会运行,例如调用 execte()的时候,这样程序的其他操作才被执行。

  好处:

      这样的延迟操作,可以构建比较复杂的 DAG 图,这样可以提高它的资源利用,没有必须要每一步都要落地,是一条线的形式执行的,这样可以大大的提高执行效率。

指定 key

      Flink 的数据模型不在基于键值对。因此,您无需将数据集类型物理打包到键和值中。

      键是"虚拟的":他们被定义为实际数据上的函数,以指导分组算子,可以在具体算子通过参数指定。

      主要有以下几种形式

  使用 tuple 的形式:

val rdd: DataSet[(Int, Int)] = env.fromCollection(List((1, 2), (1, 3)))
val rdd01: GroupedDataSet[(Int, Int)] = rdd.groupBy(0)

  还可以是字段表达式:

object filedsExepl {
  def main(args: Array[String]): Unit = {
    val env: ExecutionEnvironment = ExecutionEnvironment.createLocalEnvironment(1)
    import org.apache.flink.api.scala._
    val value: DataSet[String] = env.readTextFile("D:/hello.txt")
    value.map(w => WordWithCount(w, 1)).groupBy("word").first(2).print()
  }
}
case class WordWithCount(word: String, count: Long)

  还可以是字段表达式:

def main(args: Array[String]): Unit = {
  val env: ExecutionEnvironment = ExecutionEnvironment.createLocalEnvironment(1)
  import org.apache.flink.api.scala._
  val value: DataSet[String] = env.readTextFile("D:/hello.txt")
  value.map(w => WordWithCount(w, 1)).groupBy(x=>x.word).first(2).print()
}

指定转换函数(Transformation)

  增强函数形式:

**
  * 通过指定继承实现类或接口 指定转换函数
  */
object TransFun {
  def main(args: Array[String]): Unit = {
    val env: ExecutionEnvironment = ExecutionEnvironment.createLocalEnvironment(1)
    import org.apache.flink.api.scala._
    val data: DataSet[String] = env.fromCollection(List("1", "2", "3"))
    val rdd: DataSet[Int] = data.map(new MyMapFunction())
    rdd.print()
  }
}
class MyMapFunction extends RichMapFunction[String, Int] {
  def map(in: String): Int = {
    in.toInt
  }
}


或者

/**
  * 通过匿名内部类的这种形式,指定转换函数
  */
object TransFun01 {
  def main(args: Array[String]): Unit = {
    val env: ExecutionEnvironment = ExecutionEnvironment.createLocalEnvironment(1)
    import org.apache.flink.api.scala._
    val data: DataSet[String] = env.fromCollection(List("1", "2", "3"))
    val rdd: DataSet[Int] = data.map(new RichMapFunction[String, Int] {
      def map(in: String):Int = { in.toInt }
    })
    rdd.print()
  }
}

  Lambda Functions:

**
  * 通过使用Lambda  函数形式,指定转换函数
  */
object TransFun02 {
  def main(args: Array[String]): Unit = {
    val env: ExecutionEnvironment = ExecutionEnvironment.createLocalEnvironment(1)
    import org.apache.flink.api.scala._
    val data: DataSet[String] = env.fromCollection(List("1", "2", "3"))
    val rdd: DataSet[Int] = data.map(line=>line.toInt)
    rdd.print()
  }
}

Flink API 编程

      Flink 中程序是实现数据集转换的常规程序(例如:过滤,映射,映射,连接,分组)。数据集最初是从某些来源创建的(例如,通过读取文件或者从本地集合创建)。结果通过接收器返回,接收器可以例如数据写入(分布式)文件或者标准输出(例如命令行终端)。Flink 程序可以在各种环境中运行,独立运行或者嵌入其他程序中。执行可以在本地 JVM 中执行,也可以在许多计算机的集群上执行。

支持的数据源 DataSet

一.  基于 socket 的有:(端口获取)

  1. socketTextStream(hostName,port)
  2. socketTextStream(hostName,port,delimiter)
  3. socketTestStream(hostName,port,delimiter,maxRetry)
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    // 连接此socket获取输入数据
    val text = env.socketTextStream("vm2", 9999, '\n')
    //打印接受到的数据
    text.print()
    env.execute("DataSourceFromSocket --- ")
    

二.  基于文件或者文件夹

  1. readTextFile(String path) 读取 txt 格式的文件。

  2. readFile(FileInputFormat inputFormat,String path) 如果数据源不是 txt 文件,可以指定文件的格式。

  3. readFileStream(String filePath, long intervalMils,FileMonitoringFunction.watchType watchType) 其中 interval 是轮询的间隔,这个需要指定,而且 watch type 保留三种选择。
    FileMonitoringFunction.WatchType.ONLY_NEW_FILES 只处理新文件
    FileMonitoringFunction.WatchType.PROCESS_ONLY_APPENDED 只处理追加的内容
    FileMonitoringFunction.WatchType.REPROCESS_WITH_APPENDED 既要处理追加的 内容,也会处理file中之前的内容

    object DataSourceFromtxtFile {
      def main(args: Array[String]): Unit = {
        //A.获取执行环境
        val env = ExecutionEnvironment.createLocalEnvironment(1)
        //B.获取数据
        val text = env.readTextFile("D:/hello.txt")
        //C.数据
        text.print()
      }
    }
    

    读取csv

    def csvFile(env: ExecutionEnvironment): Unit = {
      import org.apache.flink.api.scala._
      env.readCsvFile[(String, Int, String)]("D:/hello.csv", ignoreFirstLine = true).print()
    }
    

    递归读取文件

    def readRecursiveFiles(env: ExecutionEnvironment): Unit = {
      env.readTextFile("d:/").print()
      println("-----------------------------")
      val conf: Configuration = new Configuration()
      conf.setBoolean("recursive.file.enumeration", true)
      env.readTextFile("d:/").withParameters(conf).print()
    }
    

    读取压缩格式的文件

    /**
      * 从压缩文件中读取数据生成dataset
      */
    def readCompressionFiles(env: ExecutionEnvironment): Unit = {
      env.readTextFile("d:/").print()
    }
    

三.  基于集合

/**
  * 从集合中获取数据
  *
  * @param e
  */
def fromCollection(env: ExecutionEnvironment): Unit = {
  import org.apache.flink.api.scala._
  val data = 0 to 10
val rdd: DataSet[String] = env.fromCollection(List("1","2"))
val rdd: DataSet[String] = env.fromElements("")
val rdd: DataSet[Long] = env.generateSequence(0, 1000)

  env.fromCollection(data).print()
}

sink 的目的地(代码演示)

一.  Data Sink类型

  1. writeAsText() 以 txt 形式写出去。

  2. writeAsCsv() 以 csv 形式写出去

  3. write()

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

    	object SinkApp {
      def main(args: Array[String]): Unit = {
        val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
        val data = 1 to 10
        import org.apache.flink.api.scala._
        val dds: DataSet[Int] = env.fromCollection(data)
        dds.writeAsText("d:/shuchu.txt",WriteMode.NO_OVERWRITE)
        env.execute()
      }
    }
    

二.  Flink内置 Connectors (这里有很多数据下沉的 api )

  1. ApacheKafka(source/sink)
  2. Apache Cassandra (sink)
  3. Elasticsearch (sink)
  4. Hadoop FileSystem (sink)
  5. RabbitMQ (source/sink)
  6. Apache ActiveMQ (source/sink)
  7. ARedis(sink) \color{red}{ARedis (sink)}ARedis(sink)

三.  自定义 sink

实现自定义的 sink,步骤如下:

  1. 实现 SinkFunction 接口
  2. 继承 RichSinkFunction

计数器

多分区的情况下无法计数

  1. 定义计数器
  2. 注册计数器
  3. 获取计数器
/**
  * 计数器
  */
object MyTrans01 {
  def main(args: Array[String]): Unit = {
    val env: ExecutionEnvironment = ExecutionEnvironment.createLocalEnvironment(1)
    import org.apache.flink.api.scala._
    val ds: DataSet[String] = env.fromElements("hadoop", "spark", "hbase", "redis", "zookeeper")

    /* ds.map(new RichMapFunction[String, Long] {
       var counter = 0l
       override def map(in: String): Long = {
         counter=counter+1
         println("counter:    "+counter)
         counter
       }
     }).setParallelism(3).print()*/
    //累加器
    val info = ds.map(new RichMapFunction[String, String] {
      //1.定义一个计数器(不要导错包)
      val counter: LongCounter = new LongCounter()

      override def open(parameters: Configuration): Unit = {
        //2.注册计数器
        getRuntimeContext.addAccumulator("acc--name", counter)
      }

      override def map(in: String): String = {
        counter.add(1)
        in
      }
    }).setParallelism(6)
    // info.print() //不支持这种做法
    info.writeAsText("d:/a.txt",WriteMode.OVERWRITE)
    val result: JobExecutionResult = env.execute("exe name")
    val l: Long = result.getAccumulatorResult[Long]("acc--name")
    println(l)
  }
}

分布式缓存

      Flink 提供了一个分布式缓存,类似于 Apacke Hadoop, 使本地可访问用户函数的并行实例。此功能可用于共享包含静态外部数据(如字典或机器学习的回归模型)的文件。

      缓存的工作原理如下。程序在其执行环境中以特定名称注册本地或远程文件系统(如 HDFS 或者 S3)的文件或目录作为缓存文件。当程序执行时,Flink 自动将文件或者目录复制到所有工作人员的本地文件系统。用户函数可以查找指定名称下的文件或目录,并从工作人员的本地文件系统中访问它。使用如下:

/**
  * 分布式缓存
  */
object Mytrans02 {
  def main(args: Array[String]): Unit = {
    val env = ExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
    val ds: DataSet[String] = env.fromElements("hadoop", "spark", "storm")
    env.registerCachedFile("D:/hello.txt", "FileName")
    ds.map(new RichMapFunction[String, String]() {
      override def open(parameters: Configuration): Unit = {
        val dcFile: File = getRuntimeContext.getDistributedCache().getFile("FileName")
        val lines= FileUtils.readLines(dcFile)
        //此时会出现一个问题 java-->scala
        //
        import scala.collection.JavaConverters._
        for (line <- lines.asScala) {
          println(line)
        }
      }
      override def map(in: String): String = {
        in
      }
    }).print()
    // define your program and execute
    env.execute()
  }
}

Flink 支持的数据类型

  1. Java Tuple 和 Scala Case 类

          Tuple 是包含固定数量各种类型字段的复合类。Flink Java Api 提供了 Tuple1-Tuple25。Tuple 的字段可以是 Flink 的任意类型,甚至嵌套 Tuple 。

          Scala 的 Case 类(以及 Scala 的 Tuple ,实际是 Case class 的特殊类型)是包含了一定数量多种类型字段的组合类型。Tuple 字段通过他们 1-offset 名称定为,例如 _1 代表第一个字段。Case class 通过字段名称获得。

  2. Java POJO

          Java 和 Scala 的类在满足下列条件时,将会被 Flink 视作特殊的 POJO 数据类型专门进行处理:

    1. 是公共类 2. 无参构造是公共的 3. 所有的属性都是可获得的(声明为公共的,或提供 get ,set 方法)。 4. 字段的类型必须是 Flink 支持的。Flink 会用 Avro 来序列化任意的对象。
  3. 基本类型

          Flink 支持 Java 和 Scala 所有的基本数据类型, 比如 Integer ,String,和 Double1.2 无参构造。

  4. 通用类

          Flink 支持大多数的 Java,Scala 类(API 和自定义)包含不能序列化字段的类在增加一些限制后也可支持。遵循 Java Bean 规范的类一般都可以使用。

          所有不能视为 POJO 的类 Flink 都会当做一般类处理。这些数据类型被视作黑箱,其内容是不可见的。通用类使用 Kryo 进行序列/反序列化。

  5. 值类型 Values

          通过实现 org.apache.flinktypes.Value 接口的 read 和 write 方法提供自定义代码来进行序列化/反序列化,而不是使用通用的序列化框架。

          Flink 预定义的值类型与原生数据类型是一一对应的(例如: ByteValue, ShortValue, IntValue, LongValue, FloatValue, DoubleValue, StringValue, CharValue, BooleanValue)。这些值类型作为原生数据类型的可变变体,他们的值是可以改变的,允许程序重用对象从而缓解 GC 的压力

  6. Hadoop Writables

          它实现 org.apache.hadoop.Writable 接口的类型,该类型的序列化逻辑在 write() 和 readFields() 方法中实现

  7. 特殊类型

    1. Scala 的 Either、Option 和 Try。
    2. Java API 有自己的 Either 实现。

Flink wordCount

  测试数据:
      Hello heloo word spark storm
      Hadoop hadoop hadoop

   Flink + scala 批处理
   引入pom:

<dependencies>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-scala_2.11</artifactId>
        <version>1.6.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-clients_2.11</artifactId>
        <version>1.6.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-streaming-scala_2.11</artifactId>
        <version>1.6.0</version>
    </dependency>
</dependencies>

   创建一个object类

def main(args: Array[String]): Unit = {
  val env = ExecutionEnvironment.createLocalEnvironment(1)
  val text = env.readTextFile("D:/hello.txt")
  import org.apache.flink.api.scala._
  val word = text.flatMap(_.split(" ")).map((_, 1)).groupBy(0).sum(1)
  word.print()
  word.writeAsText("D:/result.txt")
  env.execute("flink wordcount demo")
}

  打印结果

在这里插入图片描述

实时处理程序

  flink 实时接收一个端口的数据

import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.windowing.time.Time
// 定义一个数据类型保存单词出现的次数
case class WordWithCount(word: String, count: Long)
object StreamingwordCount {
  def main(args: Array[String]): Unit = {
    // 获取运行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    // 连接此socket获取输入数据
    val text = env.socketTextStream("vm2", 9999, '\n')
    //需要加上这一行隐式转换 否则在调用flatmap方法的时候会报错
    import org.apache.flink.api.scala._
    // 解析数据, 分组, 窗口化, 并且聚合求SUM
    val windowCounts = text
      .flatMap { w => w.split("\\s") }
      .map { w => WordWithCount(w, 1) }
      .keyBy("word")
      .timeWindow(Time.seconds(5), Time.seconds(1))
      .sum("count")
    // 打印输出并设置使用一个并行度
    windowCounts.print().setParallelism(1)
    env.execute("Socket Window WordCount")
  }
}

  运行程序之后,启动 linux ,执行 nc -lk 9999,输入单词

在这里插入图片描述

  查看结果
在这里插入图片描述

Time 与 Window

Time

      在 Flink 的流式处理中,会涉及到时间的不同概念,如下。

在这里插入图片描述

  1. Event Time:是事件创建的时间。它通常由事件中的时间戳描述,例如采集的日志数据中,每一条记录都会记录自己的生成时间,Flink 通过时间戳分配器访问事件时间戳。
  2. Ingestion Time:是数据进入 Flink 的时间。
  3. Processiong Time:是每一个执行基于时间操作的算子的本地系统时间,与机器相关,默认的时间属性就是 Processiong Time。

      例如,一条日志进入 Flink 的时间为 2017-11-12 10:00:00 .123(ingestion time),到达 window 的系统时间为 2017-11-12 10:00:00 .234(processing time),日志的内容如下:

     2017-11-02 18:37:15.624 INFO Fail over to rm2 (event time)

      对于业务来说,要统计 1 min 内的故障日志个数,evenTime是最重要的,因为要根据日志的生成时间进行统计。

Window

Window 概述

      streaming 流式计算是一种被设计用于处理无限数据集的数据引擎,而无限数据集市指不断增粘的本质上无限的数据集,而 window 是一种切割无限数据为有限块进行处理的手段。

      window 是无限数据流处理的核心,window 将一个无限的 stream 拆分成有限大小的 "buckets"桶,我们可以在这些桶上做计算操作。

window类型

   Window 可以分成两类:

  • CountWindow:按照指定的数据条数生成一个 Window,与时间无关。
  • TimeWindow:按照时间生成Window。

      对于 TimeWindow,可以根据窗口实现原理的不同分成三类:滚动窗口(Tumbling Window)、滑动窗口(Sliding Window)和会话窗口(Session Window)。

  1. 滚动窗口
       将数据依据固定的窗口长度对数据进行切片。

       特点:时间对齐、窗口长度固定、没有重叠

          滚动窗口分配器将每个元素分配到一个指定窗口大小的窗口中,滚动窗口有一个固定的大小,并且不会出现重叠。例如:你设置了一个 5 分钟大小的滚动窗口,如下图

在这里插入图片描述
         适用场景:适合做 BI (商业智能)统计等(做每个时间段的聚合计算)

  1. 滑动窗口
       滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成。

       特点:时间对齐,窗口长度固定,有重叠

          滑动窗口分配器将元素分配到固定长度的窗口中,与滚动窗口类似,窗口的大小由窗口大小参数来配置,另一个窗口滑动参数控制滑动窗口开始的频率。因此,滑动窗口如果滑动参数小于窗口大小的话,窗口是会重叠的,在这种情况下元素会被分配到多个窗口中。

          例如:你有 10 分钟的窗口和 5 分钟的滑动,那么每个窗口中 5 分钟的窗口里包含着上个 10 分钟产生的数据,如下图:

在这里插入图片描述
         适用场景:对最近一个时间段内的统计(求某接口最近 5 分钟的失败率来觉得是否报警)。

  1. 会话窗口
       由一系列事件组合一个指定事件长度的 timeout 间隙组成,类似于 web 应用的 session,也就是一段时间没有接收到新数据就会生成新的窗口。

       特点:时间无对齐

          session 窗口分配器通过 session 活动来对元素进行分组,session 窗口跟滚动窗口和滑动窗口相比,不会有重叠和固定的开始时间和结束时间的情况,相反,当它在一个固定的时间周期内不再收到元素,即非活动间隔产生,那么这个窗口就会关闭。。一个 session 窗口通过一个 session 间隔来配置,这个 session 间隔定义了非活跃周期的长度,当这个非活跃周期产生,那么当前的 session 将关闭并且后续的元素将被分配到新的 session 窗口中去。

在这里插入图片描述

Window API

TimeWindow

      TimeWindow 是将指定时间范围内的所有数据组成一个 window ,一次对一个 window 里面的所有数据进行计算。

  1. 滚动窗口

          Flink 默认的时间窗口根据 Processiong Time 进行窗口的划分,将 Flink 获取到的数据根据进入 Flink 的时间划分到不同的窗口中。

    def main(args: Array[String]): Unit = {
      // 获取运行环境
      val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
      // 连接此socket获取输入数据
      val text =  env.socketTextStream("vm2", 9999, '\n')
      //需要加上这一行隐式转换 否则在调用flatmap方法的时候会报错
      import org.apache.flink.api.scala._
      // 解析数据, 分组, 窗口化, 并且聚合求SUM
      val windowCounts = text
        .flatMap { w => w.split(" ") }
        .map { w => WordWithCount01(w, 1) }
        .keyBy("word")
        //滚动时间窗口
        .timeWindow(Time.seconds(5))
        .sum("count")
      // 打印输出并设置使用一个并行度
      windowCounts.print().setParallelism(1)
      env.execute("Socket TimeTumbWindow")
    }
    
  2. 滑动窗口

          滑动窗口和滚动窗口的函数名是完全一致的,只是在传参数时需要传入两个参数,一个是 window_sive,一个是 sliding_size。

          下面代码中的 sliding_size 设置为 2s , 也就是说窗口没 2s 计算一次,每次计算的 window 范围是 5s 内的所有元素

    /**
      * 滑动窗口
      */
    object TimeSlidWindow {
      def main(args: Array[String]): Unit = {
        // 获取运行环境
        val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
        // 连接此socket获取输入数据
        val text = env.socketTextStream("vm2", 9999, '\n')
        //需要加上这一行隐式转换 否则在调用flatmap方法的时候会报错
        import org.apache.flink.api.scala._
    
        // 解析数据, 分组, 窗口化, 并且聚合求SUM
        val windowCounts = text
          .flatMap { w => w.split(" ") }
          .map { w => WordWithCount01(w, 1) }
          .keyBy("word")
          //滚动时间窗口
          .timeWindow(Time.seconds(5), Time.seconds(1))
          .sum("count")
        // 打印输出并设置使用一个并行度
        windowCounts.print().setParallelism(1)
        env.execute("Socket Window WordCount")
      }
    }
    

          时间间隔可以通过 Time.miliseconds(x),Time.seconds(x),Time.minutes(x) 等其中的一个来指定。

CountWindow


未完,失误上传

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章