4.1 window

4.1.1 概述

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

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

4.1.2 Window 类型

Window 可以分成CountWindow和TimeWindow两类。CountWindow按照指定的数据条数生成一个 Window,与时间无关;TimeWindow按照时间生成Window

  • TimeWindow

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

滚动窗口(Tumbling Windows)

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

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

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

flink什么场景用广播流_检查点

适用场景:适合做BI统计等(做每个时间段的聚合计算)

滑动窗口(Sliding Windows)

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

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

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

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

flink什么场景用广播流_flink什么场景用广播流_02

适用场景:对最近一个时间段内的统计(求某接口最近 5 min 的失败率来决定是否要报警)

会话窗口(Session Windows)

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

特点:时间无对齐。

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

flink什么场景用广播流_大数据_03

4.1.3 CountWindow

计数窗口可以分为滚动计数窗口和滑动计数窗口

4.1.4 window API

  • 窗口分配器(window assigner)
  • 窗口函数(window function)

4.2 时间语义和Wartermark

4.2.1 Flink 中的时间语义

flink什么场景用广播流_API_04

  • Event Time:事件创建的时间
  • Ingestion Time:数据进入Flink的时间
  • Processing Time:执行操作算子的本地系统时间,与机器相关

不同的时间语义有不同的应用场合,我们往往更关心事件时间(Event Time)

4.2.2 设置 Event Time

我们可以直接在代码中,对执行环境调用 setStreamTimeCharacteristic方法,设置流的时间特性。具体的时间,还需要从数据中提取时间戳(timestamp)

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

4.2.3 水位线(Watermark)

  • 概念

事件出现乱序时,如果只根据eventTime决定window的运行,我们不能明确数据是否全部到位,但又不能无限期的等下去,此时必须要有个机制来保证一个特定的时间后,必须触发 window 去进行计算了,这个特别的机制,就是 Watermark。

flink什么场景用广播流_flink什么场景用广播流_05

Watermark 是一种衡量 Event Time 进展的机制,可以设定延迟触发
Watermark 是用于处理乱序事件的,而正确的处理乱序事件,通常用Watermark 机制结合 window 来实现;
数据流中的 Watermark 用于表示 timestamp 小于 Watermark 的数据,都已经到达了,因此, window 的执行也是由 Watermark 触发的。
watermark 用来让程序自己平衡延迟和结果正确性

  • 特点

watermark有以下特点:

1. watermark 是一条特殊的数据记录
2. watermark 必须单调递增,以确保任务的事件时间时钟在向前推进,而不是在后退
3. watermark 与数据的时间戳相关
  • watermark 的传递

flink什么场景用广播流_flink什么场景用广播流_06

  • watermark的引入

调用 assignTimestampAndWatermarks 方法,传入一个BoundedOutOfOrdernessTimestampExtractor,就可以指定

dataStream.assignTimestampsAndWatermarks(new AscendingTimestampExtractor<SensorReading>() {
        @Override
        public long extractAscendingTimestamp(SensorReading element) {
            return element.getTimestamp() * 1000L;
        }
    })
    // 乱序数据设置时间戳和watermark
    dataStream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<SensorReading>(Time.seconds(2)) {
        @Override
        public long extractTimestamp(SensorReading element) {
            return element.getTimestamp() * 1000L;
        }
    });

Flink 暴露了TimestampAssigner 接口供我们实现,使我们可以自定义如何从事件数据中抽取时间戳和生成watermark

dataStream.assignTimestampsAndWatermarks(new MyAssigner())

TimestampAssigner,定义了抽取时间戳,以及生成 watermark 的方法,有两种类型: AssignerWithPeriodicWatermarks和AssignerWithPunctuatedWatermarks

  • watermark的设定

在 Flink 中, watermark 由应用程序开发人员生成,这通常需要对相应的领域有一定的了解

如果watermark设置的延迟太久,收到结果的速度可能就会很慢,解决办法是在水位线到达之前输出一个近似结果

而如果watermark到达得太早,则可能收到错误结果,不过 Flink 处理迟到数据的机制可以解决这个问题

4.3 状态管理

4.3.1 Flink 中的状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IRo0KFSh-1659965307584)(https://note.youdao.com/yws/res/37339/WEBRESOURCEc52ec0bcb45e9e837a7746fc1472bd58)]

  • 由一个任务维护,并且用来计算某个结果的所有数据,都属于这个任务的状态
  • 可以认为状态就是一个本地变量,可以被任务的业务逻辑访问
  • Flink 会进行状态管理, 包括状态一致性、故障处理以及高效存储和访问,以便开发人员可以专注于应用程序的逻辑
  • 在 Flink 中,状态始终与特定算子相关联
  • 为了使运行时的 Flink 了解算子的状态,算子需要预先注册其状态

总的说来,有两种类型的状态:

  • 算子状态( Operator State),算子状态的作用范围限定为算子任务
  • 键控状态( Keyed State),根据输入数据流中定义的键( key)

4.3.2 算子状态(Operatior State)

flink什么场景用广播流_API_07

算子状态的作用范围限定为算子任务, 由同一并行任务所处理的所有数据都可以访问到相同的状态

状态对于同一子任务而言是共享的

算子状态不能由相同或不同算子的另一个子任务访问

算子状态数据结构

  • 列表状态(List state),将状态表示为一组数据的列表
  • 联合列表状态(Union list state),也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保存点( savepoint)启动应用程序时如何恢复
  • 广播状态( Broadcast state),如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态。

4.3.3 键控状态(Keyed State)

flink什么场景用广播流_检查点_08

  • 值状态(Value state),将状态表示为单个的值
  • 列表状态(List state),将状态表示为一组数据的列表
  • 映射状态(Map state),将状态表示为一组 Key-Value 对
  • 聚合状态(Reducing state & Aggregating State),将状态表示为一个用于聚合操作的列表

键控状态的用途:

  • 声明一个键控状态
  • 读取状态
  • 对状态赋值

4.3.4 状态后端(State Backends)

  • 每传入一条数据,有状态的算子任务都会读取和更新状态
  • 由于有效的状态访问对于处理数据的低延迟至关重要,因此每个并行任务都会在本地维护其状态,以确保快速的状态访问
  • 状态的存储、访问以及维护, 由一个可插入的组件决定,这个组件就叫做状态后端(state backend)
  • 状态后端主要负责两件事:本地的状态管理,以及将检查点(checkpoint)状态写入远程存储

状态后端有如下类型:

  • MemoryStateBackend,内存级的状态后端,会将键控状态作为内存中的对象进行管理,将它们存储在TaskManager 的 JVM 堆上,而将 checkpoint 存储在 JobManager 的内存中,特点是:快速、低延迟,但不稳定
  • FsStateBackend,将 checkpoint 存到远程的持久化文件系统( FileSystem) 上, 而对于本地状态,跟 MemoryStateBackend 一样,也会存在 TaskManager 的 JVM 堆上,同时拥有内存级的本地访问速度,和更好的容错保证
  • RocksDBStateBackend,将所有状态序列化后,存入本地的RocksDB中存储

4.4 容错机制

4.4.1 一致性检查点

Flink故障恢复机制的核心,就是应用状态的一致性检查点

有状态流应用的一致检查点, 其实就是所有任务的状态,在某个时间点的一份拷贝(一份快照); 这个时间点,应该是所有任务都恰好处理完一个相同的输入数据的时候;应用状态的一致检查点,是 Flink 故障恢复机制的核心

4.4.2 从检查点恢复状态

flink什么场景用广播流_大数据_09

恢复步骤如下:

1. 重启应用
2. 从 checkpoint 中读取状态,将状态重置,重置后的状态与检查点完成时的状态完全相同
3. 开始消费并处理检查点到发生故障之间的所有数据,这种检查点的保存和恢复机制可以为应用程序状态提供“精确一次”(exactly-once)的一致性,因为所有算子都会保存检查点并恢复其所有状态,这样一来所有的输入流就都会被重置到检查点完成时的位置

4.4.3 检查点的实现算法

基于 Chandy-Lamport 算法的分布式快照,将检查点的保存和数据处理分离开,不暂停整个应用

  • 检查点分界线

Flink 的检查点算法用到了一种称为分界线( barrier)的特殊数据形式,用来把一条流上数据按照不同的检查点分开。分界线之前到来的数据导致的状态更改,都会被包含在当前分界线所属的检查点中;而基于分界线之后的数据导致的所有更改,就会被包含在之后的检查点中

  • 检查点算法
  1. 两个输入流的应用程序,用并行的两个Source任务来读取
  2. JobManager会向每个source任务发送一条带有新检查点ID的消息,通过这种方式来启动检查点
  3. 数据源将它们的状态写入检查点,并发出一个检查点barrier,状态后端在状态存入检查点之后,会返回通知给source任务, source任务就会向JobManager确认检查点完成
  4. barrier向下游传递,sum任务会等待所有输入分区的barrier到达。对于barrier已经到达的分区,继续到达的数据会被缓存;而barrier尚未到达的分区,数据会被正常处理
  5. 当收到所有输入分区的barrier时, 任务就将其状态保存到状态后端的检查点中,然后将barrier继续向下游转发
  6. 向下游转发检查点barrier后,任务继续正常的数据处理
  7. Sink任务向JobManager确认状态保存到checkpoint完毕。当所有任务都确认已成功将状态保存到检查点时,检查点就真正完成了

4.4.4 保存点

  • 概念

Flink 还提供了可以自定义的镜像保存功能,就是保存点(savepoints),原则上,创建保存点使用的算法与检查点完全相同,因此保存点可以认为就是具有一些额外元数据的检查点

Flink不会自动创建保存点,因此用户(或者外部调度程序)必须明确地触发创建操作

  • 作用

除了故障恢复外,保存点可以用于: 有计划的手动备份, 更新应用程序,版本迁移,暂停和重启应用等等

4.4.5 应用开启检查点

程序中默认是不开启检查点配置的,如果要开启,可通过如下代码进行开启和配置:

// 1. 状态后端配置
env.setStateBackend( new MemoryStateBackend());
env.setStateBackend( new FsStateBackend(""));
env.setStateBackend( new RocksDBStateBackend(""));

// 2. 检查点配置
env.enableCheckpointing(300);

// 高级选项
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
env.getCheckpointConfig().setCheckpointTimeout(60000L);
env.getCheckpointConfig().setMaxConcurrentCheckpoints(2);
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(100L);
env.getCheckpointConfig().setPreferCheckpointForRecovery(true);
env.getCheckpointConfig().setTolerableCheckpointFailureNumber(0);

// 3. 重启策略配置
// 固定延迟重启
env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3, 10000L));
// 失败率重启
env.setRestartStrategy(RestartStrategies.failureRateRestart(3, Time.minutes(10), Time.minutes(1)));

4.5 状态一致性

4.5.1 什么是状态一致性

有状态的流处理,内部每个算子任务都可以有自己的状态,对于流处理器内部来说,所谓的状态一致性就是:

  • 计算结果要保证准确
  • 一条数据不丢失,也不重复计算
  • 在遇到故障时可以恢复状态, 恢复以后重新计算,结果应该也是完全正确的

状态一致性可以分为以下三类:

  • AT-MOST-ONCE(最多一次),当任务故障时,最简单的做法是什么都不干,既不恢复丢失的状态,也不重播丢失的数据
  • AT-LEAST-ONCE(至少一次),所有的事件都得到了处理,而一些事件还可能被处理多次
  • EXACTLY-ONCE(精确一次),恰好处理一次是最严格的保证,也是最难实现的,没有事件丢失,对每一个数据,内部状态仅仅更新一次

4.5.2 端到端( end-to-end)状态一致性

目前我们看到的一致性保证都是由流处理器实现的,也就是说都是在Flink流处理器内部保证的;而在真实应用中,流处理应用还包含数据源(例如 Kafka)和输出到持久化系统

端到端的一致性保证, 意味着结果的正确性贯穿了整个流处理应用的始终,每一个组件都保证了它自己的一致性,整个端到端的一致性级别取决于所有组件中一致性最弱的组件

端到端exactly-once的条件:

  • 内部保证 —— checkpoint
  • source端 —— 可重设数据的读取位置
  • sink端 —— 从故障恢复时,数据不会重复写入外部系统,有幂等写入和事务写入两种方法

事务写入的实现思想:构建的事务对应着checkpoint, 等到checkpoint真正完成的时候,才把所有对应的结果写入sink系统中,实现方式有预写日志和两阶段提交两种

  • 预写日志(Write-Ahead-Log,WAL)
    把结果数据先当成状态保存,然后在收到 checkpoint 完成的通知时,一次性写入 sink 系统。好处是:简单易于实现,由于数据提前在状态后端中做了缓存,所以无论什么sink 系统,都能用这种方式一批搞定。DataStream API 提供了一个模板类: GenericWriteAheadSink,来实现这种事务性 sink
  • 两阶段提交(Two-Phase-Commit,2PC)
    对于每个 checkpoint,sink任务会启动一个事务,并将接下来所有接收的数据添加到事务里;然后将这些数据写入外部 sink 系统,但不提交它们 —— 这时只是“预提交”;当它收到 checkpoint 完成的通知时,它才正式提交事务,实现结果的真正写入

这种方式真正实现了 exactly-once,它需要一个提供事务支持的外部sink 系统。Flink 提供了 TwoPhaseCommitSinkFunction 接口。

2PC 对外部 sink 系统的要求:

外部 sink 系统必须提供事务支持, 或者 sink 任务必须能够模拟外部系统上的事务
在 checkpoint 的间隔期间里,必须能够开启一个事务并接受数据写入
在收到 checkpoint 完成的通知之前,事务必须是“等待提交”的状态。在故障恢复的情况下,这可能需要一些时间。如果这个时候sink系统关闭事务(例如超时了),那么未提交的数据就会丢失
sink 任务必须能够在进程失败后恢复事务
提交事务必须是幂等操作

不同 Source 和 Sink 的一致性保证如下表所示:

flink什么场景用广播流_API_10

4.5.3 Flink+Kafka 端到端状态一致性

各个组件的一致性保证如下所示:

  • 内部 —— 利用 checkpoint 机制,把状态存盘,发生故障的时候可以恢复,保证内部的状态一致性
  • source —— kafka consumer 作为 source,可以将偏移量保存下来,如果后续任务出现了故障,恢复的时候可以由连接器重置偏移量,重新消费数据,保证一致性
  • sink —— kafka producer 作为sink,采用两阶段提交 sink,需要实现一个 TwoPhaseCommitSinkFunction

Exactly-once 两阶段提交步骤如下:

flink什么场景用广播流_API_11

1. JobManager协调各个TaskManager进行checkpoint存储,checkpoint保存在StateBackend中,默认StateBackend是内存级的,也可以改为文件级的进行持久化保存
2. 当checkpoint启动时,JobManager会将检查点分界线(barrier)注入数据流;barrier会在算子间传递下去
3. 每个算子会对当前的状态做个快照,保存到状态后端;checkpoint机制可以保证内部的状态一致性
4. 每个内部的transform任务遇到barrier时,都会把状态存到checkpoint里;sink任务首先把数据写入外部kafka,这些数据都属于预提交的事务;遇到barrier时,把状态保存到状态后端,并开启新的预提交事务
5. 当所有算子任务的快照完成,也就是这次的checkpoint完成时, JobManager会向所有任务发通知,确认这次checkpoint完成;sink任务收到确认通知,正式提交之前的事务,kafka 中未确认数据改为“已确认”
  1. 第一条数据来了之后, 开启一个 kafka 的事务( transaction),正常写入 kafka 分区日志但标记为未提交,这就是“预提交”
  2. jobmanager 触发 checkpoint 操作, barrier 从 source 开始向下传递, 遇到barrier 的算子将状态存入状态后端,并通知 jobmanager
  3. sink 连接器收到 barrier,保存当前状态, 存入 checkpoint, 通知 jobmanager,并开启下一阶段的事务,用于提交下个检查点的数据
  4. jobmanager 收到所有任务的通知,发出确认信息,表示 checkpoint 完成
  5. sink 任务收到 jobmanager 的确认信息, 正式提交这段时间的数据
  6. 外部kafka关闭事务,提交的数据可以正常消费了

5. API和扩展库

5.1 三层API

flink什么场景用广播流_大数据_12

最底层级的抽象ProcessFunction仅仅提供了有状态流,它允许用户可以自由地处理来自一个或多个数据流的事件,并使用一致的容错的状态。除此之外,用户可以注册事件时间并处理时间回调,从而使程序可以处理复杂的计算。

核心API(Core APIs)有 DataStream API(有界或无界流数据)和DataSet API(有界数据集)之分。DataStream API由用户定义的多种形式的转换(transformations),连接(joins),聚合(aggregations),窗口操作(windows)等等。 DataSet API 为有界数据集提供了支持,例如循环与迭代。这些 API处理的数据类型以类(classes)的形式由各自的编程语言所表示。

Table API 是以表为中心的声明式编程,其中表可能会动态变化(在表达流数据时)。Table API 遵循(扩展的)关系模型:表有二维数据结构( schema)(类似于关系数据库中的表),同时 API 提供可比较的操作,例如 select、 project、 join、 group-by、aggregate 等。

Flink 提供的最高层级的抽象是 SQL 。这一层抽象在语法与表达能力上与Table API 类似,但是是以 SQL 查询表达式的形式表现程序。 SQL 抽象与 Table API交互密切,同时 SQL 查询可以直接在 Table API 定义的表上执行

5.1.1 ProcessFunction

ProcessFunction API用来构建事件驱动的应用以及实现自定义的业务逻辑,可以访问时间戳、 watermark 以及注册定时事件。还可以输出特定的一些事件,例如超时事件等。

Flink 提供了如下7个 Process Function:

  • KeyedProcessFunction
  • CoProcessFunction
  • ProcessJoinFunction
  • BroadcastProcessFunction
  • KeyedBroadcastProcessFunction
  • ProcessWindowFunction
  • ProcessAllWindowFunction

侧输出流

大部分的 DataStream API 的算子的输出是单一输出,也就是某种数据类型的流。除了 split 算子,可以将一条流分成多条流,这些流的数据类型也都相同。 process function 的 side outputs 功能可以产生多条流,并且这些流的数据类型可以不一样。

下面是一个示例程序,用来监控传感器温度值,将温度值低于 30 度的数据输出到 side output

// 定义一个OutputTag,用来表示侧输出流低温流
    OutputTag<SensorReading> lowTempTag = new OutputTag<SensorReading>("lowTemp") {
    };

    // 测试ProcessFunction,自定义侧输出流实现分流操作
    SingleOutputStreamOperator<SensorReading> highTempStream = dataStream.process(new ProcessFunction<SensorReading, SensorReading>() {
        @Override
        public void processElement(SensorReading value, Context ctx, Collector<SensorReading> out) throws Exception {
            // 判断温度,大于30度,高温流输出到主流;小于低温流输出到侧输出流
            if( value.getTemperature() > 30 ){
                out.collect(value);
            } else {
                ctx.output(lowTempTag, value);
            }
        }
    });

    highTempStream.print("high-temp");
    highTempStream.getSideOutput(lowTempTag).print("low-temp");

5.1.2 DataStreamAPI

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h2KGrKPW-1659965307587)(https://note.youdao.com/yws/res/37404/WEBRESOURCE783e37ff7b5dc400cd8bb709a380a705)]

  • source,可以以集合、文件、kafka为数据来源,也可以自定义source
  • transform,可以是map、flatmap、filter、keyBy、Rolling Aggregation、Reduce、Split、Select、Connect、CoMap和Union

flat和flatMap区别:

map,DataStream → DataStream,可以把一个输入的数据转为另外一个数据(比如把小写字母转换为大写字母, 数字转换成他的相反数等)

FlatMap,DataStream → DataStream,可以把一个输入的数据转为0-N条数据(比如把一个单词中所有的字母拆出来)

Connect 与 Union 区别:

Union 之前两个流的类型必须是一样, Connect 可以不一样,在之后的 coMap中再去调整成为一样的

Connect 只能操作两个流, Union 可以操作多个

  • 函数
    UDF(User-defined Functions)自定义函数,极大地扩展了查询的表达能力,UDF有ScalarFunction、TableFunction、AggregateFunction三种
    Lambda Functions,匿名函数
    Rich Functions,富函数
  • Sink,可以是Kafka、Redis、Elasticsearch,也可以自定义sink

5.1.3 SQL/Table API

Table API 是一套内嵌在 Java 和 Scala 语言中的查询API, 它允许以非常直观的方式组合来自一些关系运算符的查询

Flink 的 SQL 支持基于实现了 SQL 标准的 Apache Calcit

// 2. 转换成POJO
    DataStream<SensorReading> dataStream = inputStream.map(line -> {
        String[] fields = line.split(",");
        return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
    });

    // 3. 创建表环境
    StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

    // 4. 基于流创建一张表
    Table dataTable = tableEnv.fromDataStream(dataStream);

    // 5. 调用table API进行转换操作
    Table resultTable = dataTable.select("id, temperature")
            .where("id = 'sensor_1'");
    
    // 6. 执行SQL
    tableEnv.createTemporaryView("sensor", dataTable);
    String sql = "select id, temperature from sensor where id = 'sensor_1'";
    Table resultSqlTable = tableEnv.sqlQuery(sql);

5.2 CEP简介

5.2.1 什么是CEP

复杂事件处理(Complex Event Processing,CEP),Flink CEP是在 Flink 中实现的复杂事件处理(CEP)库

CEP允许在无休止的事件流中检测事件模式,让我们有机会掌握数据中重要的部分,一个或多个由简单事件构成的事件流通过一定的规则匹配,然后输出用户想得到的数据——满足规则的复杂事件

5.2.2 CEP特点及使用场景

flink什么场景用广播流_API_13

  • 目标:从有序的简单事件流中发现一些高阶特征
  • 输入:一个或多个由简单事件构成的事件流
  • 处理:识别简单事件之间的内在联系,多个符合一定规则的简单事件构成复杂事件
  • 输出:满足规则的复杂事件

CEP一般用于分析低延迟、频繁产生的不同来源的事件流。CEP可以帮助在复杂的、不相关的时间流中找出有意义的模式和复杂的关系,以接近实时或准实时的获得通知或组织一些行为。比如实现以下一些功能:

  • 输入的流数据,尽快产生结果;
  • 在2个事件流上,基于时间进行聚合类的计算;
  • 提供实时/准实时的警告和通知;
  • 在多样的数据源中产生关联分析模式;
  • 高吞吐、低延迟的处理

5.2.3 架构

flink什么场景用广播流_检查点_14

CEP包含Event Stream、Pattern Definition、Pattern Detection和Alert Generation四个组件。开发人员要在DataStream流上定义出模式条件,之后Flink CEP引擎进行模式检测,必要时生成警告

5.2.3 Pattern API

处理事件的规则,被叫做模式(Pattern),Flink CEP 提供了Pattern API,用于对输入流数据进行复杂事件规则定义,用来提取符合规则的事件序列,使用如下:

DataStream<Event> input = ...;

    // 定义一个Pattern
    Pattern<Event, Event> pattern = Pattern.begin("start").where(...).next("middle").subtype(SubEvent.Class).
            where().followedBy().where(...);

    // 将创建好的Pattern应用到输入事件流上
    PatternStream<Event> patternStream = CEP.pattern(input, pattern);

    // 检测匹配事件序列,处理得到结果
    DataStream<Alert> result = patternStream.select(...);

模式分为三类:

  1. 个体模式(Individual Patterns)
start.times(3).where(new SimpleCondition<Event>() {...})

个体模式包括单例模式和循环模式。单例模式只接收一个事件,而循环模式可以接收多个事件。

  • 量词

可以在一个个体模式后追加量词,也就是指定循环次数

// 匹配出现4次
start.time(4)
// 匹配出现0次或4次
start.time(4).optional
// 匹配出现2、3或4次
start.time(2,4)
// 匹配出现2、3或4次,并且尽可能多地重复匹配
start.time(2,4).greedy
// 匹配出现1次或多次
start.oneOrMore
// 匹配出现0、2或多次,并且尽可能多地重复匹配
start.timesOrMore(2).optional.greedy
  • 条件

每个模式都需要指定触发条件,作为模式是否接受事件进入的判断依据,CEP 中的个体模式主要通过调用 .where() .or() 和 .until()来指定条件。

按不同的调用方式,可以分成以下几类:

简单条件(Simple Condition)

通过 .where() 方法对事件中的字段进行判断筛选,决定是否接受该事件

start.where(new SimpleCondition<Event>() {
    @Override
    public boolean filter(Event event) throws Exception {
        return event.getName.startWith("foo");
    }
});

组合条件(Combining Condition)

将简单条件进行合并; .or() 方法表示或逻辑相连, where 的直接组合就是 AND

Pattern.where(event => …/*some condition*/).or(event => /*or condition*/)

终止条件

如果使用了oneOrMore或者oneOrMore.optional,建议使用.until()作为终止条件,以便清理状态。

迭代条件

能够对模式之前所有接收的事件进行处理;调用

ctx.getEventForPattern(“name”).where(new IterativeCondition<Evnet>() {...})
  1. 组合模式(Combining Patterns,也叫模式序列)

很多个体模式组合起来,就形成了整个的模式序列,模式序列必须以一个“初始模式”开始:

Pattern<Event, Event> start = Pattern.begin("start")
  • 严格近邻(Strict Contiguity)

所有事件按照严格的顺序出现,中间没有任何不匹配的事件, 由 .next() 指定,例如对于模式”a next b” ,事件序列 [a, c, b1, b2] 没有匹配

  • 宽松近邻(Relaxed Contiguity)

允许中间出现不匹配的事件,由 .followedBy() 指定,例如对于模式”a followedBy b” ,事件序列 [a, c, b1, b2] 匹配为 {a, b1}

  • 非确定性宽松近邻(Non-Deterministic Relaxed Contiguity)

进一步放宽条件,之前已经匹配过的事件也可以再次使用,由 .followedByAny() 指定,例如对于模式”a followedByAny b” ,事件序列 [a, c, b1, b2] 匹配为 {a, b1}, {a,b2}

  • 除以上模式序列外,还可以定义 “不希望出现某种近邻关系”

.notNext() —— 不想让某个事件严格紧邻前一个事件发生

.notFollowedBy() —— 不想让某个事件在两个事件之间发生

注意事项

所有模式序列必须以 .begin() 开始

模式序列不能以 .notFollowedBy() 结束

“not” 类型的模式不能被 optional 所修饰

此外,还可以为模式指定时间约束,用来要求在多长时间内匹配有效,next.within(Time.seconds(10)

  1. 模式组(Groups of patterns)

将一个模式序列作为条件嵌套在个体模式里,成为一组模式

5.2.4 Pattern Detection

指定要查找的模式序列后,就可以将其应用于输入流以检测潜在匹配

调用 CEP.pattern(),给定输入流和模式,就能得到一个PatternStream

5.2.5 匹配事件的提取

创建PatternStream之后,就可以应用select或者flatSelect方法,从检测到的事件序列中提取事件了

select()方法需要输入一个select function作为参数,每个成功匹配的事件序列都会调用它。

select()以一个Map[String,Iterable[IN]]来接收匹配到的事件序列,其中key就是每个模式的名称,而value就是所有接收到的事件的Iterable类型

public OUT select(Map<String, List<IN> pattern>) throws Exception {
    IN startEvent = pattern.get("start").get(0);
    IN endEvent = pattern.get("end").get(0);
    return OUT(startEvent, endEvent);
}

超时事件的提取

当一个模式通过within关键字定义了检测窗口时间时,部分事件序列可能因为超过窗口长度而被丢弃;为了能够处理这些超时的部分匹配,select和flatSelect API调用允许指定超时处理程序