第 1 章 为何选择 Flink
- 许多情况下,人们希望用低延迟或者实时的流处理来获得数据的高时效性,前提是流处理本身是准确且高效的
- 优秀的流处理技术可以容错,而且能保证
exactlyonce
2 Storm
提供了低延迟的流处理,但是它为实时性付出了一些代价:很难实现高吞吐,并且其正确性没能达到通常所需的水平。换句话说,它并不能保证exactlyonce
;即便是它能够保证的正确性级别,其开销也相当大
图12:
Flink
的一个优势是,它拥有诸多重要的流式计算功能。其他项目为了实现这些功能,都不得不付出代价。比如,Storm
实现了低延迟,但是在作者撰写本书时还做不到高吞吐,也不能在故障发生时准确地处理计算状态;SparkStreaming
通过采用微批处理方法实现了高吞吐和容错性,但是牺牲了低延迟和实时处理能力,也不能使窗口与自然时间相匹配,并且表现力欠佳
- “
ApacheFlink
是为分布式、高性能、随时可用以及准确的流处理应用程序打造的开源流处理框架。”Flink
不仅能提供同时支持高吞吐和exactlyonce
语义的实时计算,还能提供批量数据处理 flink
一词表示快速和灵巧。项目采用一只松鼠的彩色图案作为logo
,这不仅因为松鼠具有快速和灵巧的特点,还因为柏林的松鼠有一种迷人的红棕色- 2014年12月一跃成为
Apache
软件基金会的顶级项目。作为Apache
软件基金会的5个最大的大数据项目之一,Flink
在全球范围内拥有200多位开发人员,以及若干公司中的诸多上线场景,有些甚至是世界500强的公司 Flink
是如何同时实现批处理与流处理的呢?答案是,Flink
将批处理(即处理有限的静态数据)视作一种特殊的流处理FlinkRuntime
执行引擎可以作为YARN
(YetAnotherResourceNegotiator
)的应用程序在集群上运行,也可以在Mesos
集群上运行,还可以在单机上运行(这对于调试Flink
应用程序来说非常有用)
图14:
Flink
技术栈的核心组成部分。值得一提的是,Flink
分别提供了面向流处理的接口(DataStreamAPI
)和面向批处理的接口(DataSetAPI
)。因此,Flink
既可以完成流处理,也可以完成批处理。Flink
支持的拓展库涉及机器学习(FlinkML
)、复杂事件处理(CEP
),以及图计算(Gelly
),还有分别针对流处理和批处理的TableAPI
Flink
解决了许多问题,比如保证了exactlyonce
语义和基于事件时间的数据窗口。开发人员不再需要在应用层解决相关问题,这大大地降低了出现bug
的概率- 不用再在编写应用程序代码时考虑如何解决问题,所以工程师的时间得以充分利用,整个团队也因此受益。好处并不局限于缩短开发时间,随着灵活性的增加,团队整体的开发质量得到了提高,运维工作也变得更容易、更高效
布衣格电信 支持真正的流处理——通过上层的
API
和下层的执行引擎都能实时进行流处理,这满足了我们对可编程性和低延迟的需求。此外,使用Flink
,我们的系统得以快速上线,这是其他任何一种方案都做不到的。如此一来,我们就有了更多的人手开发新的业务逻辑
ETL
是Extract
、Transform
和Load
的缩写,即抽取、转换和加载
第 2 章 流处理架构
- 以流为基础的架构设计让数据记录持续地从数据源流向应用程序,并在各个应用程序间持续流动。没有一个数据库来集中存储全局状态数据,取而代之的是共享且永不停止的流数据,它是唯一正确的数据源,记录了业务数据的历史。在流处理架构中,每个应用程序都有自己的数据,这些数据采用本地数据库或分布式文件进行存储
消息传输层和流处理层
- 如何有效地实现流处理架构并从
Flink
中获益呢?一个常见的做法是设置消息传输层和流处理层 - (1)消息传输层从各种数据源(生产者)采集连续事件产生的数据,并传输给订阅了这些数据的应用程序和服务(消费者)
- (2)流处理层有3个用途:
- 持续地将数据在应用程序和系统间移动;
- 聚合并处理事件;
- 在本地维持应用程序的状态
图21:
Flink
项目的架构有两个主要组成部分:消息传输层和由Flink
提供的流处理层。消息传输层负责传输连续事件产生的消息,能够提供消息传输的系统包括Kafka
和MapRStreams
。MapRStreams
是MapR
融合数据平台的一个主要组成部分,它兼容KafkaAPI
- 兼具高性能和持久性对于消息传输系统来说至关重要;
Kafka
和MapRStreams
都可以满足这个需求 - 具有持久性的好处之一是消息可以重播
第 3 章 Flink 的用途
Flink
解决了可能影响正确性的几个问题,包括如何在故障发生之后仍能进行有状态的计算Flink
所用的技术叫作检查点(checkpoint
)- 在每个检查点,系统都会记录中间计算状态,从而在故障发生时准确地重置。这一方法使系统以低开销的方式拥有了容错能力——当一切正常时,检查点机制对系统的影响非常小
Flink
还承担了跟踪计算状态的任务,从而减轻了开发人员的负担,简化了编程工作,并提高了应用程序的成功率。用同一种技术来实现流处理和批处理,大大地简化了开发和运维工作
第 4 章 对时间的处理
- 用流处理器编程和用批处理器编程最关键的区别在于对时间的处理。举一个非常简单的例子:计数。事件流数据(如微博内容、点击数据和交易数据)不断产生,我们需要用
key
将事件分组,并且每隔一段时间(比如一小时)就针对每一个key
对应的事件计数。这是众所周知的“大数据”应用,与MapReduce
的词频统计例子相似 - 流处理区别于批处理最主要的两点是:
- 流即是流,不必人为地将它分割为文件;
- 时间的定义被明确地写入应用程序代码(如以上代码的时间窗口),而不是与摄取、计算和调度等过程牵扯不清
- 流处理系统中的批处理必须符合以下两点要求
- 批处理只作为提高系统性能的机制。批量越大,系统的吞吐量就越大
- 为了提高性能而使用的批处理必须完全独立于定义窗口时所用的缓冲,或者为了保证容错性而提交的代码,也不能作为
API
的一部分。否则,系统将受到限制,并且变得脆弱且难以使用
- 在流处理中,主要有两个时间概念
- 事件时间,即事件实际发生的时间。更准确地说,每一个事件都有一个与它相关的时间戳,并且时间戳是数据记录的一部分(比如手机或者服务器的记录)。事件时间其实就是时间戳
- 处理时间,即事件被处理的时间。处理时间其实就是处理事件的机器所测量的时间
图4-4:事件时间顺序与处理时间顺序不一致的乱序事件流
- 窗口是一种机制,它用于将许多事件按照时间或者其他特征分组,从而将每一组作为整体进行分析(比如求和)
- 时间窗口是最简单和最有用的一种窗口。它支持滚动和滑动。举一个例子,假设要对传感器输出的数值求和
图45:一分钟滚动窗口计算最近一分钟的数值总和
图46:一分钟滑动窗口每半分钟计算一次最近一分钟的数值总和
- 在
Flink
中,一分钟滚动窗口的定义如下 Flink
支持的另一种常见窗口叫作计数窗口。采用计数窗口时,分组依据不再是时间戳,而是元素的数量。例如,图46中的滑动窗口也可以解释为由4个元素组成的计数窗口,并且每两个元素滑动一次。滚动和滑动的计数窗口分别定义如下- 虽然计数窗口有用,但是其定义不如时间窗口严谨,因此要谨慎使用
- 一种解决办法是用时间窗口来触发超时
Flink
支持的另一种很有用的窗口是会话窗口- 会话指的是活动阶段,其前后都是非活动阶段,例如用户与网站进行一系列交互(活动阶段)之后,关闭浏览器或者不再交互(非活动阶段)。会话需要有自己的处理机制,因为它们通常没有固定的持续时间(有些30秒就结束了,有些则长达一小时),或者没有固定的交互次数(有些可能是3次点击后购买,另一些可能是40次点击却没有购买)
- 每一个默认窗口都有一个触发器。例如,采用事件时间的时间窗口将在收到水印时被触发。对于用户来说,除了收到水印时生成完整、准确的结果之外,也可以实现自定义的触发器(例如每秒提供一次近似结果)
- 在
Flink
内部,所有类型的窗口都由同一种机制实现 - 开窗机制与检查点机制(第5章将详细讨论)完全分离。这意味着窗口时长不依赖于检查点间隔。事实上,窗口完全可以没有“时长”(比如上文中的计数窗口和会话窗口的例子)
- 高级用户可以直接用基本的开窗机制定义更复杂的窗口形式(如某种时间窗口,它可以基于计数结果或某一条记录的值生成中间结果)
- 时空穿梭意味着将数据流倒回至过去的某个时间,重新启动处理程序,直到处理至当前时间为止。像
Kafka
和MapRStreams
这样的现代传输层,支持时空穿梭,这使得它们与更早的解决方案有所区别 Flink
通过水印来推进事件时间。水印是嵌在流中的常规记录,计算程序通过水印获知某个时间点已到- 在
Flink
中,水印由应用程序开发人员生成,这通常需要对相应的领域有一定的了解。完美的水印永远不会错:时间戳小于水印标记时间的事件不会再出现 - 设定水印通常需要用到领域知识。举例来说,如果知道事件的迟到时间不会超过5秒,就可以将水印标记时间设为收到的最大时间戳减去5秒。另一种做法是,采用一个
Flink
作业监控事件流,学习事件的迟到规律,并以此构建水印生成模型 - 该架构在不断地适应(学习)新系统常态的同时,能够快速且准确地发现异常。这使它成为理想工具,并能够极大地降低因大型计算设施运行而产生的维护成本
图48展示了爱立信团队构建的数据管道
- 推送给
Kafka
的原始数据是来自云基础设施中的所有实体机和虚拟机的遥测信息和日志事件。它们经过不同的Flink
作业消费之后,被写回Kafka
主题里,然后再从Kafka
主题里被推送给搜索引擎Elasticsearch
和可视化系统Kibana
。这种架构让每个Flink
作业所执行的任务有清晰的定义,一个作业的输出可以成为另一个作业的输入
第 5 章 有状态的计算
- 流式计算分为无状态和有状态两种情况。无状态的计算观察每个独立事件,并根据最后一个事件输出结果
- 有状态的计算则会基于多个事件输出结果
- 第4章讨论的所有类型的窗口。例如,计算过去一小时的平均温度,就是有状态的计算
- 所有用于复杂事件处理的状态机。例如,若在一分钟内收到两个相差20度以上的温度读数,则发出警告,这是有状态的计算
- 流与流之间的所有关联操作,以及流与静态表或动态表之间的关联操作,都是有状态的计算
- 无状态流处理分别接收每条记录(图中的黑条),然后根据最新输入的记录生成输出记录(白条)
- 有状态流处理会维护状态(根据每条输入记录进行更新),并基于最新输入的记录和当前的状态值生成输出记录(灰条)
图5-1:无状态流处理与有状态流处理的区别。输入记录由黑条表示。无状态流处理每次只转换一条输入记录,并且仅根据最新的输入记录输出结果(白条)。有状态流处理维护所有已处理记录的状态值,并根据每条新输入的记录更新状态,因此输出记录(灰条)反映的是综合考虑多个事件之后的结果
- 在流处理中,一致性分为3个级别
atmostonce
:这其实是没有正确性保障的委婉说法——故障发生之后,计数结果可能丢失atleastonce
:这表示计数结果可能大于正确值,但绝不会小于正确值。也就是说,计数程序在发生故障后可能多算,但是绝不会少算exactlyonce
:这指的是系统保证在发生故障后得到的计数结果与正确值一致
Flink
的一个重大价值在于,它既保证了exactlyonce
,也具有低延迟和高吞吐的处理能力
图5-2:数环状项链上的珠子看上去毫无意义(甚至有些徒劳无功,因为可以永不停歇地计数),但是它可以用来很好地类比处理永不结束的事件流。在某些文化中,人们仍旧将数珠子视作消磨时间的好方法
- 在项链上每隔一段就松松地系上一根有色皮筋,将珠子分隔开;当珠子被拨动的时候,皮筋也可以被拨动;然后,你安排一个助手,让他在你和朋友拨到皮筋时记录总数。用这种方法,当有人数错时,就不必从头开始数。相反,你向其他人发出错误警示,然后你们都从上一根皮筋处开始重数,助手则会告诉每个人重数时的起始数值,例如在粉色皮筋处的数值是多少
- 按照输入记录的第一个字段(一个字符串)进行分组并维护第二个字段的计数状态
- 该程序有两个算子:
keyBy
算子用来将记录按照第一个元素(一个字符串)进行分组,根据该key
将数据进行重新分区,然后将记录再发送给下一个算子:有状态的map
算子(mapWithState
)。map
算子在接收到每个元素后,将输入记录的第二个字段的数据加到现有总数中,再将更新过的元素发射出去
图5-3:程序的初始状态。注意,
a
、b
、c
三组的初始计数状态都是0,即三个圆柱上的值。ckpt
表示检查点屏障。每条记录在处理顺序上严格地遵守在检查点之前或之后的规定,例如["b
",2]在检查点之前被处理,["a
",2]则在检查点之后被处理
图5-4:当
Flink
数据源(在本例中与keyBy
算子内联)遇到检查点屏障时,它会将其在输入流中的位置保存到稳定存储中。这让Flink
可以根据该位置重启输入
图5-6:检查点操作完成,状态和位置均已备份到稳定存储中。输入流中的所有记录都已处理完成。值得注意的是,备份的状态值与实际的状态值是不同的。备份反映的是检查点的状态
Flink
检查点算法的正式名称是异步屏障快照(asynchronousbarriersnapshotting
)。该算法大致基于ChandyLamport
分布式快照算法- 检查点由
Flink
自动生成,用来在故障发生时重新处理记录,从而修正状态。Flink
用户还可以通过另一个特性有意识地管理状态版本,这个特性叫作保存点(savepoint
) - 保存点与检查点的工作方式完全相同,只不过它由用户通过
Flink
命令行工具或者Web
控制台手动触发,而不由Flink
自动触发。和检查点一样,保存点也被保存在稳定存储中 - 对保存点的另一种理解是,它在明确的时间点保存应用程序状态的版本
图5-9:手动触发的保存点(以圆圈表示)在不同时间捕获正在运行的
Flink
应用程序的状态
图5-10:使用保存点更新
Flink
应用程序的版本。新版本可以从旧版本生成的一个保存点处开始执行
- 保存点可用于应对流处理作业在生产环境中遇到的许多挑战
- 应用程序代码升级
Flink
版本更新- 维护和迁移
- 假设模拟与恢复
A/B
测试
图5-11:在该应用程序架构中,有状态的 Flink 应用程序消费来自消息队列的数据,然后将数据写入输出系统,以供查询 。底部的详情图展示 了 Flink 应用程序的内部情况
图5-14:Yahoo!Streaming Benchmark 结果。横轴表示每秒的事件吞吐量,以千为单位。纵轴表示端到端的99百分位数延迟,以秒为单位。 在性能测评中,Spark Streaming 遇到了吞吐量和延迟性难两全的问题。随着批处理作业规模的增加,延迟升高。如果为了降低延迟而缩减规模,吞吐量就会减少。Storm 和 Flink 则可以在吞吐量增加时维持低延迟
图5-16:使用高吞吐数据生成器的结果
- 当Storm 和 Kafka 一起使用时,应用程序可以保持每秒40万事件的处理速度,并且瓶颈在于 CPU
- 当 Flink 和 Kafka 一起使用时,应用程序可以保持每秒300万事件的处理速度,并且瓶颈在于网络
- 当消除网络瓶颈时,Flink 应用程序可以保持每秒1500万事件的处理速度
- 在额外的测试中,消除队列由 MapR Streams提供,并且采用10个高性能网络节点;Flink 应用程序可以保持每秒1000万事件的处理速度
- 通过避免流处理瓶颈,同时利用 Flink 的有状态流处理能力,可以使吞吐量达到 Storm 的30倍左右 ,同时还能保证
exactly-once
和高可用性
第 6 章 批处理:一种特殊的流处理
- 如果计算结果不在执行过程中连续生成,而仅在末尾处生成一次,那就是批处理(分批处理数据)
- 批处理是流处理的一种非常特殊的情况。在流处理是,我们为数据定义滑动窗口或滚动窗口,并且在每次窗口滑动或滚动时生成结果 。批处理则不同,我们定义一个全局窗口,所有的记录都属于同一个窗口
图64:分布式排序的处理阶段
进一步使用 Flink
- Https://flink.apache.org有『快速入门』指南,通过例子教你如何使用 Flink 摄取和分析维基百科的编辑日志。只需花几分钟,你就可以开始编写你的第一个流处理程序了。
- 如果你偏爱视觉效果,可以看看 MapR 公司提供的例子:如何用 Flink 摄取纽约市出租车路线的数据流,并用 Kibana 将它可视化(https://www.mapr.com/blog/essential-guide-streaming-first-processing-apache-flink)