Flink知识点总结

一、Flink简介

1.1 初识Flink

Flink项目的理念是:Apache Flink是为分布式高性能随时可用以及准确的流处理应用程序打造的开源流处理框架。

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

Flink能够提供毫秒级别的延迟,同时保证了数据处理的低延迟、高吞吐和结果的正确性,还提供了丰富的时间类型和窗口计算、Exactly-once(就一次)语义支持,另外还可以进行状态管理,并提供了 CEP(复杂事件处理)的支持。

1.2 Flink的重要特点

在spark中,一切都是由批次组成的,离线数据是一个大批此,而实时数据是由一个一个无限的小批次组成的;

在flink中,一切都是由流组成的,离线数据是有界限的流,实时数据是一个没有界限的流,这就是所谓的有界流和无界流。

  1. 无界数据流

    无界数据流有一个开始但是没有结束,它们不会在生成时终止并提供数据,必须连续处理无界流,也就是说必须再获取后立即处理event;

    对于无界数据流是无法等待所有数据都到达,因为输入是无界的,并且再任何时间点都不会完成;

    处理无界数据通常要求以特定顺序(例如事件发生的顺序)获取event,以便能够推断结果完整性。

  2. 有界数据流

    有界数据流有明确定义的开始和结束,可以在执行任何计算之前通过获取所有数据来处理有界流;

    处理有界流不需要有序获取,因为可以始终对有界数据集进行排序,有界流的处理也称为批处理。

    Flink知识点总结_第1张图片

    这种以流为世界观的架构,获得的最大好处就是具有极低的延迟。

1.2.1 时间驱动型(Event-driven)

时间驱动型应用是一类具有状态的应用,它从一个或多个事件流提取数据,并根据到来的时间触发计算、状态更新或其他外部动作

1.2.2 流与批的世界观

  1. 批处理有界持久大量,非常适合需要访问全套记录才能完成的计算工作,一般用于离线统计
  2. 流处理无界实时,无需针对整个数据集执行操作,而是对通过系统传输的每个数据项执行操作,一般用于实时统计

1.2.3 分层API

Flink提供的最高层级的抽象是SQL

越顶层越抽象,表达含义越明显,使用越方便

越底层越具体,表达能力越丰富,使用越灵活

Flink几大模块

  • Flink Table & SQL(还没开发完)
  • Flink Gelly(图计算)
  • Flink CEP(复杂事件处理)

1.3 Flink的其他特点

  1. 支持事件时间(event-time)和处理时间(processing time)
  2. 精确一次的状态一致性保证
  3. 低延迟,每秒处理数百万个事件,毫秒级延迟
  4. 与众多常用存储系统连接
  5. 高可用,动态扩展,实现7 * 24小时全天侯运行

1.4 Flink和SparkStreaming对比

这个问题是一个非常宏观的问题,因为两个框架的不同点非常之多,但是在面试时有非常重要的一点一定要回答出来:Flink是标准的实时处理引擎,基于事件驱动;SparkStreaming是微批(Micro-Batch)的模型

SparkStreaming Flink
架构模型 主要角色包括:Master、Worker、Driver和Executor 主要包括:JobManager、TaskManager和Slot
任务调度 连续不间断的生成微小的数据批次,构建有向无环图DAG,会依次创建DStreamGraph、JobGenerator、JobScheduler 根据用户提交的代码生成StreamGraph,经过优化生成JobGraph,然后提交给JobManager进行处理,JobManager会根据JobGraph生成ExecutionGraph,ExecutionGraph是Flink调度最核心的数据结构,JobManager根据ExecutionGraph对Job进行调度
时间机制 只支持处理时间 支持处理时间、事件时间、注入时间;同时也支持watermark机制来处理滞后数据
容错机制 可以设置checkpoint,假如发生故障并重启,可以从上次checkpoint之处恢复,但是这个行为只能使得数据不丢失,可能会重复处理,不能做到恰好一次处理语义 使用两阶段提交(2PC)协议来解决可能重复处理问题,做到恰好一次处理语义

1.5 Flink和Strom对比

Storm Flink
状态管理 无状态,需用户自行进行状态管理 有状态
窗口支持 对事件窗口支持较弱,缓存整个窗口的所有数据,窗口结束时一起计算 窗口支持较为完善,自带一些窗口聚合方法,并且会自动管理窗口状态
消息投递 At Most Once/At Least Once At Most Once/At Least Once/Exactly Once
容错方式 ACK机制:对每个消息进行全链路跟踪,失败或超时进行重发 检查点机制:通过分布式一致性快照机制,对数据流和算子状态进行保存;在发生错误时,使系统能够进行回滚
应用现状 在美团点评实时计算业务中已有较为成熟的运用,有管理平台、常用API和相应的文档,大量实时作业基于Storm构建 在美团点评实时计算业务中已有一定应用,但是管理平台、API及文档等仍需进一步完善

二、Flink部署

Flink安装模式

三、Flink运行架构

四、Flink流处理API

Flink知识点总结_第2张图片

4.1 Environment

创建一个执行环境,表示当前执行程序的上下文;如果程序是独立调用的,则此方法返回本地执行环境;如果从命令行客户端调用程序以提交到集群,则此方法返回此集群的执行环境

也就是说,getExecutionEnvironment会根据查询运行的方式决定返回什么样的运行环境,是最常用的一种创建执行环境的方式

val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment

val env = StreamExecutionEnvironment.getExecutionEnvironment

4.2 Source

4.3 Transform

算子 说明
map 将输入数据一对一处理
flatMap 将输入数据打散成一个List/将输入的list数据组合在一起
filter 过滤出表达式返回true的数据
keyBy DataStream → KeyedStream:分区不分流,只是将流对象转换,数据形式没改变只是可以进行聚合操作,其实就是分组
Rolling Aggregation 滚动聚合算子(sum(), min(), max(), minBy(), maxBy())进行keyBy后可以执行聚合算子
reduce KeyedStream → DataStream:一个分组数据流的聚合操作,合并当前的元素和上次聚合的结果,产生一个新的值,返回的流包含每一次聚合的结果,而不是只返回最后一次聚合的最终结果
split 将数据流DataStream转换为两个或者多个SplitStream
select 从split分割而开的两个或者多个SplitStream获取出来两个或多个DataStream
connect 将两个DataStream转换成一个ConnectedStream 但是内部还是各自管理
coMap/coFlatMap 对connect算子的结果ConnectedStream进行各自的map/flatMap操作
union 可以将多个(数据类型相同的)流DataStream合并为一个DataStream
Split和Select

Flink知识点总结_第3张图片

DataStream → SplitStream:根据某些特征把一个DataStream拆分成两个或者多个DataStream

Flink知识点总结_第4张图片

SplitStream →DataStream:从一个SplitStream中获取一个或者多个DataStream

Connect、CoMap/CoFlatMap和Union

Flink知识点总结_第5张图片

DataStream, DataStream → ConnectedStreams:连接两个保持他们类型的数据流,两个数据流被Connect之后,只是被放在了一个同一个流中,内部依然保持各自的数据和形式不发生任何变化,两个流相互独立

Flink知识点总结_第6张图片

ConnectedStreams → DataStream:作用于ConnectedStreams上,功能与map和flatMap一样,对ConnectedStreams中的每一个Stream分别进行map和flatMap处理

Flink知识点总结_第7张图片

DataStream → DataStream:对两个或者两个以上的DataStream进行union操作,产生一个包含所有DataStream元素的新DataStream

Union与Connect区别?
  1. Union之前两个流的类型必须是一样;Connect可以不一样,在之后的CoMap中再去调整成为一样的
  2. Union可以操作多个流;Connect只能操作两个流

4.4 支持的数据类型

  1. 基础数据类型

    Flink支持所有的Java和Scala基础数据类型:Int,Double,Long,String…

  2. Java和Scala元组(Tuples)

  3. Scala样例类(case classes)

  4. Java简单对象(pojos)

  5. 其他(Arrays,Lists,Maps,Enums等)

    Flink对Java和Scala中的一些特殊目的的类型也都是支持的,比如Java的ArrayList,HashMap,Enum等

4.5 实现UDF函数–更细粒度的控制流

4.5.1 函数类(Function Classes)

Flink暴露了所有UDF函数的接口(实现方式为接口或者抽象类)

例如MapFunction,FilterFunction,ProcessFunction等

还可以将函数实现成匿名类

package com.streamapi

import org.apache.flink.api.common.functions.{FilterFunction, RichFilterFunction, RichFlatMapFunction}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.scala._
import org.apache.flink.util.Collector

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

    val stream = env.readTextFile("source/sensor.txt")

    val value = stream.map(line => {
      // e.g.  sensor_2,1600828094726,113.45370583283331
      val splited = line.split(",")
      SensorReading(splited(0), splited(1).trim.toLong, splited(2).trim.toDouble)
    })

    // 自定义Fliter
    val dataStream = value.filter(new UDFFilter())
    // 可以将函数实现成匿名类
    // 使用RichFilterFunction
    value.filter(new RichFilterFunction[SensorReading] {
      override def filter(t: SensorReading): Boolean = {
        t.id == "sensor_8"
      }
      // 可以做一些预操作
      override def open(parameters: Configuration): Unit = super.open(parameters)
    })

    dataStream.print("filter")

    env.execute("udf filter")
  }
}

// 自定义Fliter函数
class UDFFilter() extends FilterFunction[SensorReading] {
  override def filter(t: SensorReading): Boolean = {
    t.id == "sensor_8"
  }
}

4.5.2 匿名函数(Lambda Functions)

value.filter(new RichFilterFunction[SensorReading] {
    override def filter(t: SensorReading): Boolean = {
        t.id == "sensor_8"
    }
    // 可以做一些预操作
    override def open(parameters: Configuration): Unit = super.open(parameters)
})

4.5.3 富函数(Rich Functions)

“富函数”是DataStream API提供的一个函数类的接口,所有Flink函数类都有其Rich版本

它与常规函数的不同在于,可以获取运行环境的上下文,并拥有一些生命周期方法,所以可以实现更复杂的功能

  1. RichMapFunction
  2. RichFlatMapFunction
  3. RichFilterFunction

Rich Functions有一个生命周期的概念,典型的生命周期方法有:

  1. open():初始化方法,当一个算子(例如map或者filter)被调用之前open()会被调用
  2. close():生命周期中的最后一个调用的方法,做一些清理工作
  3. getRuntimeContext():提供了函数的RuntimeContext的一些信息,例如函数执行的并行度,任务的名字,以及state状态
// 自定义FlatMap函数
class UDFFlatMap extends RichFlatMapFunction[Int, (Int, Int)] {
  var subTaskIndex = 0

  override def open(parameters: Configuration): Unit = {
    subTaskIndex = getRuntimeContext.getIndexOfThisSubtask

    // 还可以做一些初始化工作,例如建立一个和HDFS的连接
  }

  override def flatMap(in: Int, collector: Collector[(Int, Int)]): Unit = {
    if (in % 2 == subTaskIndex) {
      collector.collect((subTaskIndex, in))
    }
  }

  override def close(): Unit = {
    // 以下做一些清理工作,例如断开和HDFS的连接
  }
}

4.6 Sink

五、Flink中的Window

六、时间语义与Wartermark

七、ProcessFunction API(底层 API)

  1. ProcessFunction是一个低阶的流处理操作,它可以访问流处理程序的基础构建模块:
    • 事件(event):流元素
    • 状态(state):容错性,一致性, 仅在keyed stream中
    • 定时器(timers):event time和processing time,仅在keyed stream中
  2. ProcessFunction可以看作是一个具有keyed state和timers访问权的FlatMapFunction
    • 通过RuntimeContext访问keyed state
    • 计时器允许应用程序对处理时间和事件时间中的更改作出响应;对processElement()函数的每次调用都获得一个Context对象,该对象可以访问元素的event time timestamp和TimerService
    • TimerService可用于为将来的event/process time瞬间注册回调;当到达计时器的特定时间时,将调用onTimer()方法;在该调用期间,所有状态都再次限定在创建计时器时使用的键的范围内,从而允许计时器操作键控状态
  3. DataStream API提供了一系列的Low-Level转换算子,可以访问时间戳、watermark以及注册定时事件;还可以输出特定的一些事件,例如超时事件等
  4. ProcessFunction用来构建事件驱动的应用以及实现自定义的业务逻辑(使用之前的windows函数和转换算子无法实现)
  5. Flink提供了8个ProcessFunction
    • ProcessFunction
    • KeyedProcessFunction
    • CoProcessFunction
    • ProcessJoinFunction
    • BroadcastProcessFunction
    • KeyedBroadcastProcessFunction
    • ProcessWindowFunction
    • ProcessAllWindowFunction

7.1 KeyedProcessFunction

这里重点介绍KeyedProcessFunction

KeyedProcessFunction用来操作KeyedStream

KeyedProcessFunction会处理流的每一个元素,输出为0个、1个或者多个元素

所有的Process Function都继承自RichFunction接口,所以都有open()、close()和getRuntimeContext()等方法;而KeyedProcessFunction[KEY, IN, OUT]还额外提供了两个方法:

  1. processElement(v: IN, ctx: Context, out: Collector[OUT]):流中的每一个元素都会调用这个方法,调用结果将会放在Collector数据类型中输出;Context可以访问元素的时间戳,元素的key,以及TimerService时间服务;Context还可以将结果输出到别的流(side outputs)
  2. onTimer(timestamp: Long, ctx: OnTimerContext, out: Collector[OUT]):是一个回调函数;当之前注册的定时器触发时调用;参数timestamp为定时器所设定的触发的时间戳;Collector为输出结果的集合;OnTimerContext和processElement的Context参数一样,提供了上下文的一些信息,例如定时器
    触发的时间信息(事件时间或者处理时间)

7.2 TimerService和定时器(Timers)

Context和OnTimerContext所持有的TimerService对象拥有以下方法:

  1. currentProcessingTime():Long返回当前处理时间
  2. currentWatermark():Long返回当前watermark的时间戳
  3. registerProcessingTimeTimer(timestamp: Long): Unit:会注册当前key的processing time的定时器;当processing time到达定时时间时,触发timer
  4. registerEventTimeTimer(timestamp: Long): Unit:会注册当前key的event time定时器;当水位线大于等于定时器注册的时间时,触发定时器执行回调函数
  5. deleteProcessingTimeTimer(timestamp: Long): Unit:删除之前注册处理时间定时器;如果没有这个时间戳的定时器,则不执行
  6. deleteEventTimeTimer(timestamp: Long): Unit:删除之前注册的事件时间定时器,如果没有此时间戳的定时器,则不执行

当定时器timer触发时,会执行回调函数onTimer();注意定时器timer只能在keyed streams上面使用

class TempIncreaseAlertFunction() extends KeyedProcessFunction[String, SensorReading, String] {
  // 定义一个状态,用来保存上一个传感器温度值
  lazy val lastTemp = getRuntimeContext.getState(
    new ValueStateDescriptor[Double]("lastTemp", Types.of[Double])
  )

  // 定义一个状态,用来保存注册的定时器的时间戳
  lazy val currentTimer = getRuntimeContext.getState(
    new ValueStateDescriptor[Long]("timer", Types.of[Long])
  )

  override def processElement(value: SensorReading, ctx: KeyedProcessFunction[String, SensorReading, String]#Context, out: Collector[String]): Unit = {
    // 取出上一次的温度
    val prevTemp = lastTemp.value()
    // 将当前温度更新到上一次的温度这个变量中
    lastTemp.update(value.temperature)

    // 获取定时器的时间
    val currentTimerTimestamp = currentTimer.value()

    if (prevTemp == 0.0 || value.temperature < prevTemp) {
      // 温度下降或者是第一个温度值,删除定时器
      ctx.timerService().deleteProcessingTimeTimer(currentTimerTimestamp)
      // 清空状态变量
      currentTimer.clear()
    } else if (value.temperature > prevTemp && currentTimerTimestamp == 0) {
      // 温度上升并且没有设置定时器 才开始 注册定时器
      // 获取当前处理时间,延迟1s
      val timerTs = ctx.timerService().currentProcessingTime() + 1000
      ctx.timerService().registerProcessingTimeTimer(timerTs)

      // 把定时时间保存到状态
      currentTimer.update(timerTs)
    }
  }

  override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[String, SensorReading, String]#OnTimerContext, out: Collector[String]): Unit = {
    // 输出报警信息
    out.collect("传感器id为:" + ctx.getCurrentKey + "的传感器温度值已经连续1s上升了。")
    // 清空状态
    currentTimer.clear()
  }
}

7.3 侧输出流(SideOutput)

大部分的DataStream API的算子的输出是单一输出,也就是某种数据类型的流

除了split算子,可以将一条流分成多条流,这些流的数据类型也都相同

ProcessFunction的SideOutPut功能可以产生多条流,并且这些流的数据类型可以不一样

一个SideOutPut可以定义为OutPutTag[X]对象,X是输出流的数据类型

ProcessFunction可以通过Context对象发射一个事件到一个或者多个SideOutPut

package com.api

import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.ProcessFunction
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.util.Collector

object SideOutput {
  def main(args: Array[String]): Unit = {
    // 环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    // 设置事件时间
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    // 全局并行度
    env.setParallelism(2)

    val stream = env.socketTextStream("localhost", 9999)

    val dataStream: DataStream[SensorReading] = stream.map(line => {
      val splited = line.split(",")
      SensorReading(splited(0), splited(1).trim.toLong, splited(2).trim.toDouble)
    })
      // 有界无序,能大致估算出数据流中的事件的最大延迟时间
      .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(1)) {
        override def extractTimestamp(element: SensorReading): Long = element.timestamp * 1000
      })

    val resDStream = dataStream.keyBy(_.id)
      // 监控温度传感器的温度值,将温度值低于32F的温度输出到side output
      .process(new FreezingMonitor())

    resDStream.print("process data")
    // 侧输出流
    resDStream.getSideOutput(new OutputTag[String]("freezing-alarms")).print("SideOutput")

    env.execute("SideOutput")
  }
}

// 定义样例类,传感器id,时间戳,温度
case class SensorReading(id: String, timestamp: Long, temperature: Double)

class FreezingMonitor() extends ProcessFunction[SensorReading, SensorReading] {
  // 定义一个侧输出标签
  lazy val freezingAlarmOutput = new OutputTag[String]("freezing-alarms")

  override def processElement(value: SensorReading, ctx: ProcessFunction[SensorReading, SensorReading]#Context, out: Collector[SensorReading]): Unit = {
    // 温度在32F以下时,则在侧输出流输出警告信息
    if (value.temperature < 32.0) {
      ctx.output(freezingAlarmOutput, s"Freezing Alarm for ${value.id}")
    }

    // 所有数据直接床柜输出到主流
    out.collect(value)
  }
}

7.4 CoProcessFunction

对于两条输入流,DataStream API提供了CoProcessFunction这样的low-level操作

CoProcessFunction提供了操作每一个输入流的方法:processElement1()和processElement2()

类似于ProcessFunction,这两种方法都通过Context对象来调用

这个Context对象可以访问事件数据,定时器时间戳,TimerService,以及side outputs

CoProcessFunction也提供了onTimer()回调函数

八、状态编程和容错机制

你可能感兴趣的:(Flink,flink,大数据)