Drizzle:一种在大型集群快速、自适应的流式处理

Drizzle:一种在大型集群快速、自适应的流式处理

大规模流媒体系统旨在提供高吞吐量和低延迟。它们经常被用来运行任务关键型应用程序,必须全天候可用。因此,这些系统需要适应工作量的变化和故障,这样做可以对延迟影响最小。不幸的是,已有的解决方案要求操作在实现低延迟在正常操作期间和引起最小影响在适应期间进行权衡。record-at-a-time流式传输系统,如Naiad和Flink,提供在正常执行期间低延迟但在适应期间的高延迟(例如,恢复),而微批系统,如SparkStreaming和FlumeJava以在正常操作期间的高延迟的代价实现。我们提出了一种混合流式系统Drizzle来统一两种模型的好处,以提供在正常操作期间和适应期间的低延迟。

我们的目标是实现流式工作负载的毫秒级执行,工作负载和集群属性低频变化。基于这种见解,Drizzle将执行粒度从协调粒度分离出来。我们在EC2上的128节点集群进行了实验显示Drizzle可以实现端到端的记录处理延迟小于100毫秒,相比Spark可以在低延迟方面可以提升3.5倍。与Flink(一种record-at-a-time流式系统)相比,Drizzle可以从故障中恢复的速度比Flink快4倍并且Drizzle在恢复期间的低延迟降低了13倍。

1 介绍

数据分析中的最新趋势表明了广泛采用的流处理工作负载需要低延迟和高吞吐量的执行。此类工作负载的示例包括社交媒体应用程序,例如,处理推文流,执行实时物体识别或聚合来自数十亿传感器的更新。为流式处理设计的系统经常处理数百万每台机器每秒发生的事件,旨在提供亚秒级处理延迟。

除性能要求外,由于流处理工作负载全天候部署,因此这些系统需要处理群集或数据属性的变化。变化可能包括软件,机器或磁盘故障,这些在大型集群中每隔几个小时发生一次,落后的任务,可以使工作减慢6-8倍和不同的工作量模式可能带来更多峰值和非峰值之间的负载差异超过10倍的持续时间。

为了处理这种变化,数据处理系统必须具有适应性,即动态改变节点上操作的执行,并在执行时更新执行计划确保一致的结果。考虑到这些变化的频繁性,为了满足性能SLA,系统不仅需要在正常情况下实现低延迟执行,而且在系统适应时同样需要。不幸的是,现有解决方案在低延迟在正常操作期间和适应期间之间进行权衡。我们接下来讨论这个权衡。

最近有两种执行模型出现在大数据处理中。 Naiad等系统和Apache Flink使用record-at-a-time模型,操作依靠可变状态进行合并来自多个记录的信息。相反,像ApacheSpark和FlumeJava这样的批量同步处理(BSP)系统分批处理数据
记录并依赖明确的屏障和沟通来合并记录中的信息。因此,正常运行期间,record-at-a-time系统可以提供比BSP系统低得多的延迟。另一方面,record-at-a-time系统显示适应期间的延迟较高。在这两个模型中,通过定期检查点提供容错计算状态。record-at-a-time系统检查状态使用分布式检查点算法,例如,Chandy-Lamport,而BSP系统写出来检查点在计算屏障。因此,record-at-a-time系统中的故障是昂贵的
即使在单个操作失败的情况下,所有操作的状态都必须重置为最后一个检查点并且计算必须从该检查点恢复。相比之下,作为BSP系统通常跟踪每批的处理,这使得从最后一个检查点恢复期间重用计算的部分结果成为可能。更进一步讲,恢复可以并行执行,这使得适应性可以在BSP系统中更快,更容易实现。最后,批处理记录也允许BSP系统自然地进行在每批处理中使用优化过的执行过程。这些技术特别有利于工作负载在聚合操作时减少聚合数据转移。
一种可以同时在正常操作期间和适应期间实现低延迟的明显方法是去显着减少BSP系统中的批的大小。不幸的是,这远非微不足道。 BSP系统依赖在批处理粒度上的屏障来执行调度并确保计算的正确性。然而,屏障需要在所有活动节点之间进行协调,这是分布式系统中非常昂贵的操作。当批的大小减少协调开销开始占主导地位时,使低延迟变得不切实际。我们的主要发现是流式工作负载需要毫秒级延迟,工作负载和集群属性以更慢的速度变化(几秒钟
或分钟)。所以,可以分离协调粒度从BSP系统的执行粒度中。这允许我们处理批处理每几毫秒,同时每隔几秒钟协调响应工作负载和集群变化,因此我们可以在正常操作期间和适应期间实现低延迟。
我们在Drizzle中实现了这个模型。我们的实现是基于在BSP系统的主要来自两个主要来源的开销的发现:
- (a)集中化任务调度,包括给worker的任务的最佳分配并序列化任务发送给worker
- (b)协调任务之间的数据传输。我们介绍了一组技术通过利用流式工作负载的结构来解决这些开销。

为了解决集中式调度瓶颈,我们引入组调度(第3.1节),其中多个批次(或一组)立即调度。 这从调度决策中分离出了执行任务的粒度并分摊任务序列化和启动的成本。这里的关键挑战是任务的输入依赖已经被计算之前加载任务。我们使用预调度解决了这个问题(§3.2),该算法在任务的输入已经被满足时,我们主动从任务队列出队任务到worker机器执行,并且依赖worker来加载任务。

选择合适的组大小对于确保Drizzle实现低延迟是非常重要的。为了简化选择一个组的大小,我们实现了一个组大小自动调整机制,它会根据性能目标调整调度的粒度。最后,Drizzle中的执行引擎也有有益于迭代应用程序,而且我们讨论了怎样使用Drizzle实现机器学习算法。

我们在Apache Spark上实现Drizzle并将Spark Streaming与Drizzle继承在了一起。使用雅虎的流处理基准,我们的实验在一个128节点的EC2集群,实验显示Drizzle和Spark相比可以实现端到端记录处理延迟小于100毫秒,延迟可以降低3.5倍。相比与Flink(一种即时记录流式系统),Drizzle可以从失败中快速恢复,快4倍左右,并且Drizzle在恢复期间的延迟降低了13倍。此外,通过优化批处理中的执行,我们发现,Drizzle的延迟比Flink降低约4倍。在迭代机器学习工作负载上,我们发现Drizzle可以在128台机器上80毫秒内运行完迭代,比Spark快6倍。

2 背景

我们首先描述两大类流式系统。之后我们谈论了这些系统特点并在他们之间的利弊比较中结束。

2.1 流式的计算模型

BSP流式系统。批量同步并行(BSP)模型影响了许多数据处理构架。在该模型中,计算包括系统中所有并行节点执行一些局部计算的阶段,然后是所有节点能够相互通信阻塞屏障,然后重复这一过程。 MapReduce范式遵循这个模型,Map阶段可以做任意的局部计算,然后是一个屏障,会进行整个集群的shuffle操作,之后Reduce阶段可每个reducer可以继续处理从对应mapper读取输出数据(通常都是这三个阶段)。如Dryad,Spark和FlumeJava系统拓展了MapReduce模型,允许组合多个Map和Reduce,还包括专业操作接口,例如filter,sum,group-by,join。因此,计算是操作的有向无环图(DAG)并用屏障划分为不同的阶段。在每个阶段,许多map函数可以融合在一起,如图1所示。此外,许多操作(例如,sum,reduce)可以有效通过在map阶段预先合并数据来实现减少传输的数据量。
image

流式系统,如Spark Streaming,使用FlumeJava的Google Dataflow采用上述BSP模型。 他们通过创建持续时间T秒微批实现流式。在微批处理期间,从流式源收集数据,通过整个DAG定义的操作处理并遵循通过屏障将所有数据输出到流式sink,例如Kafka。 因此,每个微批结尾都有一个屏障,如果DAG包括多个stage则每个微批内也存在屏障,例如 group by操作

在微批流式模型中,持续时间T构成a记录处理延迟的下限。不幸的是,由于所有这些系统障碍的实现方式,T不能设置为足够小的值。考虑一个简单的工作,包括一个map阶段,然后是reduce阶段(如图)。集中式驱动程序调度所有map任务轮流在有空闲资源的集群上运行。然后每个map任务为基于一些分区策略(比如散列或排序)的reducer输出每个map的记录。然后每个任务通知集中式驱动程序将输出记录分配给不同的reducer。然后,驱动程序可以根据可用的群集资源调度reduce任务,并将元数据传递给每个reduce任务,然后reduce会从不同的map输出数据中拉取每个reduce相关的数据。因此,微批中的每个屏障需要来回和驱动程序通信。因此,将T设置得太低将导致显著的驱动程序通信和调度开销,由此与驱动程序的通信最终主导处理时间。在大多数微批流式系统中,T限制为0.5秒或更长。

屏障的协调极大地简化了容错和BSP系统中的扩展。首先是调度程序在每个阶段结束时被通知,并可以重新调度必要的任务。这意味着调度程序可以在每个阶段结束时添加并行度,并且可以在为下一个stage加载任务时使用其他计算机。此外,这些系统中的容错通常是通过在每个障碍上获取一致的快照来实现。该快照可以是物理的,即记录一个阶段中每个任务的输出; 或逻辑的,即记录某些数据的计算依赖性。任务失败从那以后可以使用这些快照轻松处理,调度程序可以重新安排任务并将其读取(或重建)来自快照的输入。

即时系统:一种被用于专用系统的低延迟工作负载替代计算模型,该工作负载是长期运行的数据流模型,数据流模型已被用于构建数据库系统,流式数据库并被扩展来支持分布式计算系统如Naiad,StreamScope和Flink。在这样的系统中,用户程序同样被转换为操作的DAG,但每个操作都会被自己的处理器作为长期运行的任务运行。在处理数据时,操作会更新可变的本地状态并直接从操作之间消息转移。屏障仅在特定操作需要时插入。因此,与基于BSP的系统不同,没有调度和与集中驱动程序的通信开销。不像基于BSP的系统,在微批最后需要一个屏障,即时系统不会强加任何这样的障碍。

通常,为了处理机器故障,数据流系统使用异步分布式检查点算法来定期创建一致的快照。每当节点发生故障时,所有节点都将回滚到最近的一致的检查点和记录然后从该检查点重放。 对于输入不止一个来源的节点,检查点标记需要确保每条记录只被处理一次。 然而检查点重放代价很大:例如,从一个失败的节点恢复整个
系统必须等待新节点串行重建失败节点的可变状态。

2.2 流式系统的可取属性

大规模流式系统所需的主要属性包括:
- 高吞吐量:随着收集的数据量的爆炸,流式系统也需要扩展来处理高吞吐量。 例如LinkedIn报告它每天使用Kafka产生1万亿条消息,而Twitter的时间序列数据库
据报道,每分钟处理28亿次写入。这促使了对分布式流处理的需求在大型集群中运行以提供高系统吞吐量。
- 低延迟:流式处理系统的延迟定义为从记录开始进入到系统中输出系统中的时间。例如,如果一个用于预测趋势的异常检测工作负载有一个1秒的滚动窗口,那么处理延迟就是处理该窗口中的所有事件并生成必要的输出所需的时间。此外,处理延迟应该在整个集群中扩展计算时保持低水平。
- 适应性:由于流式作业长期运行的性质,系统很重要的一个特性是适应工作负载或集群属性的改变。 例如,来自Twitter的最新论文指出,硬件故障,负载变化或行为不当的用户代码所有这些情况经常出现在大规模集群中。 另外重要的是,在适应变化的同时,系统不违反正确性属性并确保性能SLA的正常运行时间为99.9%甚至更高。
- 一致性:分布式的主要挑战之一是随着时间的推移确保流处理结果的一致性。例如,如果我们有两个计数器,一个跟踪请求数量和另一个跟踪响应数量,确保请求总是在响应之前处理是有用的。这个可以通过保证前缀完整性来实现,其中产生的每一项输出相当于处理了一个有明确前缀定义的输入流。另外,更低级别消息传递语义像精确一次传递对于程序员实现容错的流式应用程序很有用。
- 统一的编程模型:最后,许多流式应用程序需要集成静态的数据集和库用于机器学习,图处理等。具有统一的编程模型使开发人员可以轻松地与现有集成现有的库以及在跨批处理和流处理作业共享代码。

表1:

特性 微批处理模型 即时模型
延迟 秒级 微秒级
一致性 精确一次性,前缀完整性 具有流对齐的精确一次性
检查点 基于屏障的同步 异步
适应性 微批边界 检查点和重启
统一编程模型 自然 比较困难

2.3 比较两种计算模型

表1总结了两种模型之间的差异。如上所述,基于BSP的系统由于调度和通信开销的微批长度下限具有较差的延迟。 如果是微批持续时间T设置低于这个微批长度下限,系统作业执行将落后并变得不稳定。 即时系统没有这个缺点,因为没有必要的相关屏障调度和通信开销。

另一方面,基于BSP的系统可以自然地在屏障边界处适应从故障中恢复或添加/删除节点。 即时系统会有回滚到异步检查点并从该检查点重放。此外,基于BSP的系统在每个微批次的输出都被明确的和确定的时具有很强的一致性,而即时系统的输出不保证一致。两种模型都可以保证精确一次性语义。

此外,基于BSP的系统可以自然地在流式模型、批模型或其他模型之间共享相同的编程模型。 因此,像GoogleDataflow / FlumeJava和Spark支持批处理,ETL和迭代计算的系统,例如机器学习。最后BSP系统中的执行模型也使得它很容易在每个微批应用优化执行技术。这对于在一个批中执行基于聚合的合并记录的更新的工作负责是很有优势的,这种聚合操作可以显著通过网络传输的数据量。支持这种优化在即时系统中更难并要求引入显式缓冲或批处理。

3 设计

在本节中,我们将展示BSP和即时模型可以统一为一个模型,并具有两者的优势。我们通过展示基于BSP的模型如何被修改以大大减少微批的调度和通信执行开销。我们设计的主要想法是减少这些开销允许我们运行亚秒级延迟(≈100ms)的微批。我们选择在Drizzle中扩展BSP模型,因为它允许我们保持统一编程模型。 我们相信它也有可能开始使用记录即服务方法并对其进行修改以获得模型与我们建议的方法具有相似的好处。我们接下来讨论我们使用的技术,并讨论一些与我们的设计相关的权衡取舍。
image

3.1 组调度

BSP架构,如Spark,FlumeJava或Scope使用集中式调度器,该调度器实现了许多复杂的调度技术,包括:考虑局部性的算法,落后者缓解,公平共享等。在这些系统中调度单个阶段的处理流程如下:首先,集中式调度器计算每个任务应分配的worker,会考虑计算本地性和其他限制,然后这些任务被序列化并使用RPC发送到对应的worker。使用的调度算法的复杂性加上运行调度程序的单个机器的计算和网络限制对任务的调度速度提出了基本限制。

我们观察到,在流处理作业中,用于处理微批的计算DAG在很大程度上是静态的,并且很少发生变化。 基于这一观察,我们建议在微批次中使用相同的调度决策。重用调度决策意味着我们可以同时为多个微批调度任务(如图),从而分摊集中式调度开销。

要了解这有何帮助,请考虑用于计算移动平均线的流式作业。假设数据源保持不变,则为每个微批计算的优先位置将是相同的。如果是群集配置也保持静态,将为每个微批计算相同的worker到任务的映射。因此,我们可以运行一次调度算法并重用其决策。同样,我们可以通过将来自多个微批次的任务组合到单个消息中来减少RPC的序列化和网络开销。

使用组调度时,需要谨慎选择一次调度的微批量。我们将在3.3节中讨论组大小如何影响的性能和自适应属性,以及在3.4节中自动选择大小的现有技术。

3.2 预调度shuffle

虽然上一节描述了我们如何消除微批之间的屏障或协调,如第2节(如图)所述,现有的BSP系统也在微批中设置了一个屏障来协调数据传输以进行shuffle操作。接下来我们将讨论如何消除这些屏障,从而消除组内的所有协调。

在shuffle操作中,我们有一组产生输出的上游任务(或map任务)和一组接收输出并运行缩减功能的下游任务(或reduce任务)。在Spark或Hadoop等现有BSP系统中,上游任务通常将其输出写入本地磁盘,并通知集中式驱动程序将输出记录分配给不同的Reducer。 然后,驱动程序创建从上游任务中拉取数据到下游任务。因此,在这种情况下,元数据通过集中式驱动器传送,然后接着是一个屏障,在该屏障处使用基于拉的机制进行数据传输。

为了消除这个障碍,我们在Drizzle中的上游任务(如图)之前预先调度下游任务。我们执行调度,以便首先加载下游任务;这样就可以使用元数据来调度上游任务,这些元数据告知上游任务在完成时需要通知运行下游任务的机器。因此,数据直接在Worker之间传输,无需任何集中协调。这种方法有两个好处。首先,它可以更好地扩展worker数量,因为它避免了集中式元数据管理。其次,它消除了屏障,只有在前一阶段的所有任务完成后才会启动后续阶段。
image

我们通过在管理预调度的任务的每台worker机器上添加本地调度器来实现预调度。首次加载预调度任务时,这些任务将标记为非活动状态,不使用任何资源。worker机器上的本地调度器跟踪需要满足的数据依赖性。上游任务完成后,它会通知相应的worker节点,本地调度器会更新未完成的依赖项列表。当非活动任务的所有数据依赖性得到满足时,本地调度器将该任务置位活动状态并运行它。

3.3 Drizzle的适应性

组调度和shuffle预调度消除了微批内部和跨批处理的屏障,并确保每组只发生一次屏障。 然而,在这样做时,我们在适应变化时会产生开销,并且我们将讨论这会如何影响下面的容错,弹性和工作负载变化。这种开销很大程度上不会影响记录处理的延迟,但是记录处理延迟会继续在组内产生。

容错:与现有的BSP系统类似,我们在Drizzle中定期创建同步检查点。检查点可以在任何微批结束时进行,并且一组微批的结束代表一个自然边界。我们使用从worker到driver的心跳来检测机器故障。发现worker节点故障后,调度程序将重新提交在故障worker上运行的任务。默认情况下,这些恢复任务从可用的最新检查点开始执行。由于每个微批的计算是确定,我们使用两种技术来进一步加速恢复过程。首先,恢复任务在许多机器上并行执行。其次,我们还重用了在早期微批中运行的map阶段产生的任何中间数据。 这是通过lineage血统实现的,这个特性是现有BSP系统中已有的功能。

使用预调度意味着在Drizzle故障恢复期间我们需要处理一些其他情况。对于在新机器上运行的reduce任务,集中式调度器在任务执行完成之前预填充了数据依赖列表。类似地,调度器还更新上游(map)任务,以将后续微批的输出发送到新机器。在这两种情况下,我们发现集中式调度程序简化了设计,并有助于确保worker可以依靠单一来源获取作业进度。

弹性:除了处理被删除的节点之外,我们还可以处理添加的节点以提高性能。为此,我们与现有的集群管理器集成在一起,如YARN,Mesos,应用层可以选择何时请求或放弃资源的策略。在组边界最后,Drizzle会更新可用资源列表并调整要为下一组调度的任务。因此,在这种情况下,使用更大的组可能导致在响应群集变化时的更大延迟。

数据变化:最后,我们处理类似于群集变化的数据属性变化。在每个微批执行期间,会收集关于执行过程中的许多执行状态数据。这些状态数据在一个组的最后被聚合到一起,可供查询优化器使用,以决定备用的查询计划是否会有更好地执行性能

3.4 自动选择组大小

从直观来讲,组大小控制着Drizzle中协调权衡的性能。对于给定的作业,使用较大的组总是会减少总体调度开销,从而提高性能。然而,较大的组对适应性产生负面影响。 因此,我们在Drizzle中的目标是选择尽可能小的组,同时确保调度开销最小化。

我们实现了一个受TCP拥塞控制启发的自适应组大小调整算法。在组任务执行期间,我们使用计数器来跟踪系统中各个部分花费的时间。使用这些计数器,我们就能够确定在组的执行时间中多少用于调度和其他协调中,多少用于worker执行任务。调度所花费的时间与整体执行的比率给我们提供了任务调度的开销大小,我们的目标是在用户指定的区间内维持这种开销。

当开销超过上限时,我们成倍增加组大小以确保开销迅速减少。一旦开销低于下限,我们将组大小逐渐减小以提高适应性。这类似于TCP和AIMD中使用的AIMD策略可证明地收敛于通常情况。我们还对组间的调度开销进行指数平均,以确保单个尖峰(由于称为垃圾收集等瞬态事件)不会影响进程。

3.5 讨论

我们将讨论用于预调度的组调度和拓展的其他设计方法,可以进一步提高性能。

其他设计方法: 我们认为的另一种设计方法是将BSP系统中的现有调度程序视为黑盒,并在前一个微批的任务执行时pipeline调度下一个微批。也就是说,当第一批微批执行时,集中式驱动程序会调度一个或多个后续微批。使用流水线调度,如果微批处理的执行时间是texec,调度开销是tsched,则微批b的总运行时间是b × max(texec,tsched)。 基准线将采用b ×(texec + tsched)。我们发现这种方法不适用于较大的集群,其中tsched的值可能大于texec。

改进预调度: 在Drizzle中使用预调度时,reduce任务通常需要在开始执行之前等待来自所有上游map任务的通知。如果我们有足够的语义信息来确定阶段的通信结构,我们可以减少任务等待的输入数量。例如,如果我们使用二叉树裁剪来聚合信息,则每个reduce任务仅需要前一阶段运行的两个map任务的输出。通常来说,由于用户定义的map和hash分区函数,推断将要生成的DAG的通信结构是一个难题。对于像treeReduce或广播这样的高级操作,DAG结构是可预测的。我们已经实现了对Drizzle中使用TreeReduce的DAG结构的支持,并计划在未来支持其他操作。

4 Drizzle用于迭代工作负载

除了大规模流处理之外,Drizzle的执行模型也非常适合运行迭代工作负载。在许多应用程序中都可以找到迭代工作负载,我们专注于使用迭代优化算法训练的通用机器学习模型,例如随机梯度下降。接下来我们将描述迭代执行如何从Drizzle中受益,然后描述无冲突的共享变量,一种支持轻量级数据共享的扩展。

迭代执行:许多大型机器学习库如MLlib和Mahout都是建立在BSP框架之上的。在这些库中,迭代算法通常通过将每次迭代作为BSP作业来实现。例如,在小批量SGD中,每台机器计算数据子集的梯度,并且在迭代结束时聚合来自所有任务的梯度。然后将结果广播到下一次迭代的任务中。梯度计算花费的时间大约为毫秒,但调度开销变得很大。

Drizzle中的执行模型可用于减少迭代算法的调度开销。在这种情况下,我们使用组调度来分摊多次迭代的调度开销。与流式类似,我们发现调度决策相关的数据局部性等在迭代中保持不变。该领域的另一个挑战是如何以最小的协调来跟踪和传播更新到共享模型。 接下来我们将讨论无冲突共享变量如何应对这一挑战。

无冲突的共享变量:大多数机器学习算法对模型变量执行交换更新,并且跨worker共享模型更新等同于实现AllReduce。为了在一组迭代中启用交换更新,我们引入了无冲突的共享变量。

无冲突的共享变量在思想上与CRDT类似,并提供访问和交换更新共享数据的API。我们开发了一个可以与各种底层实现一起使用的API。我们的API包含两个主要功能:(1)get函数,可选择阻止同步更新。这通常在任务开始时调用。在同步模式下,它会等待先前迭代的所有更新。(2)一个提交函数,描述了任务已完成对共享变量的所有更新。另外,当一个执行组开始和结束时,我们提供对共享变量执行的回调。这是为了使每次执行能够在组边界检查它们的状态,因此无冲突的共享变量是Drizzle中统一容错策略的一部分。

在我们当前的实现中,我们关注的是可以放在单个机器上的模型(在标准服务器具有≈200GB内存的情况下,这些模型仍然可能有数百万个参数)并构建对具有同步更新复制共享变量的支持。 我们实现了一种合并策略,该策略在与其他副本交换更新之前聚合机器上的所有更新。虽然其他参数服务器实现了更强大的一致性语义,但我们主要关注的是研究控制平面开销如何影响性能。我们计划在未来将Drizzle与现有的最先进的参数服务器集成。

5 实现

我们通过扩展Apache Spark 2.0.0实现了Drizzle。我们的实现需要大约4000行Scala代码,修改了Spark调度程序和执行引擎。 在本节中,我们将介绍我们在实现中所做的一些额外性能改进,并描述我们如何将Drizzle与Spark Streaming和MLlib集成。

Spark改进:现有的Spark调度程序实现使用了两个线程:一个用于计算stage依赖和优先位置,另一个用于管理执行程序上的任务排队,序列化和启动任务。我们观察到,当预调度好几个阶段时,任务序列化和启动通常是一个瓶颈。在我们的实现中,我们将序列化和启动任务分离到专用线程,如果有许多阶段可以并行调度,则可选择使用多个线程。我们还优化了预调度任务的位置查找,这些优化有助于在提前调度多个阶段时减少开销。但是,我们还有一些其他的性能改进没有在Drizzle中实现。例如,虽然迭代作业通常在迭代中共享相同的闭包,但我们不会跨迭代分摊闭包的序列化。这需要分析作为闭包一部分的Scala字节码,我们计划在将来实现这一点。

Spark Streaming:Spark Streaming体系结构包括一个创建Spark RDD的JobGenerator和一个在处理微批时在RDD上运行的闭包。 Spark Streaming中的每个微批都与执行时间戳相关联,因此每个生成的RDD都有一个关联的时间戳。在Drizzle中,我们扩展JobGenerator以同时提交多个RDD,其中每个生成的RDD都有合理的时间戳。例如,当Drizzle被配置为使用3的组粒度,并且起始时间戳为t时,我们将生成时间戳为t,t + 1和t + 2的RDD。这里的挑战之一是我们如何处理类似的输入源Kafka,HDFS等。在现有的Spark架构中,微集中处理的key或文件的元数据管理由driver完成。为了在没有集中协调的情况下处理这个问题,我们修改为由输入源来计算worker上的元数据,作为读取输入的任务的一部分。最后,我们注意到这些更改仅限于Spark Streaming内部,用户应用程序不需要修改即可与Drizzle一起使用。

机器学习:为了将机器学习算法与Drizzle集成,我们引入了一个新的迭代原语(类似于Flink中的iterate),它允许开发人员指定需要在每次迭代时运行的代码。我们使用无冲突共享变量(而不是广播变量)来更改要存储的模型变量,并类似地更改梯度计算函数。我们使用无冲突共享变量的稀疏和密集向量实现来实现基于梯度下降的优化算法。 像Logistic回归,SVM这样的高级算法不需要改变,因为他们可以使用修改后的优化算法。

6 评估

我们接下来评估Drizzle的性能。首先,我们使用一系列微基准测试来测量Drizzle的调度性能,并分析调度程序中每个步骤所花费的时间。接下来,我们使用许多不同的场景评估Drizzle的适应性,包括容错、弹性并评估我们的自动调整算法。最后,我们测量了两个真实场景应用使用Drizzle的影响:工业流基准和标准机器学习基准上的逻辑回归任务。 我们将Drizzle与Apache Spark(一种BSP样式系统)和Apache Flink(一种即时记录流处理系统)进行比较。

6.1 搭建

我们在Amazon EC2中的128个r3.xlarge实例上运行了我们的实验。每台机器有4个cpu,30.5 GB内存和80 GB SSD存储。我们将Drizzle配置为每台机器上使用4个slot执行任务。对于我们的所有实验,我们在进行测量之前先预热JVM。 我们使用Apache Spark v2.0.0和Apache Flink v1.1.1作为我们实验的基线。我们比较的所有三个系统都是在JVM上实现的,我们对它们使用相同的JVM堆大小和垃圾收集器。

6.2 微基准测试(Micro Benchmark)

在本节中,我们将介绍评估组调度和预调度优势的微基准测试。我们为每个微基准测试进行了10次试验,并报告了中位数,第5和第95百分位数。

6.2.1 组调度

我们首先评估Drizzle中组调度的好处,以及随着集群大小的增加,减少了调度开销的影响。 我们使用弱缩放实验,其中每个任务的计算量是固定的(我们研究了6.5节中的强扩展)但是群集的大小(以及因此的任务数量)增长。对于此实验,我们将任务数设置为等于群集中的核心数。在理想系统中,计算时间应保持不变。我们使用简单的工作负载,其中每个任务计算随机数的总和,并且每个微批的计算时间小于1ms。请注意,此基准测试中没有随机操作。我们测量每个微批在运行100个微批时所花费的平均时间,并且我们将集群大小从4-128个机器扩展。

正如第二2节中所讨论的,我们看到(图4)随着群集大小的增加,Spark的任务调度开销增长,使用128台机器时大约为200ms。Drizzle能够分摊这些开销,从而在群集大小上实现7 - 46倍的加速。凭借100和128台机器的组大小,Drizzle的调度开销不到5毫秒,而Spark的调度开销大约为195毫秒。
image
我们分析了图4(b)中的好处。为此,我们绘制每个任务的平均时间,用于调度,任务传输(包括序列化,反序列化和任务的网络传输)和计算。我们可以看到调度和任务传输主导了Spark的这个基准测试的计算时间,并且Drizzle能够使用组调度分摊这两个开销。

6.2.2 预调度shuffle

为了测量预调度的优势,我们使用与前一小节相同的设置,但为每个微批处理添加一个shuffle阶段,其中包含16个reduce任务。我们比较了图4(c)中运行100个微批时每个微批所花费的时间。随着我们改变集群大小,Drizzle在Spark上的速度提高了2.7倍到5.5倍

图4(c)还显示了仅使用预调度的好处(组大小= 1)。在这种情况下,在微批次之间仍然存在屏障,并且只能消除单个微批中的屏障。 我们看到,使用128台机器时,仅预调度的优势仅限于20毫秒。但是,为了使组调度有效,我们需要预先调度DAG中的所有任务,因此预调度至关重要。

最后,我们看到两个stage的微批作业的时间(128台机器为45毫秒)明显高于上一节(5毫秒)中一个stage的微批作业的时间。虽然此开销的一部分来自用于触发reduce任务的消息,但我们还发现,随着map任务数量的增加,在reduce任务中获取和处理shuffle数据的时间也会增加。为了减少这种开销,我们计划研究reduce连接初始化时间和改进数据序列化/反序列化的技术。

6.3 流式工作负载

为了衡量Drizzle如何提高流式应用程序的性能,我们运行了Yahoo! streamin基准,用于比较各种流处理系统。基准测试模拟在广告展示流上的运行分析。 生产者将JSON记录插入流中。然后,基准测试将事件分组为每个广告活动的10秒窗口,并测量窗口结束后窗口中所有事件处理所需的时间。例如,如果窗口在时间a结束并且在时间b处理来自窗口的最后一个事件,则该窗口的处理等待时间被称为b-a。

当使用Spark和Drizzle中的微批处理模型实现时,此工作负载每个微批包括两个阶段:一个map阶段,用于将输入JSON和分组事件到窗口中;还有一个reduce阶段,用于聚合同一窗口中的事件。对于Flink实现,我们使用优化版本,它使用JSON中存在的事件时间戳在Flink中创建窗口。与微批处理模型类似,我们在这里有两个操作,一个解析事件的map操作和一个窗口操作,它从相同窗口收集事件并每10秒触发一次更新。

我们启动了一个输入流来插入20M事件/秒并测量事件处理延迟。我们使用前五分钟来预热系统并报告下一个五分钟的执行结果。我们调整每个系统以实现最低延迟,同时满足吞吐量要求。 对于Spark,我们调整了微批大小,同样我们调整了Flink中的缓冲区刷新持续时间。
image
性能比较:在128台机器上处理延迟的CDF如图5(a)所示。我们看到Drizzle能够实现大约350毫秒的中值延迟,并与Flink实现的延迟相匹配。我们还验证了我们从Flink获得的延迟与先前报告的相同基准数据匹配。我们还看到,通过减少调度开销,Drizzle的中值延迟比Spark低大约3.6倍。

容错:我们使用与上面相同的设置并对所有三个系统属性的容错进行基准测试。 在这个实验中,我们在240秒后杀死集群中的一台机器,并像以前一样测量事件处理延迟。 图5(b)显示了每个窗口跨时间测量的延迟。我们绘制每个系统的五次运行的平均值和标准差。 我们看到在Spark中使用微批模型具有良好的相对适应性,其中故障期间的延迟大约是正常处理延迟的3倍,并且只有1个窗口受到影响。Drizzle具有类似的行为,其中故障期间的延迟从大约350ms增加到大约1s。与Spark类似,Drizzle的延迟也在一个窗口后恢复正常

另一方面,Flink在故障期间经历严重的减速,并且延迟时间达到18s左右。大多数这种减速是由于停止和重新启动拓扑中的所有操作以及从最新检查点恢复执行所需的额外协调。 我们还看到,如此巨大的延迟意味着系统无法赶上输入流,并且在延迟恢复到正常范围之前需要大约40秒(或4个窗口)。

微批优化:当用于流式任务时,微批处理模型的一个优点是它可以在批处理中使用额外优化。 在此基准测试的情况下,由于输出仅要求窗口中的事件数量,因此我们可以通过在map端聚合每个窗口的结果来减少混洗的数据量。我们使用reduceBy运算符而不是groupBy运算符在Drizzle和Spark中实现了这种优化。图5(c)显示了启用优化时处理延迟的CDF。在这种情况下,我们看到Drizzle的中位延迟时间为94毫秒,比Spark快2倍。 由于Flink在通过key分区后创建窗口,因此我们无法直接应用相同的优化。我们在评估以下部分中使用针对Drizzle和Spark的优化版本。

总之,我们发现Drizzle实现了一个有价值的权衡,正常执行期间的延迟低于像Spark这样的BSP系统,故障期间的恢复比Flink之类的即时记录系统快得多。

6.4 Drizzle中的适应性

接下来我们将评估Drizzle中组大小的重要性,特别是它如何在容错和弹性方面影响适应性。 接下来,我们将展示我们的自动调整算法如何找到最佳的组大小。

6.4.1 组调度的容错

要在Drizzle中测量组大小对故障恢复的重要性,我们使用与上一节相同的工作负载,并改变组大小。 在本实验中,我们在每个组的末尾创建检查点。我们测量窗口的处理延迟,和五次运行的中间处理延迟如图6(a)所示
image
我们可以看到,使用更大的组可能会导致更高的延迟和更长的恢复时间。这主要是因为两个原因。 首先,由于我们只在具有较大组的每个组的末尾创建检查点,这意味着需要重放更多记录。此外,当机器发生故障时,需要更新预调度的任务以反映新的任务位置,并且当有大量任务时该过程需要更长的时间。在未来,我们计划调查检查点间隔是否可以与组解耦,以便在预调度中更好地处理故障。

6.4.2 处理弹性

为了衡量Drizzle如何实现弹性,我们考虑一个场景,我们从Yahoo流式基准测试开始,但只使用集群中的64台机器。我们在4分钟后向集群添加64台机器,并测量系统响应和使用新机器所需的时间。为了测量弹性,我们监测执行微批的平均延迟,和改变组大小的结果。如图6(b)所示。

我们发现使用更大的组会拖延适应群集更改所需的时间。在这种情况下,使用50的组大小,延迟在10秒内从150ms降至100ms。另一方面,使用600的组大小需要100秒才能做出反应。 最后,如图所示,弹性也会在首次使用新机器时导致性能下降。这是因为某些输入数据需要从正在使用的机器移动到新机器。

6.4.3 组大小调整

为了评估我们的组大小调整算法,我们使用相同的Yahoo流式基准测试,但更改微批大小。 直观来看,调度开销与微批大小成反比,因为小批意味着需要安排更多任务。我们使用5%到10%的调度开销目标进行实验,并以组大小为2开始。调整算法的进度如图6(c)所示,微批大小为100ms和250ms。

我们看到,对于较小的微批,小的组起初开销较高,因此算法将组大小增加到64.随后,当开销低于5%时,组大小累计减少到49。在250ms微批处理的情况下,我们看到8的组大小足以维持低调度开销。

6.5 机器学习工作负载

最后,我们来看看Drizzle对机器学习工作负载的优势。我们使用随机梯度下降(SGD)实现的逻辑回归来评估这一点。我们使用路透社语料库第1卷(RCV1)数据集,其中包含来自文本文档的余弦标准化TF-IDF向量。我们使用SGD从这些数据中训练两个模型:第一个存储为密集向量,而第二个我们使用L1正则化和阈值来获得稀疏模型。我们展示了与图7中的Spark相比,Drizzle每次迭代所花费的时间。
!image
从图中可以看出,在使用密集向量更新的同时,Drizzle和Spark在大约32台机器上达到了扩展限制。 这是因为超过32台机器在模型更新通信上花费的时间占据了计算梯度所花费的时间。 然而,使用稀疏向量更新,我们发现Drizzle可以更好地扩展,并且每次迭代的时间在128台机器上降至大约80ms(相比之下,Spark为500ms)。为了理解稀疏更新如何有帮助,我们绘制了图7(c)中传输的数据量。除了传输更少的数据之外,我们发现随着模型接近收敛,更少的特征更新,进一步减少了通信开销。虽然使用Spark进行稀疏向量更新,虽然模型更新很小,但任务调度开销占据每次迭代所需的时间,因此使用超过32台机器会使作业运行速度变慢

最后,鉴于上述计算和通信的权衡,我们注意到使用大集群对此数据集效率不高。事实上,RCV1可以使用像LibLinear这样的工具在一台机器上解决。但是,使用较小的数据集会给予Drizzle的调度层压力,并突出缩放瓶颈所在的位置。此外,对于不适合单台机器的问题,算法通常在每次迭代中对数据进行采样,并且我们的示例类似于使用677k的样本来解决此类问题。

7 相关工作

流处理:包括Aurora和TelegraphCQ在内的数据库系统已经研究了在一台机器上进行流处理。由于许多数据流的输入事件比率很高,最近的工作研究了大规模的分布式流处理框架,例如Spark Streaming,Storm,Flink,Google Dataflow等,来跨群集缩放流处理。除了分布式检查点之外,最近在StreamScope上的工作已经提出使用可靠的顶点和可靠的通道来实现容错。在Drizzle中,我们专注于重用BSP系统中现有的容错语义,并提高性能以实现低延迟。

BSP性能:包括Nimbus和Thrill在内的最新工作重点是实施高性能BSP系统。两个系统都声称运行时(即JVM)的选择对性能有重大影响,并选择在C++中实现其执行引擎。 此外,类似于我们工作的Nimbus发现调度程序是迭代作业的瓶颈并使用调度模板。 但是,在执行期间,Nimbus使用可变状态并专注于HPC应用程序,而我们则专注于通过在Drizzle中使用确定性微批处理流媒体作业来提高适应性。另一方面,Thrill专注于数据平面中的查询优化; 因此,我们的工作与Thrill正交。
集群调度:像Borg,YARN和Mesos这样的系统可以调度来自不同框架的工作并实施资源共享策略。先前的工作也确定了更短的任务持续时间的好处,这导致了Sparrow,Apollo等分布式作业调度程序的开发。这些调度框架侧重于跨job调度,而Drizzle的重点是在单个job中进行调度。为了提高job性能,已经提出改进数据局部性,减轻落后的任务,重新优化查询和加速网络传输的技术。在Drizzle中,我们研究如何将这些技术用于流式作业,而不会产生大量开销。
之前的工作也研究了在shuffle阶段消除屏障以提高性能的好处。除了移除屏障之外,Drizzle中的预调度还有助于消除数据传输的集中协调。最后,许多键值存储和参数服务器已经提出了具有各种一致性语义的分布式共享存储器接口。Drizzle中无冲突的共享变量具有类似的语义,我们将它们与Drizzle中的组调度,容错支持集成在一起。
机器学习框架:大规模机器学习一直是最近研究的重点。这包括新算法,分布式模型训练系统和高级别面向用户的开发库。在Drizzle中,我们专注于提高迭代机器学习工作负载的BSP框架的性能,并研究如何消除调度开销来帮助解决这个问题。

8 结论和未来的工作

大数据流处理系统将自己定位为BSP或即时记录。Drizzle表明这些方法并没有根本不同,并展示了我们如何弥合它们之间的差距。这使得Drizzle能够结合两者的最好特性,在正常操作和适应期间实现极低的延迟,同时提供强大的一致性保证和统一的编程模型。
在本文中,我们专注于优化低延迟工作负载的控制平面(例如协调)的设计技术。在未来,我们还计划研究如何改进数据平面的性能,例如,使用计算加速器或专用网络硬件来实现低延迟RPC。此外,我们计划扩展Drizzle并将其与其他执行引擎(例如,Impala,Greenplum)集成,从而开发用于通用低延迟调度的架构。
致谢:我们要感谢Matei Zaharia,Ganesh Ananthanaryanan对早期草案的评论。 该研究部分得到了DHS奖HSHQDC-16-3-00083,NSF CISE远征奖CCF-1139158,DOE奖SN10040 DESC0012463和DARPA XData奖FA8750-12-2-0331的支持,以及来自Amazon Web Services,Google的赠品 ,IBM,SAP,The Thomas和Stacey Siebel Foundation,Apple Inc.,Arimo,Blue Goji,Bosch,Cisco,Cray,Cloudera,Ericsson,Facebook,Fujitsu,HP,Huawei,Intel,Microsoft,Mitre,Pivotal,Samsung,Schlumberger ,Splunk,State Farm和VMware。

实现源码github地址amplab/drizzle-spark

猜你喜欢

转载自blog.csdn.net/Shie_3/article/details/81269265