事件驱动的应用

处理函数

简介

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的更多相关文章

  1. Flink-v1.12官方网站翻译-P005-Learn Flink: Hands-on Training

    学习Flink:实践培训 本次培训的目标和范围 本培训介绍了Apache Flink,包括足够的内容让你开始编写可扩展的流式ETL,分析和事件驱动的应用程序,同时省略了很多(最终重要的)细节.本书的重 ...

  2. Flink-v1.12官方网站翻译-P025-Queryable State Beta

    可查询的状态 注意:可查询状态的客户端API目前处于不断发展的状态,对所提供接口的稳定性不做保证.在即将到来的Flink版本中,客户端的API很可能会有突破性的变化. 简而言之,该功能将Flink的托 ...

  3. Flink-v1.12官方网站翻译-P015-Glossary

    术语表 Flink Application Cluster Flink应用集群是一个专用的Flink集群,它只执行一个Flink应用的Flink作业.Flink集群的寿命与Flink应用的寿命绑定. ...

  4. Flink-v1.12官方网站翻译-P008-Streaming Analytics

    流式分析 事件时间和水印 介绍 Flink明确支持三种不同的时间概念. 事件时间:事件发生的时间,由产生(或存储)该事件的设备记录的时间 摄取时间:Flink在摄取事件时记录的时间戳. 处理时间:您的 ...

  5. Flink-v1.12官方网站翻译-P004-Flink Operations Playground

    Flink操作训练场 在各种环境中部署和操作Apache Flink的方法有很多.无论这种多样性如何,Flink集群的基本构件保持不变,类似的操作原则也适用. 在这个操场上,你将学习如何管理和运行Fl ...

  6. Flink-v1.12官方网站翻译-P002-Fraud Detection with the DataStream API

    使用DataStream API进行欺诈检测 Apache Flink提供了一个DataStream API,用于构建强大的.有状态的流式应用.它提供了对状态和时间的精细控制,这使得高级事件驱动系统的 ...

  7. Flink-v1.12官方网站翻译-P019-Generating Watermarks

    生成水印 在本节中,您将了解 Flink 提供的 API,用于处理事件时间时间戳和水印.关于事件时间.处理时间和摄取时间的介绍,请参考事件时间的介绍. 水印策略介绍 为了使用事件时间,Flink需要知 ...

  8. Flink-v1.12官方网站翻译-P018-Event Time

    事件时间 在本节中,您将学习如何编写时间感知的Flink程序.请看一下及时流处理,了解及时流处理背后的概念. 关于如何在Flink程序中使用时间的信息请参考windowing和ProcessFunct ...

  9. Flink-v1.12官方网站翻译-P016-Flink DataStream API Programming Guide

    Flink DataStream API编程指南 Flink中的DataStream程序是对数据流实现转换的常规程序(如过滤.更新状态.定义窗口.聚合).数据流最初是由各种来源(如消息队列.套接字流. ...

随机推荐

  1. flume集成hdfs(hdfs开启kerberos认证)

    )当 sink 到 hdfs 时: ) 需修改 flume-env.sh 配置,增添 hdfs 依赖库: FLUME_CLASSPATH="/root/TDH-Client/hadoop/h ...

  2. 解决FastJson循环引用的问题

    temp 本来被循环引用,运行方法报错. 解决方法:对temp进行处理 SerializerFeature feature = SerializerFeature.DisableCircularRef ...

  3. [ABP教程]第六章 作者:领域层

    Web开发教程6 作者:领域层 关于此教程 在这个教程系列中,你将要构建一个基于ABP框架的应用程序 Acme.BookStore.这个应用程序被用于甘丽图书页面机器作者.它将用以下开发技术: Ent ...

  4. 腾讯消息队列CMQ部署与验证

    环境 IP 备注 192.168.1.66 node1 前置机 192.168.1.110 node2 192.168.1.202 node3 架构图 组件介绍 组件 监听端口 access 1200 ...

  5. 知识图谱和neo4j的基本操作

    一.知识图谱的简介 1.知识图谱是什么 知识图谱本质上是语义网络(Semantic Network)的知识库 可以理解为一个关系图网络. 2.什么是图 图(Graph)是由节点(Vertex)和边(E ...

  6. zabbix v3.0安装部署【转】

    关于zabbix及相关服务软件版本: Linux:oracle linux 6.5 nginx:1.9.15 MySQL:5.5.49 PHP:5.5.35 一.安装nginx: 安装依赖包: yum ...

  7. STM32 HAL库之串口详细篇

    一.基础认识 (一) 并行通信 原理:数据的各个位同时传输 优点:速度快 缺点:占用引脚资源多,通常工作时有多条数据线进行数据传输 8bit数据传输典型连接图: 传输的数据是二进制:11101010, ...

  8. 【Redis3.0.x】配置文件

    Redis3.0.x 配置文件 概述 Redis 的配置文件位于Redis安装目录下,文件名为 redis.conf. 可以通过 CONFIG 命令查看或设置配置项. Redis 命令不区分大小写. ...

  9. 【Flutter】功能型组件之跨组件状态共享

    前言   在Flutter开发中,状态管理是一个永恒的话题.   一般的原则是:如果状态是组件私有的,则应该由组件自己管理:如果状态要跨组件共享,则该状态应该由各个组件共同的父元素来管理.   对于组 ...

  10. mount: /dev/sdxx already mounted or /xxxx busy解决方法

    异常现象: 解决方法: 1.    輸入root的密碼,進入單用戶2.    重新掛載/目錄,使其變為可讀可寫 # mount –o rw,remount / 3.    修改/etc/fstab文件 ...