第 3 章 Flink 的用途
3.2 分阶段采用 Flink
尽管Flink 拥有非常丰富的功能,并能处理极为复杂的数据,但是没有必要 为了采用Flink 而彻底抛弃其他技术。流处理架构可以分步来实现。有些公 司在引入流处理架构时,先实现简单的应用程序,等到熟悉后再推广。虽 然试点应用程序的类型完全取决于公司的需求,但是许多公司都有相似的 流处理“价值链”。
接下来,让我们抓住机会深入了解Flink 能做什么以及它是如何做到的。第 4~6 章将介绍 Flink 的一些基本功能。
35
第 4 章
对时间的处理
用流处理器编程和用批处理器编程最关键的区别在于对时间的处理。举一 个非常简单的例子:计数。事件流数据(如微博内容、点击数据和交易数 据)不断产生,我们需要用key 将事件分组,并且每隔一段时间(比如一 小时)就针对每一个key 对应的事件计数。这是众所周知的“大数据”应 用,与MapReduce 的词频统计例子相似。
4.1 采用批处理架构和Lambda架构计数
尽管看起来简单,但是大规模的计数任务在实践中出人意料地困难。当然,
计数无处不在。针对联机分析处理多维数据集的聚合或其他操作,都可以 简单地归结为计数。图4-1 展示了如何采用传统的批处理架构实现计数 任务。
在该架构中,持续摄取数据的管道每小时创建一次文件。这些文件通常被 存储在HDFS 或 MapR-FS 等分布式文件系统中。像 Apache Flume 这样的 工具可以用于完成上述工作。由调度程序安排批处理作业(如MapReduce 作业)分析最近生成的一个文件(将文件中的事件按key 分组,计算每个 key 对应的事件数),然后输出计数结果。对于每个使用 Hadoop 的公司来 说,其集群都有多个类似的管道。
36 | 第 4 章
调度程序
时间
服务和存储
文件 1
文件 2
文件 3
作业 1
作业 2
作业 3
图4-1:用定期运行的批处理作业来实现应用程序的持续性。数据被持续地分割为 文件(如以一小时为单位);然后,批处理作业将文件作为输入,以此达到持续处 理数据的效果
这种架构完全可行,但是存在以下问题。
• 太多独立的部分。为了计算数据中的事件数,这种架构动用了太多系统。
每一个系统都有学习成本和管理成本,还可能存在bug。
• 对时间的处理方法不明确。假设需要改为每 30 分钟计数一次。这个变动 涉及工作流调度逻辑(而不是应用程序代码逻辑),从而使DevOps 问题 与业务需求混淆。
• 预警。假设除了每小时计数一次外,还需要尽可能早地收到计数预警(比 如在事件数超过10 时预警)。为了做到这一点,可以在定期运行的批 处理作业之外,引入Storm 来采集消息流(Kafka 或者 MapR Streams)。
Storm 实时提供近似的计数,批处理作业每小时提供准确的计数。但是 这样一来,就向架构增加了一个系统,以及与之相关的新编程模型。上 述架构叫作Lambda 架构,如图 4-2 所示。第 1 章有过简要的介绍。
对时间的处理 | 37
调度程序
流处理作业 时间
服务和存储
文件 1
文件 2
文件 3
作业 1
作业 2
作业 3
图4-2:Lambda 架构用定期运行的批处理作业来实现应用程序的持续性,并通过流 处理器获得预警。流处理器实时提供近似结果;批处理层最终会对近似结果予以纠正
• 乱序事件流。在现实世界中,大多数事件流都是乱序的,即事件的实际 发生顺序(事件数据在生成时被附上时间戳,如智能手机记录下用户登 录应用程序的时间)和数据中心所记录的顺序不一样。这意味着本属于 前一批的事件可能被错误地归入当前一批。批处理架构很难解决这个问 题,大部分人则选择忽视它。
• 批处理作业的界限不清晰。在该架构中,“每小时”的定义含糊不清,分 割时间点实际上取决于不同系统之间的交互。充其量也只能做到大约每 小时分割一次,而在分割时间点前后的事件既可能被归入前一批,也可 能被归入当前一批。将数据流以小时为单位进行分割,实际上是最简单 的方法。假设需要根据产生数据的时间段(如从用户登录到退出)生成 聚合结果,而不是简单地以小时为单位分割数据,则用如图4-1 和图 4-2 所示的架构无法直接满足需求。
38 | 第 4 章
(Kafka 或 MapR Streams)。消息传输系统为负责处理所有数据的流处理器(在本 例中是Flink)提供流数据,产生的结果既是实时的,也是正确的
DataStream<LogEvent> stream = env // 通过Kafka生成数据流
.addSource(new FlinkKafkaConsumer(...)) // 分组
.keyBy("country") // 将时间窗口设为60分钟 .timeWindow(Time.minutes(60)) // 针对每个时间窗口进行操作
.apply(new CountPerWindowFunction());
对时间的处理 | 39
Storm Trident 是这样实现微批处理的:它先创建一个大的 Storm 事件,其 中包含固定数量的子事件;然后将这些聚合在一起的子事件用持续运行的 Storm 拓扑处理。Spark Streaming 的微批处理架构和批处理架构本质上是一 致的,只不过对用户隐藏了前两步(摄取和存储),并将每份微批数据用预
40 | 第 4 章
提出的。团队成员包括Tyler Akidau 和 Frances Perry 等人。如果想了解更多关于 Dataflow 模型的内容,请参考 Tyler Akidau 的文章Streaming 101 和 Streaming 102。
Flink 处理时间和窗口的机制很大程度上来源于 Tyler Akidau 等人所著的关于 Dataflow 模型的论文。
对时间的处理 | 41
42 | 第 4 章
一分钟滚动窗口收集最近一分钟的数值,并在一分钟结束时输出总和,如 图4-5 所示。
输入
输出 滚动窗口
图4-5:一分钟滚动窗口计算最近一分钟的数值总和
一分钟滑动窗口计算最近一分钟的数值总和,但每半分钟滑动一次并输出 结果,如图4-6 所示。
输入
输出 滑动窗口
图4-6:一分钟滑动窗口每半分钟计算一次最近一分钟的数值总和
第一个滑动窗口对9、6、8 和 4 求和,得到 27。半分钟后,窗口滑动,然 后对8、4、7 和 3 求和,得到 22,照此类推。
在Flink 中,一分钟滚动窗口的定义如下。
stream.timeWindow(Time.minutes(1))
对时间的处理 | 43 每半分钟(即30 秒)滑动一次的一分钟滑动窗口如下所示。
stream.timeWindow(Time.minutes(1), Time.seconds(30))
4.4.2 计数窗口
Flink 支持的另一种常见窗口叫作计数窗口。采用计数窗口时,分组依据不 再是时间戳,而是元素的数量。例如,图4-6 中的滑动窗口也可以解释为由 4 个元素组成的计数窗口,并且每两个元素滑动一次。滚动和滑动的计数窗 口分别定义如下。
stream.countWindow(4) stream.countWindow(4, 2)
虽然计数窗口有用,但是其定义不如时间窗口严谨,因此要谨慎使用。时
图灵社区会员 ArthurWYS([email protected]) 专享 尊重版权
44 | 第 4 章
对时间的处理 | 45 重新处理
当前时间
图4-7:流处理架构拥有时空穿梭(即重新处理数据)的能力。流处理器支持事件时间,
这意味着将数据流“倒带”,用同一组数据重新运行同样的程序,会得到相同的结果
若要按时间回溯并正确地重新处理数据,流处理器必须支持 事件时间。
如果窗口的设定是根据系统时间而不是时间戳,那么每次运行同样的程序,
都会得到不同的结果。事件时间使数据处理结果具有确定性,因为用同一 组数据运行同样的程序,会得到相同的结果。
4.6 水印
支持事件时间对于流处理架构而言至关重要,因为事件时间能保证结果正 确,并使流处理架构拥有重新处理数据的能力。当计算基于事件时间时,
如何判断所有事件是否都到达,以及何时计算和输出窗口的结果呢?换言 之,如何追踪事件时间,并知晓输入数据已经流到某个事件时间了呢?为 了追踪事件时间,需要依靠由数据驱动的时钟,而不是系统时钟。
以图4-5 中的一分钟滚动窗口为例。假设第一个窗口从 10:00:00 开始(即 从10 时 0 分 0 秒开始),需要计算从 10:00:00 到 10:01:00 的数值总和。当 时间就是记录的一部分时,我们怎么知道10:01:00 已到呢?换句话说,我 们怎么知道盖有时间戳10:00:59 的元素还没到呢?
46 | 第 4 章
Flink 通过水印来推进事件时间。水印是嵌在流中的常规记录,计算程序通 过水印获知某个时间点已到。对于上述一分钟滚动窗口,假设水印标记时 间为10:01:00(或者其他时间,如 10:03:43),那么收到水印的窗口就知道 不会再有早于该时间的记录出现,因为所有时间戳小于或等于该时间的事
对时间的处理 | 47 Seyvet 和 Ignacio Mulas Viela 说道:
该架构在不断地适应(学习)新系统常态的同时,能够快速且准 确地发现异常。这使它成为理想工具,并能够极大地降低因大型 计算设施运行而产生的维护成本。
图4-8 展示了爱立信团队构建的数据管道。
数据源 Kafka
Kafka Elasticsearch Kibana Flink
图4-8:爱立信团队采用的基于 Flink 的流处理架构
注3: 本节内容基于 Nicolas Seyvet 和 Ignacio Mulas Viela 在 Strata + Hadoop World London 2016 研讨会上所做的演讲。Ignacio Mulas Viela 在 2015 年的 Flink Forward 研讨会 上也做过相关的演讲。
注4: https://www.oreilly.com/ideas/questioning-the-lambda-architecture
48 | 第 4 章
推送给Kafka 的原始数据是来自云基础设施中的所有实体机和虚拟机的遥 测信息和日志事件。它们经过不同的Flink 作业消费之后,被写回 Kafka 主 题里,然后再从Kafka 主题里被推送给搜索引擎 Elasticsearch 和可视化系统 Kibana。这种架构让每个 Flink 作业所执行的任务有清晰的定义,一个作业 的输出可以成为另一个作业的输入。图4-9 展示了异常检测管道,每个中间 流都是Kafka 主题(以分配给它的数据命名),每个长方形代表一个 Flink 作业。
原始数据
新奇点 统计信息
提取器
贝叶斯 异常检测
统计信息
图4-9:爱立信公司的异常检测管道采用 Flink 实现统计信息提取和异常检测 在本案例中,为什么Flink 对事件时间的支持很重要呢?有两个原因。
(1) 有助于准确地识别异常。时间对识别异常很重要。当许多日志事件在同 一时间出现时,通常说明可能有错误发生。为了将这些事件正确地分组 和归类,考虑它们的真实时间(而不是处理时间)很重要。
(2) 有助于采用流处理架构。在流处理架构中,所有的计算都由流处理器完
(2) 有助于采用流处理架构。在流处理架构中,所有的计算都由流处理器完