【无标题】

本文主要介绍Flink流式计算中的窗口操作和CEP编程模型。窗口操作包括Window类型、生命周期,以及滚动、滑动、会话、全局窗口等不同类型,还介绍了开窗聚合算子。CEP编程模型是基于DataStream的复杂事件处理编程模型,能实现无界流数据的组合匹配。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

	、Window 开窗

可以看到,在Flink的流式计算中,数据都是以DataStream的形式来表示。而对流数据的计算,基本上都是一个先分流后合流的过程。而window开窗函数可以理解为是一种更高级的分流的方法。Window将一个无限的流式数据DataStram拆分成有限大小的"Bucket"桶,通过对桶中数据的计算最终完成整个流式数据的计算。他也是处理流式数据时的一种常见的方法,在KafkaStream、Spark Streaming等这些流式框架中都有。
6.1 Window类型
Flink中的Window整体上可以分为两类
Keyed Window 针对keyedStream进行的开窗。keyed Stream会将原始的无界流切分成多个逻辑上的keyed stream。在Keyed Stream上的开窗函数window,可以指定并行度,由多个任务并行执行计算任务。所有拥有相同Key的数据将会被分配到同一个并行任务中。常见的操作是这样的:
stream
.keyBy(…) <- keyed versus non-keyed windows
.window(…) <- required: “assigner”
[.trigger(…)] <- optional: “trigger” (else default trigger)
[.evictor(…)] <- optional: “evictor” (else no evictor)
[.allowedLateness(…)] <- optional: “lateness” (else zero)
[.sideOutputLateData(…)] <- optional: “output tag” (else no side output for late data)
.reduce/aggregate/fold/apply() <- required: “function”
[.getSideOutput(…)] <- optional: “output tag”
Non-Keyed Window 针对DataStream进行的开窗。这种开窗是将所有的流式数据生成一个window,这时这个window就不能进行并行计算了,只能以并行度1,由一个单独的任务进行计算。这种开窗方式显然是不利于利用集群的整体资源的,所以通常用得比较少。常见的操作是这样的:
stream
.windowAll(…) <- required: “assigner”
[.trigger(…)] <- optional: “trigger” (else default trigger)
[.evictor(…)] <- optional: “evictor” (else no evictor)
[.allowedLateness(…)] <- optional: “lateness” (else zero)
[.sideOutputLateData(…)] <- optional: “output tag” (else no side output for
late data)
.reduce/aggregate/fold/apply() <- required: “function”
[.getSideOutput(…)] <- optional: “output tag”
在这里可以看到,在API上,Keyed Window和Non-keyed Window,基本上是一致的,唯一的区别就是开窗函数window和windowAll。所以,后续分析Flink的窗口操作时,将不再区分keyed Window和Non-keyed Window。但是他们两者的区别还是要明白。
6.2 window的生命周期
简单来说,一个window,会指定一个包含数据的范围,从第一个属于他的数据到达之后就被创建出来,而等所有数据都处理完后就会被彻底移除。这个移除的时刻是由指定的窗口结束时间加上后续设定的 allowedLateness时长决定的。例如设定每分钟创建一个window,正常从每分钟的0秒开始创建一个window,然后到这一分钟的60秒就会结束这个window。但是flink允许设定一个延迟时间,比如5秒,那么这个window就会在下一秒的5秒才移除,这是为了防止网络传输延时造成的数据丢失。关于数据的时序问题,后面会有专门的分析。在flink中,需要通过一个WindowAssigner对象来指定数据开窗的方式。例如,对于DataStream,他的开窗方式是这样的
stream.windowAll(TumblingEventTimeWindows.of(Time.seconds(60)));
//windowAll方法需要传入的是一个WindowAssigner对象。
public AllWindowedStream<T, W> windowAll(
WindowAssigner<? super T, W> assigner)
Flink提供了WindowAssigner的四种不同的实现方式。滚动窗口 Tumbling window,滑动窗口 Sliding window,会话窗口 Session window 以及 全局窗口 Global Window。另外,Flink中对于还有另外一种根据消息个数开窗的方式。 对于DataStream是countWindowAll,对于KeyedStream是countwindow。这种方式是指一个窗口只包含固定条数的数据。这种方式只考虑数据的数量,没有时间的概念。之前分析过,对于无界流的计算,时间和顺序是非常重要的,所以这种根据消息个数开窗的方式,在实际场景中用得比较少。示例代码com.roy.flink.window.CountWindowDemo
6.3 滚动窗口 Tumbling window
滚动窗口需要指定一个固定的窗口大小window size,并且窗口之间不会重叠。

例如
DataStream input = …;
// 5秒一个窗口,根据EventTime切分。
input
.keyBy()
.window(TumblingEventTimeWindows.of(Time.seconds(5)))
.();
// 5秒一个窗口,根据ProcessTime切分。
input
.keyBy()
.window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
.();
// 开窗函数还可以接受一个偏移量,表示开窗的起点与标准起点的差距。例如下面的-8表示时区。
input
.keyBy()
.window(TumblingEventTimeWindows.of(Time.days(1), Time.hours(-8)))
.();
在上面的最后一个示例中看到,滚动开窗除了接收一个window size参数外,还可以接收一个offset参数。这个表示开窗的偏移量。例如默认情况下,按照一个小时开一个窗,那么拿到的分
区范围是 [1:00:00 ~ 1:59:59, 2:00:00 ~ 2:59:59 …]。但是,当你设定一个15分钟的偏移量之后,得到的分区范围就是 [1:15:00 ~ 2:14:59, 2:15:00 ~ 3:14:59 …]。
6.4 滑动窗口 Sliding widow
滑动窗口与滚动窗口一样有一个窗口大小window size,另外还有一个滑动间隔的windowslide。例如,在新冠肺炎期间,我们需要每天统计14天内的行程,这样window slide就是1天,而window size就是14天。这里可以看到,只要window slide参数小于window size,那么必然就会有元素出现在多个window中。而如果window slide参与等于window size,那就是上面的滚动窗口了。

示例代码:
DataStream input = …;
// sliding event-time windows
input
.keyBy()
.window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5)))
.();
// sliding processing-time windows
input
.keyBy()
.window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5)))
.();
// sliding processing-time windows offset by -8 hours
input
.keyBy()
.window(SlidingProcessingTimeWindows.of(Time.hours(12), Time.hours(1),
Time.hours(-8)))
.();
这里滑动窗口依然可以接收一个偏移量的可选参数。
6.5 会话窗口 Session window
会话窗口是以session会话的方式来划分窗口。会话窗口没有窗口大小和滑动间距这样的参数,他只需要指定一个会话间隔session gap参数。这个会话间隔可以是一个固定的参数,也可
以是一个计算函数。只要有相邻两个元素之间的时间间隔超过了这个会话间隔,那么就会划分为两个不同的window。例如如果需要通过打开机记录,统计员工上下班打卡的时间,为了避免重复打卡造成的误判,就可以用session window进行开窗,在不同的窗口期内统计员工真实的上下班时间。因为员工可能在忘记自己已经打过卡后,在短时间内重复打卡。但是,上班打卡和下班打卡之间的时间间隔就会长得多。

示例代码
DataStream input = …;
// event-time session windows with static gap
input
.keyBy()
.window(EventTimeSessionWindows.withGap(Time.minutes(10)))
.();
// event-time session windows with dynamic gap
input
.keyBy()
.window(EventTimeSessionWindows.withDynamicGap((element) -> {
// determine and return session gap
}))
.();
// processing-time session windows with static gap
input
.keyBy()
.window(ProcessingTimeSessionWindows.withGap(Time.minutes(10)))
.();
// processing-time session windows with dynamic gap
input
.keyBy()
.window(ProcessingTimeSessionWindows.withDynamicGap((element) -> {
// determine and return session gap
}))
.();
6.6 全局窗口 Global window
全局窗口会把所有相同的元素划分到一个窗口中,而不进行主动的切分。 例如对于keyedStream,就会把所有key相同的元素划分到一个窗口中。这种全局窗口没有对窗口进行切分,窗口范围没有开始也没有结束。因此自然也是不能直接用的。全局窗口需要自己定义一个trigger来触发窗口计算。实际上,可以把全局窗口认为是一种可自定义的窗口。上述几种类型的窗口是都全局窗口的一种实现方式。

6.7 trigger与evictor
使用全局窗口,后续至少要有一个trigger方法。trigger需要传入一个Trigger对象,这是一个抽象类,他代表的是窗口应该在何时关闭,触发计算。Flink本身提供了很多实现类:

其中EventTimeTrigger和ProcessingTimeTrigger主要是根据数据的时间语义来触发,这两个Trigger在理解完后面的时间语义后,自然就理解了。CountTrigger是一个比较浅显易懂的示例,如果想要深入理解如何定制Trigger,那么这个CountTrigger就是一个很好的参考。
简单理解,就是通过Trigger中的各种onxxx方法,来响应流式数据,然后通过返回
TriggerResult对象来决定是否需要出发窗口切换。
CountTrigger需要传入一个参数,表示消息的个数,当消息个数达到阈值后进行窗口划分。
/**

  • Creates a trigger that fires once the number of elements in a pane reaches the
    given count.
  • @param maxCount The count of elements at which to fire.
  • @param The type of {@link Window Windows} on which this trigger can operate.
    /
    public static CountTrigger of(long maxCount) {
    return new CountTrigger<>(maxCount);
    }
    DeltaTrigger可以根据自定义的方式来设计窗口划分的指标以及阈值,也是非常好用的一个实现类。DeltaTrigger需要提供一个DeltaFunction函数以及一个threshold阈值。他的实现方式类似于Session window。也是通过计算两个相邻数据之间的间隔来划分窗口。只不过这个间隔就不再是一个固定的时间,而是由DetaFunction计算出来的一个Delta指标。Delta指标大于threshold阈值时,就会触发一次窗口划分。相当于是Session Window的定制版本。
    /
    *
  • Creates a delta trigger from the given threshold and {@code DeltaFunction}.
  • @param threshold The threshold at which to trigger.
  • @param deltaFunction The delta function to use
  • @param stateSerializer TypeSerializer for the data elements.
  • @param The type of elements on which this trigger can operate.
  • @param The type of {@link Window Windows} on which this trigger can operate.
    */
    public static <T, W extends Window> DeltaTrigger<T, W> of(
    double threshold, DeltaFunction deltaFunction, TypeSerializer
    stateSerializer) {
    return new DeltaTrigger<>(threshold, deltaFunction, stateSerializer);
    }
    对于WindowedStream和AllWindowedStream,还有一个evictor函数也经常会用到。evictor函数需要传入一个Evictor对象。Evictor是用来对窗口中的对象进行剔除的。

其中,TimeEvictor需要传入一个偏移时长 keep_time,所有时长早于 (当前时间 - 偏移时长)的元素就会被从windows中驱逐。例如需要每10分钟开一次窗,但是只需要统计每个窗口内后8分钟的数据,这时就可以通过一个偏移时长为8分钟的evictor加一个10分钟的滚动窗口来实现。CountEvictor则只保留窗口内固定个数的消息。DeltaEvictor基于一个DeltaFunction函数以及一个threshold阈值来进行过滤,这跟DetaTrigger是类似的。过滤时,以窗口中的第一个元素为起点,Delta指标超过threshold阈值的元素将会被剔除。这个Delta就是由DeltaFunction计算出来的一个指标。用户可以自定义DeltaFunction的实现。例如可以以时间作为指标,那就是统计一定时间范围内的元素。其实通过这些示例可以看到,通过全局窗口+Trigger+Evictor的方式进行定制更自由更复杂的窗口切分方案。示例代码 com.roy.flink.window.WindowAssignerDemo
6.7 开窗聚合算子
对流式数据进行开窗的目的,肯定是为了对窗口内的数据进行统计计算。这些统计方法和基础
的DataStream统计是很类似的。
1 Window Apply
windowedStream.apply (new WindowFunction<Tuple2<String,Integer>, Integer, Tuple, Window>
() {
public void apply (Tuple tuple,
Window window,
Iterable<Tuple2<String, Integer>> values,
Collector out) throws Exception {
int sum = 0;
for (value t: values) {
sum += t.f1;
}
out.collect (new Integer(sum));
}
});
// applying an AllWindowFunction on non-keyed window stream
allWindowedStream.apply (new AllWindowFunction<Tuple2<String,Integer>, Integer, Window>
() {
public void apply (Window window,
Iterable<Tuple2<String, Integer>> values,
Collector out) throws Exception {
int sum = 0;
for (value t: values) {
sum += t.f1;
}
out.collect (new Integer(sum));
}
});
给窗口内的所有数据提供一个
整体的处理函数,可以称为全窗口聚合函数。例如下面是求和的示例。
2、 Window Reduce
Windowed Stream -> DataStream 同样是通过两个相邻元素的处理,来叠加完成整个集合的处理。
windowedStream.reduce (new ReduceFunction<Tuple2<String,Integer>>() {
public Tuple2<String, Integer> reduce(Tuple2<String, Integer> value1, Tuple2<String,
Integer> value2) throws Exception {
return new Tuple2<String,Integer>(value1.f0, value1.f1 + value2.f1);
}
});
3、 Aggregations on Window
Windowed Steam -> DataStream 在整个window上进行一些整体的统计。
windowedStream.sum(0);
windowedStream.sum(“key”);
windowedStream.min(0);
windowedStream.min(“key”);
windowedStream.max(0);
windowedStream.max(“key”);
windowedStream.minBy(0);
windowedStream.minBy(“key”);
windowedStream.maxBy(0);
windowedStream.maxBy(“key”);
同样 min是返回所选列中最小的数据,而minBy是返回所选列最小的这一行。
4、自定义窗口聚合函数
对于WindowedStream,也可以通过aggregate方法传入一个自定义的AggregateFunction
实现类来实现自定义的窗口聚合。
// WindowFunction的四个泛型依次表示: 传入数据类型、返回结果类型、key类型、窗口类型。
windowedStream.apply(new WindowFunction<Stock, Tuple2<String,Integer>, String,
TimeWindow>() {
//四个参数依次表示:当前数据的key,当前窗口类型,当前窗口内所有数据的迭代器、输出
结果收集器
@Override
public void apply(String s, TimeWindow window, Iterable input,
Collector<Tuple2<String,Integer>> out) throws Exception {
final int count = IteratorUtils.toList(input.iterator()).size();
out.collect(new Tuple2<>(s,count));
}
})
示例代码 com.roy.flink.window.WindowFunctionDemo
在这里重点是需要理解下apply与aggregate两种聚合方式的区别。apply聚合方式会持续收集窗口内的数据,待窗口的数据全部收集完成后,拿到整个窗口期内的数据,进行整体处理。相当于是一个批处理的过程。可以称之为全窗口聚合。而aggregate聚合方式则是来一条数据处理一次,并将结果保存到累加器中。当窗口结束后,直接从累加器中返回当前窗口的计算结果。可以称之为流式聚合。这两种聚合机制,aggregate流式聚合的方式效率会更高,而apply全窗口聚合能够拿到计算过程中更多的信息,因此会更为灵活。当需要定制时,可以根据业务场景灵活取舍。并且,在具体编码实现时,我们只需要记住这两种机制,就不需要完全记住编码的方式了。
7、CEP编程模型
Flink CEP即 Flink Complex Event Processing,是基于DataStream流式数据提供的一套复
杂事件处理编程模型。你可以把他理解为基于无界流的一套正则匹配模型,即对于无界流中的各
种数据(称为事件),提供一种组合匹配的功能。

上图中,以不同形状代表一个DataStream中不同属性的事件。以一个圆圈和一个三角组成一
个Pattern后,就可以快速过滤出原来的DataStream中符合规律的数据。举个例子,比如很多网
站需要对恶意登录的用户进行屏蔽,如果用户连续三次输入错误的密码,那就要锁定当前用户。
在这个场景下,所有用户的登录行为就构成了一个无界的数据流DataStream。而连续三次登录
失败就是一个匹配模型Pattern。CEP编程模型的功能就是从用户登录行为这个无界数据流
DataStream中,找出符合这个匹配模型Pattern的所有数据。这种场景下,使用我们前面介绍的
各种DataStream API其实也是可以实现的,不过相对就麻烦很多。而CEP编程模型则提供了非常
简单灵活的功能实现方式。
使用CEP编程模型首先需要引入maven依赖:
的处理思想。@TOC

欢迎使用Markdown编辑器

你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: Alt

居中的图片: Alt

居中并且带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目Value
电脑$1600
手机$12
导管$1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文本居中第二列文本居右第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPEASCIIHTML
Single backticks'Isn't this fun?'‘Isn’t this fun?’
Quotes"Isn't this fun?"“Isn’t this fun?”
Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

2014-01-07 2014-01-09 2014-01-11 2014-01-13 2014-01-15 2014-01-17 2014-01-19 2014-01-21 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

Created with Raphaël 2.3.0 开始 我的操作 确认? 结束 yes no
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值