参考书籍编程
Stream Processing with Apache Flinkhttps://www.oreilly.com/library/view/stream-processing-with/9781491974285/缓存
《基于Apache Flink的流处理》https://book.douban.com/subject/34912177/安全
注:本文主要是针对《基于Apache Flink的流处理》的笔记服务器
1-8章笔记下载地址网络
本章的目标是介绍流处理的基本概念以及对其处理框架的要求。session
Dataflow程序一般表示为有向图,并发
图2-1显示了一个数据流程序,它从文章的输入流中提取并计数一些标签。app
像图2-1中的数据流图被称为逻辑图,由于它们传达了计算逻辑的高级视图。为了执行数据流程序,它的逻辑图被转换成物理Dataflow图,该图详细说明了程序是如何执行的。例如,若是咱们使用分布式处理引擎,每一个 算子可能有几个并行任务在不一样的物理机器上运行。负载均衡
图2-2显示了图2-1的逻辑图的物理数据流图。在逻辑Dataflow图中,节点表明算子,而在物理Dataflow图中,节点表明任务。每一个任务负责计算一部分的输入数据。框架
能够以不一样的方式利用数据流图中的并行性。
首先,能够对某个算子的输入数据进行分区,并在数据子集上并行执行相同操做的任务。这种类型的并行称为数据并行。数据并行很是有用,由于它容许将大量计算数据分布到多个不一样的物理节点上并行执行。
其次,可让不一样算子的任务 并行执行相同或不一样数据的计算。这种类型的并行称为任务并行。使用任务并行,能够更好地利用集群的计算资源。
数据交换策略定义了数据项如何被分配给物理Dataflow图中的不一样任务。在这里,咱们简要介绍一些常见的数据交换策略,如图2-3所示。
下面看看如何将Dataflow的概念运用到并行数据流处理中。咱们先给出数据流的定义:数据流是一个长度可能无限长的事件序列
数据流的例子以下:监控器产生的监控数据、传感器产生的测量数据、信用卡交易数据、气象站观测数据、搜索引擎搜索记录等
对于批处理应用程序,咱们一般关心做业的总执行时间,或者咱们的处理引擎读取输入、执行计算和写回结果须要多长时间。因为流应用程序连续运行,而且输入多是无限的,所以在流处理中没有总执行时间的概念。取而代之的是,流处理必须尽量快地为传入数据提供结果(延迟),同时还要应对很高的事件输入速率(吞吐)。咱们用延迟和吞吐来表示这两方面的性能需求。
延迟表示处理一个事件所需的时间。本质上,它是接收事件到在输出中看到事件处理效果的时间间隔。
在数据流中,延迟以时间为单位进行衡量,例如毫秒。根据应用程序的不一样,可能会关心平均延迟、最大延迟或百分比延迟。例如,10ms的平均延迟意味着平均在10ms内处理事件。或者,10毫秒的95%延迟值意味着95%的事件在10毫秒内获得处理。
像Apache Flink这样的现代流处理引擎能够提供低至几毫秒的延迟。
吞吐量是对系统处理能力的一种度量——它的处理速率。也就是说,吞吐量告诉咱们系统每单位时间能够处理多少个事件。
须要注意的是,处理的速率取决于事件到达速率;低吞吐量不必定表示性能差。在流式系统中,一般但愿确保系统可以处理最大的预期事件到达速率。也就是说,主要关心的是肯定峰值吞吐量,即系统处于最大负载时的性能限制。
一旦事件到达速率超过了预期的最大值,咱们就不得不开始缓冲事件。若是系统继续以超过其处理能力的接收速率接收数据,缓冲区可能会变得不可用,数据可能会丢失。这种状况一般被称为背压。
此时,应该清楚的是,延迟和吞吐 不是独立的指标。
下降延迟可提升吞吐量。若是一个系统能够更快地执行操做,它能够在相同的时间内执行更多的操做。而一个很好的方式就是并行处理
流处理引擎一般提供一组内置操做来接收、转换和输出数据流。这些操做能够用来构成Dataflow图来表明流式应用的逻辑。在本节中,咱们将介绍最常见的流式操做。
操做能够是无状态的,也能够是有状态的。
数据接入和数据输出操做容许流处理器与外部系统通讯。
数据接入是从外部系统 获取原始数据并将其转换为适合处理格式的操做。实现数据接入逻辑的算子称为数据源。
数据输出是以适合外部系统使用的形式产生输出的操做。实现数据输出的算子称为数据汇,
转换操做是单程操做(single-pass),每一个事件都独立处理。操做一个接一个地处理事件,并对事件数据进行一些转换,产生一个新的输出流。通常来讲,转换操做比较简单,不用维护内部状态
转换操做的算子能够接受多个输入并产生多个输出流。他们还能够经过将一个流分红多个流或将多个流合并成一个流来修改数据流图的结构。
滚动聚合是针对每一个输入事件不断更新的聚合操做,好比总和、最小值和最大值。聚合操做是有状态的,并将当前状态与传入事件相结合以生成新的聚合值。图2-5显示了一个滚动最小聚合。操做符保持当前的最小值,并针对每一个传入事件相应地更新它。
转换和滚动聚合 每次处理一个事件,以生成输出事件并更新状态。可是,有些操做必须收集和缓存事件。例如求中位数的函数。为了在无限流上高效地计算这些操做,须要限制这些操做维护的数据量。在本节中,咱们将讨论窗口操做。
窗口还支持在数据流上进行一些有趣的查询。例如:若是有一个为司机提供实时交通讯息的应用程序。在这个场景中,您想知道在过去几分钟内某个位置是否发生了拥堵。这时候咱们只关注过去几分钟这个窗口的数据。
窗口操做不断地从一个无界事件流中建立 长度有限的事件集(称为桶),并让咱们对这些桶 执行计算。事件一般根据数据属性或时间分配到桶中。窗口的行为由一组策略定义。窗口策略决定什么时候建立新的存储桶,将哪些事件分配给哪些存储桶,以及什么时候计算桶中的数据。窗口策略的指定能够基于时间、数量或其余数据属性
下面介绍常见的窗口类型的语义
滚动窗口将事件分配到长度固定的不重叠的桶中。当窗口边界经过时,全部事件都被发送到一个计算函数进行处理。基于计数的滚动窗口定义了在触发评估以前收集了多少事件。图2-6显示了一个基于计数的滚动窗口,它将输入流分到四个元素组成的桶。基于时间的滚动窗口定义了桶中事件的时间间隔。图2-7显示了一个基于时间的滚动窗口,它将事件收集到桶中,并每10分钟触发一次计算。
滑动窗口将事件分配到固定大小的容许互相重叠的桶中。所以,一个事件 可能属于多个桶。咱们经过指定桶的长度和滑动间隔来定义滑动窗口。图2-8中的窗口长度为4,滑动间隔为3。
会话窗口在常见的现实场景中很是有用,在这些场景中,滚动窗口和滑动窗口都不能应用。考虑一个分析在线用户行为的应用程序。在这样的应用程序中,咱们但愿未来自同一会话的事件分到一组。
会话窗口根据会话间隔(session gap)对事件进行分组,会话间隔定义了认为会话已关闭的非活动时间。(也就是若是用户在很长的一段时间内没有与服务器通讯就认为他的会话已经关闭了)
窗口操做与流处理中的两个主要概念密切相关:时间语义和状态管理。
处理时间是机器上本地时钟的时间。处理时间窗口包括在一段时间内** 碰巧到达窗口的全部事件,由机器的本地时钟测量。如图2-12所示
事件时间是流中的事件实际发生的时间。事件时间经过附加到流事件的时间戳来判断。
图2-13显示:即便事件有延迟,事件时间窗口也能准确地把事件分配到正确的窗口中,从而反映事情发生的真实状况。
不管数据流的处理速度有多快,事件到达算子的顺序是怎样的,事件时间窗口的计算将产生相同的结果。
经过依赖事件时间,即便是在无序数据的状况下,咱们也能够保证结果的正确性。此外,当与可重放的流结合时,时间戳的肯定性使你可以回到过去。也就是说,你能够重放一个流并分析历史数据,就像事件是实时发生的同样。
到目前为止,在咱们关于事件时间窗口的讨论中,咱们忽略了一个很是重要的方面:咱们如何决定事件时间窗口的触发时机(何时中止收集并开始计算)?也就是说,咱们要等多久才能肯定咱们已经收到了某个时间点以前发生的全部事件?考虑到分布式系统的不可预测性和由外部带来的各类延迟,这些问题没有绝对正确的答案。
水位线(watermark)是一种全局进度度量,它是一个时间点。它代表咱们确信这个时间点以前的事件所有到达了。本质上,水位线提供了一个逻辑时钟,通知系统当前的事件时间。当操做员收到时间为T的水位线时,能够假设不会再收到时间戳小于T的事件。水位线对于事件时间窗口和处理无序事件的算子都是必不可少的。
水位线提供告终果可信度和延迟之间trade-off。
流处理系统会提供某种机制来处理在水位线以后到达的事件。
此刻你可能会想,既然事件时间解决了咱们全部的问题,为何咱们还要去关心处理时间?
事实是,在某些状况下,处理时间确实颇有用。
状态在数据处理中无处不在。任何复杂一点的计算都须要它。为了产生结果,函数在一段时间或多个事件上累积状态(例如,计算汇集或检测模式)。有状态算子使用传入事件和内部状态来计算它们的输出并更新状态。
在连续运行的流做业中,状态在事件之间是持久的,咱们能够在编程模型中将其做为一级公民公开。而在以前的批处理中,后一个批次的数据是看不到前一个批次的数据的。
因为流操做引擎有可能处理的是无限流,所以应当心不要让内部状态无限增加。为了限制状态的大小,算子一般会对到目前为止看到的事件进行某种总结或概要。这样的摘要能够是计数、总和、迄今为止所看到的事件的抽样、窗口缓冲区。
支持有状态算子会带来不少实现上的挑战:
流式做业中的算子状态很是重要,应防止出现故障。若是状态在故障期间丢失,恢复后的结果将是不正确的。流处理引擎不只须要保证在出现任务故障时能够正常运行,还须要保证结果和算子状态的正确性。
对于输入流中的每一个事件,任务执行如下步骤:
在这些步骤中的任何一个均可能发生故障,系统必须清楚地定义其在每种故障场景中的如何处理。例如,一个定义完整的流式处理系统须要明确如下问题:若是任务在第一步失败,事件会丢失吗?若是在更新了内部状态后失败了,恢复后还会再更新吗?而在上面这些状况下,输出仍是正确的吗?
在批处理场景中,全部这些问题都获得了回答,由于批处理做业能够简单地从头开始从新启动。所以,没有事件丢失,状态彻底是从零开始创建的。然而,在流处理中,这些问题很棘手。流式系统经过提供结果保障(result guarantee)来定义它们在出现故障时的行为。接下来,咱们回顾了现代流处理引擎提供的几种不一样级别的结果保障。
当任务失败时,最简单的方法就是不作任何事情来恢复丢失的状态和重放丢失的事件。至多一次只保证每一个事件至多处理一次。换句话说,系统能够简单地丢弃事件,不作任何事情来确保结果的正确性。这种类型的保障也被称为“无保障”,由于即便是系统丢弃全部事件也能够提供这种保证。
在大多数现实世界的应用程序中,人们指望事件不会丢失。这种类型的保证被称为至少一次,这意味着全部事件都将被处理,而且其中一些事件有可能被处理屡次。若是应用程序的正确性仅取决于信息的完整性,重复处理多是能够接受的。
为了确保至少一次这种结果保障,须要有一种方法来重放(replay)事件——要么从源(source),要么从某个缓冲区(buffer)。
下面介绍两种保证至少一次的方式
精确一次是最严格的保证,也很难实现。它意味着不只不会有事件丢失,并且每一个事件只容许处理一次。从本质上来讲,精确一次意味着咱们的应用程序将提供彻底正确的结果,就好像从未发生过失败同样。
精确一次是以致少一次为前提的,所以数据重放机制必不可少。
并且在故障恢复以后,处理引擎应该知道一个事件的更新是否已经反映在状态上。有两种实现方式:
事务性更新是实现这一结果的一种方式,可是它们会致使大量的性能开销。
相反,Flink使用轻量级快照机制来实现一次结果保证
端到端保证指的是整个数据处理流水线上的结果正确性。流水线上的每一个组件都提供本身的保证,完整管道的端到端保证将由全部组件中最弱的那个组件来决定。有时候弱的保障可能会表现出强的语义,好比,你使用至少一次来求最大值或者最小值,管道的其余组件都使用精确一次,那么这个管道也是端到端精确一次的。