Apache Flink
Apache Flink
Apache Flink是一个为有状态计算(stateful computation)设计实现的分布式计算引擎。支持在常见的集群形式/集群管理器(Hadoop YARN, Apache Mesos, Kubernetes等)上运行,面向内存级计算速度以及任意尺度扩展设计。
优点:
- “精确一次”语义(Exactly Once)
- 有状态流计算(Stateful)(多种灵活的窗口等)
- ……
支持的流可以是有界的(bounded)或无界的(unbounded)。
Flink支持的常见类型应用:事件驱动型应用、数据分析应用(实时的有状态或无状态流计算、批量计算)、管道型应用。
事件驱动型设计,数据和计算不会分离,即计算程序从本地(内存或磁盘)访问数据。系统容错性实现于框架通过定期向远程的持久存储写入检查点(checkpoint)。
检查点(checkoutpoint)机制,TODO功能作用。检查点相关工作以异步、增量方式进行,对业务工作影响甚微。
DataStream API (bounded/unbounded streams)
DataSet API (bounded data sets)
Table API (SQL)
代码可在Table和 DataStream/DataSet间无缝转换。
Flink程序的基石是数据流(streams)和转换操作(transformations)。转换操作即是将一个或多个流作为输入,产出一个或多个流作为结果输出的过程。
数据流程(dataflow)形成任意的有向无环图(DAG)。
数据流程始于一个或多个source,止于一个或多个sink。
并行数据流程 Parallel Dataflow
数据流分区(stream partitions);算子子任务 operator subtasks,算子并发度 operator parallelism
窗口 Windows
窗口可以是基于时间的,或是基于数据的。时间窗口指在一定时间范围内的一个窗口期。数据窗口指一定的连续数据小集合。
窗口分为滑动窗口(sliding window,窗口间可有交叠数据)、滚动窗口(tumbling windows,无交叠)、会话窗口(session window,TODO窗口跨度占据一段长度的连续事件流,但其中有部分数据不属于窗口,即属于窗口的数据是不连续的,可有间隙,不属于窗口的数据即是非活动性数据,而间隙前后的数据属于同个窗口,这种窗口是会话窗口)。
时间 Time
- 事件时间 event time 事件在被创建时的时间。
- 摄入时间 ingestion time 事件被摄入flink source的时间。
- 处理时间 processing time 一个基于时间的操作在处理时的时间。
有状态操作 Stateful Opertions
涉及状态的操作,只能存在于带键流中。
容错 Fault Tolerance
用流重放(stream replay)和检查点机制(checkpointing)来实现程序容错。
流上批次 Batch on Streaming
批处理只能在有界流上进行。
批处理程序的容错不会使用检查点机制,而是通过完全重放数据流实现。
分布式运行时环境 Distributed Runtime Evironment
JobManagers(masters), 有至少1个作业管理器,其中一个是领导者(leader),其余是后备者。
TaskManagers(workers)任务管理器。至少1个。
保存点 savepoints。
编码Flink程序
flink核心相关包,如flink-streaming-scala
, flink_scala
, scala-library
,在应用程序开发过程中需要被用到,但不应该被打包到应用的最终程序包中。因为这些包已经在flink框架被提供,再次包含到应用程序包中会使得程序包包含多余的依赖,还有可能导致这些核心包因版本冲突而无法运行应用程序。以maven作为构建工具时,声明核心相关包依赖时,其scope应该被设为provided
,这样就不会在最终程序包中包含相关依赖。
算子(operator):改变程序状态或转换数据的过程,或其相关的定义者,如函数、类等。
DataSet & DataStream:
DataSet
和DataStream
是表示输入数据的类,可以认为其是可以包含重复元素的不可变collection(immutable collections)。DataSet
表示有限的collection,DataStream
可以是无限的。
编码Flink程序的一般步骤:
- 创建执行环境 execution environment (
getExecutionEnvironment()
), - 加载初始数据 initial data (sources),
- 定义转换操作 transformations,
- 定义结果保存 results (sinks),
- 触发程序执行 execution (
env.execute()
)
StreamExecutionEnvironment
中:
getExecutionEnvironment() //一般只需用到这个
createLocalEnvironment()
createRemoteEnvironment(host: String, port: Int, jarFiles: String*)
Flink程序的执行是惰性的,所有的操作(sources, transformations, sinks)接口在被调用时实际未被执行,只是被加入到执行计划中,程序会在执行环境调用.execute()
时被执行。
指定键 Specifying Key:
Flink中,存在带键的流(通过.groupBy(),keyBy()
等生成),普通流也就分为带键流(keyed stream)和无键流(non-keyed stream),flink的数据模型并非基于“键-值”对,故流的键是虚拟的,仅在某些算子上有帮助。
将普通流转换为带键流(KeyedStream
, KeyedDataSet
)时需要指定键。
DataSet.groupBy()
, DataStream.keyBy()
等接口需要指定键。
- 整型参数
def (Int*)
一般针对元素类型为元组的流(DataSet
或DataStream
),参数为元组中元素的索引(基于0始偏移)。 - 字符串参数
def (String, String*)
参数为对象的属性名,其中可用点号.
访问内嵌对象。 - 函数参数
def (T=>K)
元素到键的映射函数。
整型参数和字符串参数的形式中,可指定多个值,表示组合键。
转换函数:
Lambda表达式,或者MapFunction[T,V]
,或其子类型RichMapFunction
(具有富函数特征,即感知运行时上下文RuntimeContext
)。在引入Scala API扩展后,可使用偏函数等。
流元素的数据类型
Flink对DataSet
、DataStream
中元素的类型有限制,受支持的几种数据类型如下:
- Java Tuples and Scala Case Classes:java元组指Flink提供的元组类,在包
org.apache.flink.api.java.tuple
下;scala元组本身是scala case class。 - Java POJOs
POJO规范:class必须是public
的;所有字段是public
的,或者private
但此时其必须有对应的java bean风格的getter&setter(getXxx,setXxx);有public
的无参构造函数; 此外,字段应可被flink序列化;
POJO类在Flink中以PojoTypeInfo
表示,以PojoSerializer
序列化。可注册自定义序列化器。
Flink能更高效处理POJO类,相比非POJO的普通类,因知道POJO的字段信息。 - Primitive Types
- Regular Classes 普通类,除开POJO的class。无法在一些操作上高效执行,因没有类的结构信息。用Kryo框架进行序列化/反序列化。
- Values (
org.apache.flinktypes.Value
)
自定义序列化和反序列化过程,通过实现方法void write(DataOutputView)
和void read(DataInputView)
。其中,若还需自定义拷贝方法的,需利用Value
接口的子接口org.apache.flinktypes.CopyableValue
。
基本数据类型(int,boolean等)有对应的Value,Flink已将其预定义在框架中,它们被设计为可变的(mutable),以便重用,以减轻JVM垃圾回收的压力。 - Hadoop Writables
org.apache.hadoop.Writable
- Special Types 特殊类型,如scala中的
Try
,Option
,Flink Java API中有自己对应的实现。
在Flink Java API中,某些接口需要编码者显式地告知Flink数据类型信息,这是因为Flink需要知道流的数据类型信息,而Java泛型会擦除类型信息。
Scala API 扩展:
// for DataSet API
import org.apache.flink.api.scala.extensions._
// for DataStream API
import org.apache.flink.streaming.api.scala.extensions._
函数名一般具有形如xxxWith()
的with后缀,具有方法xxx()的语义,如mapWith(), flapMapWith()分别具有map(), flatMap()的语义。
累加器 Accumulator
完成累加过程的操作者,主要有“加(add)”操作和累加结果两个要素,结果值在Flink作业结束后才有效。
Flink内置的累加器:
- 计数器(counters)
IntCounter
,LongCounter
,DoubleCounter
- 直方图(Hitogram) 关于一系列数据的离散分布图。在Flink底层为数据元素到整型的映射表。
使用累加器:
- 将累加器添加到环境
val acc = new IntCounter()
getRuntimeContext().addAccumulator(name="my_acc", acc);
- 调加操作
add(V value)
acc.add(1)
- 获取结果,在作业结束之后。
jobResult.getAccumulatorResult(name="my_acc")
累加器的名字作用在整个作业(Job)域,所以一个作业内不同的操作函数处可获取和使用同一个累加器,Flink将会合并其结果。
累加器接口Accumulator<V,R>
,是一个泛型,类型参数V
代表要被“加”(add(·)
)操作的元素的类型,类型参数R
代表(每次以及最终)累加结果类型。如Histogram
的V实参为整型,而R的即为Histogram
,因其每次加都应返回直方图,直至最终得到结果直方图;如IntCounter
,V和R的实参均为整型。
对于操作元素和累加结果的类型相同的,如各种计数器(counters),可实现接口SimpleAccumulator
。
maven依赖:
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-scala_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-scala_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
<scope>provided</scope>
</dependency>
流 DataStream API
数据源 Data Sources (输入)
readTextFile(path)
readFile(fileInputFormat, path)
readFile(fileInputFormat, path, watchType, interval, pathFilter)
socketTextStream(host, port)
fromCollection(Seq)
fromCollection(Iterator)
fromElements(elements: _*)
fromParallelCollection(SplittableIterator)
generateSequence(from, to)
addSource(.)
自定义。如读取kafka数据addSource(new FlinkKafkaConsumer08<>(...))
。
数据沉积 Data Sinks (输出)
addSink(.)
print()
,printToErr()
用来打印数据到标准输出流或标准错误流。
方法writeAsText() / TextOutputFormat
, writeAsCsv(...) / CsvOutputFormat
, writeUsingOutputFormat() / FileOutputFormat
, writeToSocket()
,应该仅用于测试,其不会保证精确一次语义,不参与检查点机制。
时间 Time
事件时间、处理时间、(数据)摄入时间。
设置应用程序中时间的类型:
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
事件时间 Event Time
事件被创建时的时间。
水印(Watermark) 事件消息队列中的事件可能是乱序的,或者其序并非是事件创建时间序。水印是一个事件时间戳,可被认为是数据流中一个特殊的消息数据,关于时间戳t的水印W(t)
消息,表示数据流中此后的事件的时间一定晚于t
,即读取到水印消息时,表明事件时间早于或等于t(≤t
)的事件已经被读完。
水印的生成:TODO
延迟元素:关于时间戳t的水印出现了,但其后还有时间小于t的事件。
周期性水印(Periodic Watermark),在以事件创建时间为序的流中,在事件时间轴上周期性地取时间戳即可作为水印。
类AssignerWithPeriodicWatermarks
用以定义周期性水印的生成逻辑。
//官方例子
/**
* This generator generates watermarks assuming that elements arrive out of order,
* but only to a certain degree. The latest elements for a certain timestamp t will arrive
* at most n milliseconds after the earliest elements for timestamp t.
*/
class BoundedOutOfOrdernessGenerator extends AssignerWithPeriodicWatermarks[MyEvent] {
val maxOutOfOrderness = 3500L // 3.5 seconds
var currentMaxTimestamp: Long = _
override def extractTimestamp(element: MyEvent, previousElementTimestamp: Long): Long = {
val timestamp = element.getCreationTime()
currentMaxTimestamp = max(timestamp, currentMaxTimestamp)
timestamp
}
override def getCurrentWatermark(): Watermark = {
// return the watermark as current highest timestamp minus the out-of-orderness bound
new Watermark(currentMaxTimestamp - maxOutOfOrderness)
}
}
/**
* This generator generates watermarks that are lagging behind processing time by a fixed amount.
* It assumes that elements arrive in Flink after a bounded delay.
*/
class TimeLagWatermarkGenerator extends AssignerWithPeriodicWatermarks[MyEvent] {
val maxTimeLag = 5000L // 5 seconds
override def extractTimestamp(element: MyEvent, previousElementTimestamp: Long): Long = {
element.getCreationTime
}
override def getCurrentWatermark(): Watermark = {
// return the watermark as current time minus the maximum time lag
new Watermark(System.currentTimeMillis() - maxTimeLag)
}
}
间接性水印(Punctuated Watermarks),借助类AssignerWithPunctuatedWatermarks
来实现生成逻辑。
//官方例子
class PunctuatedAssigner extends AssignerWithPunctuatedWatermarks[MyEvent] {
override def extractTimestamp(element: MyEvent, previousElementTimestamp: Long): Long = {
element.getCreationTime
}
override def checkAndGetNextWatermark(lastElement: MyEvent, extractedTimestamp: Long): Watermark = {
if (lastElement.hasWatermarkMarker()) new Watermark(extractedTimestamp) else null
}
}
时间戳是整型,为epoch milliseconds。
事件时间和水印的生成:
def run(ctx: SourceContext[Element]) : Unit = {
...
//为事件打时间戳
ctx.collectWithTimestamp(element, timestamp:Long)
//生成水印
ctx.emitWatermark(new Watermark(next.getWatermarkTime)
}
状态 State
状态从有无键分为带键状态(keyed state)和算子状态(无键状态)(operator state),从是否受Flink管理分为受管状态(managed state)和原生状态(raw state)。
带键状态(Keyed State):始终与键关联,仅可用于带键流(KeyedStream
)。
算子状态(Operator State),即指无键状态(non-keyed state),区别于带键状态。
用以保存Flink状态的存储系统可以在Java堆(heap)或堆外(off-heap)内存,Flink可以自己去管理这些内存。
受管状态(managed state):受Flink管理,状态数据存放于受Flink运行时管理的数据结构内。
原生状态(raw state),未受Flink管理的状态,数据存放于算子的数据结构中。
受管带键状态 Managed Keyed States
受管带键状态(managed keyed states)类:
ValueState<T>
带键流中,一个键只有一个值。更新update(T)
;取值value():T
。ListState<T>
元素列表。添加元素add(T)
、addAll(List<T>)
;获取所有元素get():Iterable[T]
;用新列表替换掉当前列表update(List<T>)
。ReducingState<T>
将若干元素归约为一个值。在每次add(T)
时会将之前归约的值与此归约形成新的归约值。AggregatingState<IN, OUT>
MapState<UK, UV>
键值对状态。
状态有方法clear()
,用以取消与键的关联,清除状态数据。
状态描述符StateDescriptor
,用以描述状态的定义,此外,在读取状态时需要,它保存了状态的名字,其被用以在运行时上下文中查找状态。
状态的读取是利用RuntimeContext
实现,富函数(rich functions)(接口RichFunction
)具有获取运行时上下文(RuntimeContext
)的方法,故通过RichFunction
及其众多子类(如RichFlatMapFunction
等)来实现对状态的访问。
RuntimeContext
中用以提供状态读取的方法:
ValueState<T> getState(ValueStateDescriptor<T>)
ReducingState<T> getReducingState(ReducingStateDescriptor<T>)
ListState<T> getListState(ListStateDescriptor<T>)
AggregatingState<IN, OUT> getAggregatingState(AggregatingStateDescriptor<IN, ACC, OUT>)
MapState<UK, UV> getMapState(MapStateDescriptor<UK, UV>)
例子,关于状态读取及有状态转换函数:
//官方例子
class CountWindowAverage extends RichFlatMapFunction[(Long, Long), (Long, Long)] {
private var sum: ValueState[(Long, Long)] = _
override def flatMap(input: (Long, Long), out: Collector[(Long, Long)]): Unit = {
// access the state value
val tmpCurrentSum = sum.value
// If it hasn't been used before, it will be null
val currentSum = if (tmpCurrentSum != null) {
tmpCurrentSum
} else {
(0L, 0L)
}
// update the count
val newSum = (currentSum._1 + 1, currentSum._2 + input._2)
// update the state
sum.update(newSum)
// if the count reaches 2, emit the average and clear the state
if (newSum._1 >= 2) {
out.collect((input._1, newSum._2 / newSum._1))
sum.clear()
}
}
override def open(parameters: Configuration): Unit = {
sum = getRuntimeContext.getState(
new ValueStateDescriptor[(Long, Long)]("average", createTypeInformation[(Long, Long)])
)
}
}
object ExampleCountWindowAverage extends App {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.fromCollection(List(
(1L, 3L),
(1L, 5L),
(1L, 7L),
(1L, 4L),
(1L, 2L)
)).keyBy(_._1)
.flatMap(new CountWindowAverage())
.print()
// the printed output will be (1,4) and (1,5)
env.execute("ExampleManagedState")
}
状态TTL
状态有TTL,所有类型的状态类都支持条目独自TTL(per-entry TTL,指在如ListState,MapState中个个元素/键值对有自己独立的TTL)。
状态TTL设置于状态描述符,StateDescriptor.enableTimeToLive(StateTtlConfig)
。
更新策略:
StateTtlConfig.UpdateType.OnCreateAndWrite
- 仅在创建及写入时(默认策略)。StateTtlConfig.UpdateType.OnReadAndWrite
- 读写(创建属于一种写入)。
过期状态的可见性:在访问时状态时,对于超过TTL的过期值(expired)是否仍需被返回的配置。过期值只有在还未被清理(not cleaned up)的情况下才有可能被返回。
可见性策略:
StateVisibility.NeverReturnExpired
(默认)StateVisibility.ReturnExpiredIfNotCleanedUp
返回过期值,若其还未被清理。
val ttlConfig = StateTtlConfig
.newBuilder(Time.seconds(1)) //TTL时长
.setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite) //更新策略
.setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired) //过期值可见性策略
.build
过期状态的清理:
cleanup in full snapshot: TODO
cleanup in background: TODO
cleanup during RocksDB compaction: TODO
incrementale cleanup 增量式清理: TODO
通过恢复而来的状态,如果其TTL特性的是否启用与恢复前不一致,则会导致兼容性失败问题(compatibility failure)而抛出状态迁移异常StateMigrationException
。
TTL目前(flink 1.9.0)仅受支持于关乎处理时间(processing time)的类型(字段StateTtlConfig.ttlTimeCharacteristic
)。
Scala API中的带键流KeyedStream
为部分方法提供了有状态操作版本(stateful functions),如filterWithState(...)
,mapWithState()
,flatMapWithState()
。
受管算子状态 Managed Operator States
TODO
接口CheckpointedFunction
:
public interface CheckpointedFunction {
void snapshotState(FunctionSnapshotContext var1) throws Exception;
void initializeState(FunctionInitializationContext var1) throws Exception;
}
有状态的数据源函数 Stateful Source Functions:
类RichSourceFunction<OUT>
。
容错
TODO
算子 Operators
流转换操作
map()
flatMap()
filter()
keyBy()
DataStream -> KeyedStream
reduce()
KeyedStream -> DataStream
聚合操作(aggregations) min(.) max(.) minBy(.) maxBy(.) sum(.)
KeyedStream -> DataStream
windowAll(.)
DataStream -> AllWindowedStream 这是一个非并行操作。
Window Apply apply(WindowFunction)
WindowedStream -> DataStream
All Window Apply apply(AllWindowFunction)
AllWindowedStream -> DataStream
Window Reduce reduce(.)
WindowedStream -> DataStream
物理分区
partitionCustom(.)
, shuffle()
, rebalance()
, rescale()
, broadcast()
任务链&资源组
startNewChain()
, disableChaining()
, slotSharingGroup(.)
窗口 Windows
Keyed Windows:关乎键的窗口,从带键流(KeyedStream
)调用窗口化函数.window(.)
而来。
从数据流到keyed windows的代码的一般形式为:
val stream:DataStream[_] = ...
stream
.keyBy(...) // -> KeyedStream
.window(...) // -> WindowedStream
Non-Keyed Windows:没有关乎键,从无键流调用.windowAll(.)
而来。代码的一般形式:
val stream:DataStream[_] = ...
stream
.windowAll(...) // -> AllWindowedStream
带键流允许窗口性计算以并行方式进行,键间任务是并行的,一个键的所有元素将被送往同个并发任务。
无键流中的窗口性操作不会并行。
窗口将在(属于本窗口的)元素到来时被创建。将在窗口的生存条件完结后(如时间区间窗口的生存时长结束后)以及加上定义的延迟时间结束后,被移除。
每个窗口有个触发器(Trigger)和窗口函数(Window Function),函数定义了窗口中元素会被执行的操作,触发器定义了窗口中操作的触发条件。
回收器(Evictor):其定义了窗口中元素在窗口附属函数处理完毕后被回收的行为。
窗口分配器 Window Assigners
窗口分配器(Assigner):其定义了如何将 来临的(incoming) 元素分配到一个或多个窗口。
内置窗口分配器 tumbling windows, sliding windows, session windows and global windows
可自定义。
内置窗口分配器,除开global,都是基于时间的,时间可以是处理时间或事件时间。
基于时间的窗口,对应类TimeWindow
,基于时间的一个窗口,其有起止时间戳,左闭右开区间[start, end)
,表示覆盖的时间戳范围。
滚动窗口 Tumbling Windows:所有窗口时间区间长度一致为一个固定值,窗口间无重叠无空隙。
//基于处理时间
TumblingEventTimeWindows.of(size:Time/*窗口跨度*/, offset:Time/*时间校准偏移量,默认0*/)
//时间校准偏移量指的是,将目标时间(如事件创建时间)调整为UTC+0时需要的校准值,而不是将UTC调整为目标时区的值。如,若元素时间当初以UTC+8写入的,此时传入的参数应为-8小时 Time.hours(-8)。
//基于事件时间
TumblingProcessingTimeWindows.of(size, offset)
滑动窗口 Sliding Windows:所有窗口时间区间长度一致为一个固定值,窗口间允许有重叠。
//参数`slide`指被重叠的时间区间长度,并非前个窗口起始与下个窗口起始的间距
SlidingEventTimeWindows.of(size, slide)
SlidingEventTimeWindows.of(size, slide, offset=0)
会话窗口 Session Windows:以连续“活动的”元素组成一个会话,窗口间无重叠,无固定的起止时间。所谓“活动的”,即两个连续元素间的时间间隔不超过某个值,若超过一定值前面元素即是窗口的结束元素,前面的连续活动元素形成一个会话窗口。会话窗口间的间隙(gap)可以是固定值(static gap),可以由间隙提取器(gap extractor)指定。在Flink底层,会话窗口的形成过程是,每个元素到达时为之创建一个窗口,然后通过合并时间间隔小于会话窗口间隙的窗口最终形成一个会话窗口。
全局窗口 Global Windows:对于KeyedStream
,将该键下所有元素组到一个窗口,称为关于对应键的全局窗口。全局窗口需要被指定触发器才会被Flink执行其中的操作计算,默认触发器是NeverTrigger
,其不会触发计算。
创建例子:
val stream: DataStream[_] = ...
stream.keyBy(<key selector>)
.window(GlobalWindows.create())
窗口函数 Window Functions
窗口函数是用来作用在窗口内元素的函数。
窗口函数有:
- 归约函数
ReduceFunction
- 给定同种类型的两个值使其归约为一个值,也是同种类型的。纵观窗口内所有元素,归约函数会使得所有元素最终被归约到一个值(如果窗口内至少有一个元素)。 - 聚合函数
AggregateFunction
- 定义了中间状态并入一个元素的行为、中间状态间的合并行为、从中间状态取出输出值的行为。这里的中间状态即是累加器(accumlator)。纵观窗口内所有元素,聚合输出的实现是,首先创建多个空的中间状态(多个是因为被实现为并发进行的),然后每个中间状态对各自所辖的若干元素一个个地并入(.add(.)
),之后将所有中间状态合并(.merge()
)为一个,最终取出输出值(.getResult()
)。归约属于聚合的一种。 - 窗口处理函数
ProcessWindowFunction
- 能够获取窗口内所有元素,以及窗口的元信息(meta-data),如时间、状态。该函数能够更自由灵活处理窗口元素,以性能为代价因其无法以增量式进行。
带增量聚合的窗口处理:…… aggregate(AggregateFunction, ProcessWindowFunction)
, reduce(ReduceFunction,ProcessWindowFunction)
触发器 Triggers
触发器决定了窗口计算的流程控制。
触发器会接收两种信号 fire和purge。
发射 fire:表示窗口已准备好,可进行计算。
清洗 purge:清洗窗口内的内容,不会清除窗口和触发器的状态。
触发结果 TriggerResult:
枚举TriggerResult
:
CONTINUE
- 均不做fire和purge。FIRE_AND_PURGE
- 都做fire和purge。FIRE
- 仅firePURGE
- 仅purge
内置的触发器类:EventTimeTrigger
, ProcessingTimeTrigger
, ContinuousEventTimeTrigger
, ContinuousProcessingTimeTrigger
, CountTrigger
, DeltaTrigger
, PurgingTrigger
.
回收器 Evictors
回收器可以控制窗口内元素的移除,在触发器fire后且于窗口函数应用之前/之后/之前后。
public interface Evictor<T, W extends Window> extends Serializable {
// Called before windowing function.
void evictBefore(Iterable<TimestampedValue<T>> elements, int size, W window, EvictorContext evictorContext);
// Called after windowing function.
void evictAfter(Iterable<TimestampedValue<T>> elements, int size, W window, EvictorContext evictorContext);
内置的回收器类: CountEvictor
, TimeEvictor
, DeltaEvictor
.
延迟
在与基于事件时间的窗口打交道时,延迟于水印时间戳之后的元素默认被丢掉,除非为窗口算子指定允许的延迟时长(即默认允许延迟为0)。
一个延迟(但未被丢弃)的元素进入窗口后,可能导致触发器再次fire,这取决于所用触发器是如何定义这种行为的。EventTimeTrigger
会导致再次fire。
设置允许延迟的方法:WindowedStream.allowedLateness(lateness:Time)
。
可将延迟元素收集为数据流程的一个副输出(side output)。WindowedStream.sideOutputLateData(.)
。
连接 Joining
可以连接两个数据流(DataStream
)为一个联结流(JoinedStream[T1,T2]
),通过调用其中一个流的.join(DataStream)
方法。
流联结后,涉及窗口的联结、区间的连接。
窗口的连接 Window Join
滚动窗口的连接:
滑动窗口的连接:
会话窗口的连接:
时间区间连接 Interval Join
流的处理函数 Process Function
这是一个底层接口用到函数类型参数。.process(ProcessFunction)
异步I/O Async I/O
流的副输出 Side Outputs:除了流经过若干转换、计算操作导出的主输出,还可在一些条件下额外输出数据,这些是流的副输出。
定义副输出前,需要先为其定义OutputTag
,以便程序标识它。
批 DataSet API
TODO
Flink与Spark的批处理:
Flink的批处理(batch on streaming)与Apache Spark的小批次处理(Spark streaming mini-batch),两者均能处理批数据,也都能保证精确一次(Exactly-Once)语义,它们不同之处在于:
- Flink批处理和Spark批处理实现方式不同。Flink将批和流的处理统一了起来,流上批处理出一直伴随流进行的,并非将批转为流来实现。Spark的处理方式是在流中收集元素为批,然后调度一个作业以处理批数据。Flink的延迟在毫秒级,Spark批处理延迟在秒级。
- 精确一次(Exactly-Once)语义实现原理不同。(……详述……)
Table API & SQL
TODO
部署
从下载页面选择最新版flink程序包下载到本地。
#解压程序包
# cd <程序包所在目录>
tar xzvf flink-*.tgz # flink-1.9.0-bin-scala_2.12
cd flink-* # cd flink-<版本号>/
./bin/start-cluster.sh # 启动本地模式的flink
如果成功启动,在本机的8081端口有一个web程序监控flink状态,即地址 http://127.0.0.1:8081 。
示例代码
见 gitee:xmaples/flink-exmaples (https://gitee.com/xmaples/flink-examples)
知识主要基于版本1.9.0。在版本1.9.0中的接口还不太稳定,若干接口被标注为"演进中"`@PublicEvolving`,若干接口被弃用。Flink官网文档的更新有部分没有跟上代码的更新,代码中被弃用的知识部分仍还在官网文档中,本文档已尽力跟上代码。