Flink-v1.12官方网站翻译-P009-Event-driven Applications
事件驱动的应用
处理函数
简介
ProcessFunction将事件处理与定时器和状态结合起来,使其成为流处理应用的强大构件。这是用Flink创建事件驱动应用的基础。它与RichFlatMapFunction非常相似,但增加了定时器。
例子
如果你做过 "流分析 "培训中的实战练习,你会记得它使用TumblingEventTimeWindow来计算每个司机在每个小时内的小费总和,像这样。
// compute the sum of the tips per hour for each driver
DataStream<Tuple3<Long, Long, Float>> hourlyTips = fares
.keyBy((TaxiFare fare) -> fare.driverId)
.window(TumblingEventTimeWindows.of(Time.hours(1)))
.process(new AddTips());
用KeyedProcessFunction做同样的事情是相当直接的,也是很有教育意义的。让我们先把上面的代码替换成这样。
// compute the sum of the tips per hour for each driver
DataStream<Tuple3<Long, Long, Float>> hourlyTips = fares
.keyBy((TaxiFare fare) -> fare.driverId)
.process(new PseudoWindow(Time.hours(1)));
在这段代码中,一个名为PseudoWindow的KeyedProcessFunction被应用于一个键控流,其结果是一个DataStream<Tuple3<Long,Long,Float>>(就是使用Flink内置时间窗口的实现所产生的那种流)。
PseudoWindow的整体轮廓是这样的形状。
// Compute the sum of the tips for each driver in hour-long windows.
// The keys are driverIds.
public static class PseudoWindow extends
KeyedProcessFunction<Long, TaxiFare, Tuple3<Long, Long, Float>> { private final long durationMsec; public PseudoWindow(Time duration) {
this.durationMsec = duration.toMilliseconds();
} @Override
// Called once during initialization.
public void open(Configuration conf) {
. . .
} @Override
// Called as each fare arrives to be processed.
public void processElement(
TaxiFare fare,
Context ctx,
Collector<Tuple3<Long, Long, Float>> out) throws Exception { . . .
} @Override
// Called when the current watermark indicates that a window is now complete.
public void onTimer(long timestamp,
OnTimerContext context,
Collector<Tuple3<Long, Long, Float>> out) throws Exception { . . .
}
}
需要注意的事情。
- ProcessFunctions有好几种类型--这是一个KeyedProcessFunctions,但还有CoProcessFunctions、BroadcastProcessFunctions等。
- KeyedProcessFunction是RichFunction的一种。作为一个RichFunction,它可以访问在管理键控状态下工作所需的open和getRuntimeContext方法。
- 有两个回调要实现:processElement和onTimer。"processElement "在每次传入事件时被调用;"onTimer "在定时器发射时被调用。这些定时器可以是事件时间,也可以是处理时间定时器。processElement和onTimer都提供了一个上下文对象,该对象可以用来与TimerService交互(除其他外)。这两个回调也都传递了一个可以用来发出结果的Collector。
open()方法
// Keyed, managed state, with an entry for each window, keyed by the window's end time.
// There is a separate MapState object for each driver.
private transient MapState<Long, Float> sumOfTips; @Override
public void open(Configuration conf) { MapStateDescriptor<Long, Float> sumDesc =
new MapStateDescriptor<>("sumOfTips", Long.class, Float.class);
sumOfTips = getRuntimeContext().getMapState(sumDesc);
}
由于票价事件可能会不按顺序到达,所以有时需要处理一个小时的事件,然后再完成前一个小时的结果计算。事实上,如果水印延迟比窗口长度长得多,那么可能会有许多窗口同时打开,而不是只有两个。本实现通过使用MapState来支持这一点,MapState将每个窗口结束的时间戳映射到该窗口的提示之和。
processElement()方法
public void processElement(
TaxiFare fare,
Context ctx,
Collector<Tuple3<Long, Long, Float>> out) throws Exception { long eventTime = fare.getEventTime();
TimerService timerService = ctx.timerService(); if (eventTime <= timerService.currentWatermark()) {
// This event is late; its window has already been triggered.
} else {
// Round up eventTime to the end of the window containing this event.
long endOfWindow = (eventTime - (eventTime % durationMsec) + durationMsec - 1); // Schedule a callback for when the window has been completed.
timerService.registerEventTimeTimer(endOfWindow); // Add this fare's tip to the running total for that window.
Float sum = sumOfTips.get(endOfWindow);
if (sum == null) {
sum = 0.0F;
}
sum += fare.tip;
sumOfTips.put(endOfWindow, sum);
}
}
要考虑的事情。
- 迟到的事件会怎样?在水印后面的事件(即迟到)会被丢弃。如果你想做一些比这更好的事情,可以考虑使用侧面输出,这将在下一节解释。
- 这个例子使用了一个MapState,其中键是时间戳,并为同一个时间戳设置一个Timer。这是一种常见的模式;它使得在定时器发射时查找相关信息变得简单而高效。
onTimer()方法
public void onTimer(
long timestamp,
OnTimerContext context,
Collector<Tuple3<Long, Long, Float>> out) throws Exception { long driverId = context.getCurrentKey();
// Look up the result for the hour that just ended.
Float sumOfTips = this.sumOfTips.get(timestamp); Tuple3<Long, Long, Float> result = Tuple3.of(driverId, timestamp, sumOfTips);
out.collect(result);
this.sumOfTips.remove(timestamp);
}
观察。
- 传递给onTimer的OnTimerContext上下文可以用来确定当前的密钥。
- 我们的伪窗口是在当前水印到达每个小时结束时被触发的,此时调用onTimer。这个onTimer方法从sumOfTips中删除了相关的条目,这样做的效果是无法容纳迟到的事件。这相当于在使用Flink的时间窗口时,将allowLateness设置为零。
性能考虑因素
Flink提供了针对RocksDB优化的MapState和ListState类型。在可能的情况下,应该使用这些类型来代替持有某种集合的ValueState对象。RocksDB状态后端可以追加到ListState,而不需要经过(去)序列化,对于MapState,每个键/值对都是一个独立的RocksDB对象,因此MapState可以有效地被访问和更新。
侧面输出
简介
有几个很好的理由可以让Flink操作者有一个以上的输出流,比如报告。
- 异常
- 畸形事件
- 后事
- 操作警报,如与外部服务的连接超时。
侧输出是一种方便的方式。除了错误报告之外,侧输出也是实现流的n路分割的好方法。
例子
现在,您可以对上一节中被忽略的晚期事件做些什么了。
一个侧输出通道与一个 OutputTag<T>相关联。这些标签具有与侧输出的DataStream的类型相对应的通用类型,它们具有名称。
private static final OutputTag<TaxiFare> lateFares = new OutputTag<TaxiFare>("lateFares") {};
上面展示的是一个静态的OutputTag<TaxiFare>,它既可以在PseudoWindow的processElement方法中发出晚期事件时被引用。
if (eventTime <= timerService.currentWatermark()) {
// This event is late; its window has already been triggered.
ctx.output(lateFares, fare);
} else {
. . .
}
并在访问这边的流时,在作业的主方法中输出。
// compute the sum of the tips per hour for each driver
SingleOutputStreamOperator hourlyTips = fares
.keyBy((TaxiFare fare) -> fare.driverId)
.process(new PseudoWindow(Time.hours(1))); hourlyTips.getSideOutput(lateFares).print();
另外,您也可以使用两个具有相同名称的OutputTags来引用同一侧输出,但如果您这样做,它们必须具有相同的类型。
结束语
在这个例子中,你已经看到了如何使用ProcessFunction来重新实现一个直接的时间窗口。当然,如果Flink内置的窗口API满足你的需求,无论如何,请继续使用它。但如果你发现自己在考虑用Flink的窗口做一些变形,不要害怕推出自己的窗口。
此外,ProcessFunctions对于计算分析之外的许多其他用例也很有用。下面的实践练习提供了一个完全不同的例子。
ProcessFunctions的另一个常见用例是用于过期的陈旧状态。如果你回想一下Rides和Fares练习,其中使用RichCoFlatMapFunction来计算一个简单的连接,示例解决方案假设TaxiRides和TaxiFares是完美匹配的,每个rideId是一对一的。如果一个事件丢失了,同一乘车ID的其他事件将永远保持在状态。这可以替换为一个KeyedCoProcessFunction来实现,并且可以使用一个定时器来检测和清除任何陈旧的状态。
实践
与本节配套的实战练习是长乘警报练习。
下一步阅读什么
Flink-v1.12官方网站翻译-P009-Event-driven Applications的更多相关文章
- Flink-v1.12官方网站翻译-P005-Learn Flink: Hands-on Training
学习Flink:实践培训 本次培训的目标和范围 本培训介绍了Apache Flink,包括足够的内容让你开始编写可扩展的流式ETL,分析和事件驱动的应用程序,同时省略了很多(最终重要的)细节.本书的重 ...
- Flink-v1.12官方网站翻译-P025-Queryable State Beta
可查询的状态 注意:可查询状态的客户端API目前处于不断发展的状态,对所提供接口的稳定性不做保证.在即将到来的Flink版本中,客户端的API很可能会有突破性的变化. 简而言之,该功能将Flink的托 ...
- Flink-v1.12官方网站翻译-P015-Glossary
术语表 Flink Application Cluster Flink应用集群是一个专用的Flink集群,它只执行一个Flink应用的Flink作业.Flink集群的寿命与Flink应用的寿命绑定. ...
- Flink-v1.12官方网站翻译-P008-Streaming Analytics
流式分析 事件时间和水印 介绍 Flink明确支持三种不同的时间概念. 事件时间:事件发生的时间,由产生(或存储)该事件的设备记录的时间 摄取时间:Flink在摄取事件时记录的时间戳. 处理时间:您的 ...
- Flink-v1.12官方网站翻译-P004-Flink Operations Playground
Flink操作训练场 在各种环境中部署和操作Apache Flink的方法有很多.无论这种多样性如何,Flink集群的基本构件保持不变,类似的操作原则也适用. 在这个操场上,你将学习如何管理和运行Fl ...
- Flink-v1.12官方网站翻译-P002-Fraud Detection with the DataStream API
使用DataStream API进行欺诈检测 Apache Flink提供了一个DataStream API,用于构建强大的.有状态的流式应用.它提供了对状态和时间的精细控制,这使得高级事件驱动系统的 ...
- Flink-v1.12官方网站翻译-P019-Generating Watermarks
生成水印 在本节中,您将了解 Flink 提供的 API,用于处理事件时间时间戳和水印.关于事件时间.处理时间和摄取时间的介绍,请参考事件时间的介绍. 水印策略介绍 为了使用事件时间,Flink需要知 ...
- Flink-v1.12官方网站翻译-P018-Event Time
事件时间 在本节中,您将学习如何编写时间感知的Flink程序.请看一下及时流处理,了解及时流处理背后的概念. 关于如何在Flink程序中使用时间的信息请参考windowing和ProcessFunct ...
- Flink-v1.12官方网站翻译-P016-Flink DataStream API Programming Guide
Flink DataStream API编程指南 Flink中的DataStream程序是对数据流实现转换的常规程序(如过滤.更新状态.定义窗口.聚合).数据流最初是由各种来源(如消息队列.套接字流. ...
随机推荐
- flume集成hdfs(hdfs开启kerberos认证)
)当 sink 到 hdfs 时: ) 需修改 flume-env.sh 配置,增添 hdfs 依赖库: FLUME_CLASSPATH="/root/TDH-Client/hadoop/h ...
- 解决FastJson循环引用的问题
temp 本来被循环引用,运行方法报错. 解决方法:对temp进行处理 SerializerFeature feature = SerializerFeature.DisableCircularRef ...
- [ABP教程]第六章 作者:领域层
Web开发教程6 作者:领域层 关于此教程 在这个教程系列中,你将要构建一个基于ABP框架的应用程序 Acme.BookStore.这个应用程序被用于甘丽图书页面机器作者.它将用以下开发技术: Ent ...
- 腾讯消息队列CMQ部署与验证
环境 IP 备注 192.168.1.66 node1 前置机 192.168.1.110 node2 192.168.1.202 node3 架构图 组件介绍 组件 监听端口 access 1200 ...
- 知识图谱和neo4j的基本操作
一.知识图谱的简介 1.知识图谱是什么 知识图谱本质上是语义网络(Semantic Network)的知识库 可以理解为一个关系图网络. 2.什么是图 图(Graph)是由节点(Vertex)和边(E ...
- zabbix v3.0安装部署【转】
关于zabbix及相关服务软件版本: Linux:oracle linux 6.5 nginx:1.9.15 MySQL:5.5.49 PHP:5.5.35 一.安装nginx: 安装依赖包: yum ...
- STM32 HAL库之串口详细篇
一.基础认识 (一) 并行通信 原理:数据的各个位同时传输 优点:速度快 缺点:占用引脚资源多,通常工作时有多条数据线进行数据传输 8bit数据传输典型连接图: 传输的数据是二进制:11101010, ...
- 【Redis3.0.x】配置文件
Redis3.0.x 配置文件 概述 Redis 的配置文件位于Redis安装目录下,文件名为 redis.conf. 可以通过 CONFIG 命令查看或设置配置项. Redis 命令不区分大小写. ...
- 【Flutter】功能型组件之跨组件状态共享
前言 在Flutter开发中,状态管理是一个永恒的话题. 一般的原则是:如果状态是组件私有的,则应该由组件自己管理:如果状态要跨组件共享,则该状态应该由各个组件共同的父元素来管理. 对于组 ...
- mount: /dev/sdxx already mounted or /xxxx busy解决方法
异常现象: 解决方法: 1. 輸入root的密碼,進入單用戶2. 重新掛載/目錄,使其變為可讀可寫 # mount –o rw,remount / 3. 修改/etc/fstab文件 ...