窗口(Window)

本文翻译自文档Windows

-----------------------------------

Flink使用窗口的概念,根据element的时间戳或者其他指标,将可能无限的DataStream分割为有限的数据切片(slice)。我们在处理无限数据流以及进行聚合element的transformation时需要此种窗口分割。

注意:我们在此文档中讨论的大多是keyed windowing,即window是应用在KeyedStream上的。关键字下的窗口具有一定的优势,即它可以在element传递给user function之前就能按照window和关键字共同二次分割element。由于不同关键字的element可以相互独立处理,所以该工作可以在cluster之上分布式进行。有关non-keyed window的信息,请查看文档non-keyed windowing

一、基础部分

一个带窗口的transformation至少需要定义一个key(见文档specifying keys)、一个window assigner以及一个window functionkey将无限而无关键字的流分割成逻辑的有关键字数据流,而window assigner将element赋值给有限的各自关键字的窗口(per-key window)。最后window function会用于处理每个窗口的element。

带窗口transformation的基础结构如下所示:

DataStream<T> input = ...;

input.keyBy(<key selector>)
  .window(<window
assigner>)
  .<windowed transformation>(<window
function>);

我们会在接下来的一节中单独讲window assigners

Window
transformation可以是reduce()fold()或者apply()之一,它们相对应的需要一个ReduceFunctionFoldFunctionWindowFunction。我们将会在下文window functions中具体描述定义一个带窗口transformation的不同方式。

在更进一步的用例中,你可以定义一个Trigger来决定一个窗口什么时候才是ready for
processing
的。相关详细内容见于本文triggers小节。

二、Window
Assigners

Window
assigner定义了数据流的element将如何分割进有限的数据切片。Flink自带预先实现了针对多数典型用例的window assigner,以tumbling
window
sliding
window
session
window
global
window
命名,此外,你还可以通过继承WindowAssigner类来自定义自己的window
assigner。除了global
window,所有自带window
assigner都是基于时间(可以是processing
time或者event time)来分配element。有关Flink如何处理时间,请见文档event time

在描述这些window
assigner如何用于Flink程序之前,我们先描述它们的工作机制。我们将使用抽象图来可视化每个assigner的工作机制:在下面的内容中,紫色圈是数据流的element,它们以不同的关键字进行分割(在该例中关键字为user1,
user2, user3),x轴表示时间的进展。

2.1Global Windows

Global
Window的定义表明我们不会进一步将element二次分割到窗口中。每个element将被分配到一个单独的各自关键字的窗口中。该窗口化模式仅仅在同时拥有一个自定义trigger时才有用。否则由于global
window没有用以聚合element的常态结束,所以不会发生任何计算。

2.2 Tumbling Window

Tumbling
window

assigner将element分配到一个固定长度、无重叠的窗口,且该窗口的window
size有用户定义。例如,如果你定义window
size为5分钟,window
function每次调用都会得到5分钟的element。

2.3 Sliding Windows

Sliding
window

assigner和tumbling
window一样,将element分配到一个固定长度(等于window
size),但该窗口可重叠,重叠的大小由用户定义的参数window
slide定义。由于窗口可重叠,故一个element可以分配到多个窗口中去。

例如,你可以定义一个window
size为10分钟,且slide为5分钟。在该窗口中,每此window
function会得到10分钟的element,且5分钟调用一次。

2.4 Session Windows

Session window assigner在窗口边界需要根据到达数据调整的情况下十分适用。Tumbling
windows和sliding
windows的assigner都将element分配到开始于固定时间点并且拥有固定window
size的窗口中。而在session中,你可以让关键字窗口开始于它们自己的时间点,并且在一段无活动情况(inactivity)出现时结束窗口。该窗口的配置参数session
gap
定义了等待新数据多长时间就结束一个session。

2.5 定义一个Window
Assigner

除了GlobalWindows,内置window
assigner都有两个版本,一个处理processing-time
windowing,另一个处理event-time
windowing。Processing-time
assigner根据worker设备的当前时钟来分配element,而event-time
assigner根据element的时间戳来分配窗口。有关processing
time和event time的区别以及如何给element分配时间戳的内容,请见文档event time

下面的代码片段展示了在程序中如何使用每个window assigner:

DataStream<T>
input = ...;

//
tumbling event-time windows

input
  .keyBy(<key
selector>)
  .window(TumblingEventTimeWindows.of(Time.seconds(5)))
  .<windowed transformation>(<window
function>);

//
sliding event-time windows

input
  .keyBy(<key
selector>)
  .window(SlidingEventTimeWindows.of(Time.seconds(10),
Time.seconds(5)))
  .<windowed transformation>(<window
function>);

//
event-time session windows

input
  .keyBy(<key
selector>)
  .window(EventTimeSessionWindows.withGap(Time.minutes(10)))
  .<windowed transformation>(<window
function>);

//
tumbling processing-time windows

input
  .keyBy(<key
selector>)
  .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
  .<windowed transformation>(<window
function>);

//
sliding processing-time windows

input
  .keyBy(<key
selector>)
  .window(SlidingProcessingTimeWindows.of(Time.seconds(10),
Time.seconds(5)))
  .<windowed transformation>(<window
function>);

//
processing-time session windows

input
  .keyBy(<key
selector>)
  .window(ProcessingTimeSessionWindows.withGap(Time.minutes(10)))
  .<windowed transformation>(<window
function>);

//
global windows

input
  .keyBy(<key
selector>)
  .window(GlobalWindows.create())
  .<windowed transformation>(<window
function>);

注意:我们可以通过Time.millisecond(x)Time.second(x)Time.minutes(x)等等方法来定义时间。

三、Window
Functions

在系统确定一个窗口已经做好了处理的准备时(有关系统是如何确定窗口可处理,见文档trigger),Flink将使用Window
Function处理每个窗口中的element。

Window
Function可以是ReduceFunctionFlodFunctionWindowFunction。由于Flink在element到达各自窗口时递增地聚合它们,所以前两个方法可以更加高效地执行。WindowFunction获取窗口中所有element的Iterable以及这些element所属的窗口的额外元信息(meta
information)。

由于Flink在调用使用了WindowFunction的窗口化transformation之前,必须在内部缓存一个窗口中所有element,所以此种transformation无法像其他情况一样高效执行。我们可以将WindowFunction和一个ReduceFunctionFoldFunction相结合,从而在递增聚合窗口中element的同时,还可以获取WindowFunction接收的额外信息,通过这种方式,我们可以缓解上述问题。我们在下面会对各种情况一一举例。

3.1 ReduceFunction

一个reduce方法定义了两个值如何结合形成一个element。Flink可以使用它来递增地聚合窗口中的element。

程序中的ReduceFunction如下例:

DataStream<Tuple2<String,
Long>> input = ...;

input.keyBy(<key
selector>)
  .window(<window
assigner>)
  .reduce(new
ReduceFunction<Tuple2<String, Long>> {
    public Tuple2<String,
Long> reduce(Tuple2<String,
Long> v1, Tuple2<String, Long> v2)
{

      return new Tuple2<>(v1.f0,
v1.f1 + v2.f1);
    }
  });

一个ReduceFunction定义了两个输入中的element是如何结合产生一个输出element的。在上面的例子中,将会计算出一个窗口中所有element的第二个域的总和。

3.2 FoldFunction

一个fold方法可以定义如下:

DataStream<Tuple2<String,
Long>> input = ...;

input
  .keyBy(<key
selector>)
  .window(<window
assigner>)
  .fold("",
new
FoldFunction<Tuple2<String, Long>,
String>> {
    public String fold(String
acc, Tuple2<String, Long> value) {
      return acc + value.f1;
    }
  });

一个FoldFunction定义了输入中的element如何加上一个累加初始值(在本例中为"",即空字符串)。在上例中,将会计算出输入中所有Long域的字符串连接(concatenation)结果。

3.3 WindowFunction - 一般情况

WindowFunction以性能开销的增加,换来了最大的灵活性(它可以获得key和Window的引用)。WindowFunction由于无法递增聚合窗口中的element,在窗口准备好处理之前,Flink都必须内部缓存整个窗口,所以带来性能上的开销。一个WindowFunction将得到处理的窗口中所有element的Iterable。WindowFunction接口的签名如下所示:

public
interface
WindowFunction<IN,
OUT, KEY, W extends Window> extends Function,
Serializable {

  /**
  * Evaluates the window
and outputs none or several elements.

  *
  * @param key The key
for which this window is evaluated.

  * @param window The
window that is being evaluated.

  * @param input The
elements in the window being evaluated.

  * @param out A
collector for emitting elements.

  *
  * @throws Exception The
function may throw exceptions to fail the program and trigger recovery.

  */
  void apply(KEY
key, W window, Iterable<IN> input,
Collector<OUT> out) throws Exception;
}

下面我们举例使用WindowFunction来计算一个窗口中element数量。我们之所以选择WindowFunction,是因为我们想在发送计数的同时,还想访问并一同发送有关窗口的信息。这将非常低效,我们应当在练习中和一个ReduceFunction一同实现WindowFunction。在下一节中,我们将会看到将ReduceFunctionWindowFunction结合来获取递增地聚合以及之前添加的WindowFunction的信息

DataStream<Tuple2<String,
Long>> input = ...;

input
  .keyBy(<key
selector>)
  .window(<window
assigner>)
  .apply(new
MyWindowFunction());

/*
... */

public
class
MyWindowFunction implements
WindowFunction<Tuple<String, Long>, String,
String, TimeWindow> {

void
apply(String
key, TimeWindow window, Iterable<Tuple<String,
Long>> input, Collector<String> out)
{

  long
count = 0;
  for (Tuple<String,
Long> in: input) {
    count++;
  }
  out.collect("Window:
" + window + "count:
" + count);
}

}

3.4 带有递增聚合的WindowFunction

一个WindowFunction可以与ReduceFunction或者FoldFunction结合,在结合后,ReduceFunction/FoldFunction将会用来在窗口的element到达时递增聚合它们,而WindowFunction则会在窗口准备好处理时得到已经聚合后的结果。这种方式使我们可以即获得窗口递增计算的优势,又可以获得编写一个WindowFunction提供的额外窗口元信息。

下面的例子为我们展示了递增聚合方法如何与WindowFunction结合:

DataStream<Tuple2<String,
Long>> input = ...;

//
for folding incremental computation

input
  .keyBy(<key
selector>)
  .window(<window
assigner>)
  .apply(<initial
value>, new MyFoldFunction(),
new
MyWindowFunction());

//
for reducing incremental computation

input
  .keyBy(<key
selector>)
  .window(<window
assigner>)
  .apply(new
MyReduceFunction(), new MyWindowFunction());

四、处理迟到数据

在处理事件时间窗口时,可能会发生element迟到的情况,即Flink用来持续跟踪事件时间进展的watermark已经晚于到达element所属的窗口的结束时间戳了的情况。有关event time和其中迟到element等等Flink如何处理event time的详细讨论,请见event timelate element

你可以定义一个带窗口transformation如何处理迟到element以及允许的迟到时间(lateness)。相关的参数为allowed
lateness,该参数定义了element最多可以迟到多长时间。对于在allowed
lateness之内到达的element,Flink仍然会将它们放入窗口中并且考虑到计算结果之内。而在allowed
lateness之外到达的element则将被抛弃。Flink同样保证一旦Watermark超过窗口结束时间加上allowed
lateness,由窗口Operation持有的所有状态都将进入垃圾回收。

默认地,allowed
lateness设置为0,即在watermark之后到达的element将会被抛弃。你可以通过以下方式定义allow
lateness:

DataStream<T>
input = ...;

input
  .keyBy(<key
selector>)
  .window(<window
assigner>)
  .allowedLateness(<time>)
  .<windowed transformation>(<window
function>);

注意,当使用GlobalWindows的window
assigner时,没有数据会变为迟到数据,因为全局窗口的结束时间戳为Long.MAX_VALUE

五、Triggers

一个Trigger决定着窗口(由WindowAssigner赋值)什么时候准备好由window
function处理。trigger观察element如何加入窗口中,并且持续跟踪processing
time和event time的进展。一旦一个trigger决定窗口已准备好处理,它就会被触发。这是Window
Operation获取当前处于窗口中的数据,并且将它们传递给window
function来产生处于触发状态的(firing)窗口的输出的信号。

除了GlobalWindows,每个WindowAssigner都带有一个默认trigger来适用于绝大多数用例。例如,TumblingEventTimeWindows拥有EventTimeTrigger作为默认trigger,该trigger简单地在watermark超过窗口的结束时间时触发。

你可以通过使用给定Trigger类来调用trigger()方法定义所用的trigger。一个带窗口transformation的全貌大致如下:

DataStream<T>
input = ...;

input
  .keyBy(<key
selector>)
  .window(<window
assigner>)
  .trigger(<trigger>)
  .<windowed transformation>(<window
function>);

Flink自带一些开箱即用的trigger:包括上面提到的EventTimeTrigger,基于由watermark衡量的event time的进展来决定是否触发;还有ProcessingTimeTrigger,与EventTimeTrigger大致一样,但基于processing
time;最后是CountTrigger,在一个窗口的element数量溢出给定界限时触发。

注意,通过使用trigger()方法定义一个trigger,你将重写WindowAssigner的默认trigger。例如,若你为TunmblingEventTimeWindows定义CountTrigger为trigger,窗口将不会基于时间进展触发,而仅仅依靠计数结果来触发。在当前版本下,若你想要同时对时间和计数都做出响应,你只能自定义trigger。

内部Trigger
API在当前版本仍然处于测试阶段,但如果你想要编写自定义trigger,请检出(check
out)该代码。Trigger.java

六、non-keyed
windowing

你同样可以在定义一个带窗口transformation时忽略KeyBy()方法,该方式将使Flink无法并行处理各自不同key的窗口,本质上将transformation变成了一个非并行的Operation。

警告:正如本文开始介绍中提到的,由于non-keyed
windows不能分各自key独立计算,所以有着无法在集群上分布式运行的缺陷,这将带来一些性能上的影响。

一个带non-keyed
window的transformation的基本结构如下代码所示:

DataStream<T>
input = ...;

input
  .windowAll(<window
assigner>)
  .<windowed transformation>(<window
function>);

Flink Program Guide (6) -- 窗口 (DataStream API编程指导 -- For Java)的更多相关文章

  1. Flink Program Guide (2) -- 综述 (DataStream API编程指导 -- For Java)

    v\:* {behavior:url(#default#VML);} o\:* {behavior:url(#default#VML);} w\:* {behavior:url(#default#VM ...

  2. Flink Program Guide (10) -- Savepoints (DataStream API编程指导 -- For Java)

    Savepoint 本文翻译自文档Streaming Guide / Savepoints ------------------------------------------------------ ...

  3. Flink Program Guide (8) -- Working with State :Fault Tolerance(DataStream API编程指导 -- For Java)

    Working with State 本文翻译自Streaming Guide/ Fault Tolerance / Working with State ---------------------- ...

  4. Flink Program Guide (3) -- Event Time (DataStream API编程指导 -- For Java)

    Event Time 本文翻译自DataStream API Docs v1.2的Event Time ------------------------------------------------ ...

  5. Flink Program Guide (7) -- 容错 Fault Tolerance(DataStream API编程指导 -- For Java)

    false false false false EN-US ZH-CN X-NONE /* Style Definitions */ table.MsoNormalTable {mso-style-n ...

  6. Flink Program Guide (5) -- 预定义的Timestamp Extractor / Watermark Emitter (DataStream API编程指导 -- For Java)

    本文翻译自Pre-defined Timestamp Extractors / Watermark Emitter ------------------------------------------ ...

  7. Flink Program Guide (4) -- 时间戳和Watermark生成(DataStream API编程指导 -- For Java)

    时间戳和Watermark生成 本文翻译自Generating Timestamp / Watermarks --------------------------------------------- ...

  8. Flink Program Guide (1) -- 基本API概念(Basic API Concepts -- For Java)

    false false false false EN-US ZH-CN X-NONE /* Style Definitions */ table.MsoNormalTable {mso-style-n ...

  9. Flink Program Guide (9) -- StateBackend : Fault Tolerance(Basic API Concepts -- For Java)

    State Backends 本文翻译自文档Streaming Guide / Fault Tolerance / StateBackend ----------------------------- ...

随机推荐

  1. 使用SignalR和SQLTableDependency跟踪数据库中记录的变动

    原文地址:查看 SqlTableDependency是一个组件用来接收数据库的通知,包含在数据表上该记录的值的Inserted.Deleted或者Update操作. 备注:原文提供示例代码下载,但是j ...

  2. volatile 和const 变量的使用

    一.volatile定义: 一个定义为volatile的变量是说这变量可能会被意想不到的被改变,这样,有了volatile变量后,就提醒编译器就不会去假设这个变量的值了.精确地说就是,编译中的优化器在 ...

  3. 导出kettle数据转换设置

    在本地测试好的数据清洗配置,要转移到服务器上,就要用到导出数据转换设置. 一直是这使用是在导入设置的 时候总是提示不是kettle的文档格式.后来才发现这是资源库的配置,而不是数据转 换的配置. 导出 ...

  4. CentOS6.4 LAMP环境搭建

    网上的教程,不能按着抄打进去,这样会打乱你环境放置位置, 会导致配置路径会出问题. 要有一个环境目录优化, 把环境文件都装在/usr/local里面 首先,把安装文件rar都放置在/usr/local ...

  5. Eloquent ORM 之关联查询

    小伙伴们好,本文是在我的前一篇随笔的基础上完成的,还没有浏览的同学,请移尊驾哦 Eloquent ORM学习笔记. 前一篇文章用到了leftJoin方法,其实Eloquent对于模块之间的关联查询有自 ...

  6. Spring 系列: Spring 框架简介(转载)

    Spring 系列: Spring 框架简介 http://www.ibm.com/developerworks/cn/java/wa-spring1/ Spring AOP 和 IOC 容器入门 在 ...

  7. ASCII、ANSI、GB2312、Unicode、UTF-8之间的关系

    1.ASCII码: ASCII(American Standard Code for Information Interchange,美国信息互换标准代码)是基于拉丁字母的一套电脑编码系统.它主要用于 ...

  8. TCP 协议三次握手过程解析带实例

    TCP(Transmission Control Protocol) 传输控制协议 TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接: 位码即tcp标志位,有6种标 ...

  9. 杭电oj 2719

    Tips:本程序没有什么难度,只要按照逻辑进行替换即可,需要注意的是,由于输入串中含有空格符号,所以不能使用scanf("%s",ch);来读取一串,可以使用gets()函数读取一 ...

  10. 【LeetCode练习题】Evaluate Reverse Polish Notation

    Evaluate Reverse Polish Notation Evaluate the value of an arithmetic expression in Reverse Polish No ...