暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spark与实时计算

胖风烟静 2020-08-15
1669


1、Spark简介

1.1 Spark是什么

Spark是一个基于内存计算的大数据并行计算框架,使用 scala 实现,提供了Java、Scala、 Python、R等语言的调用接口。基于Spark的核心能力,还衍生出了一套Spark生态系统。


1.2 Spark生态系统

Spark生态包括:Spark Core、Spark SQL、Spark Streaming、MLlib和GraphX等组件;



1)Spark Core:包含Spark的核心基础功能,如内存计算、任务调度、部署模式、存储管理、故障恢复等。Spark建立在统一的抽象RDD数据模型之上,使其可以以基本一致的方式应对不同的大数据处理场景;

2)Spark SQL:Spark SQL能够统一处理RDD与关系表,使开发人员可以轻松的使用SQL命令进行查询与进行复杂的数据分析;使用它,开发可以以类SQL语句的方式直接处理RDD,同时也可以查询Hive、HBase等外部数据。

3)Spark Streaming:Spark Streaming支持高吞吐、可容错处理的实时流数据处理,核心思路是将流式处理分解为一系列短小的批处理作业。它支持多种数据源,如Kafka、Flume和TCP套接字等;

4)MLlib:MLlib提供了常用的机器学习算法实现,包括聚类、分类、回归、协同过滤等,降低了机器学习的门槛;

5)GraphX:GraphX是Spark中用于图计算的API,能在海量的数据上运行复杂的图算法。


1.3 Spark的特点

Spark依旧是批处理的计算框架,同时他又支持实时流数据处理。而且在批处理方面,相较于MapReduce,他更快,这主要是由于它具有以下特点:

1)内存计算:提供Cache,将中间结果直接存在内存中,支持反复的迭代计算,相较于MapReduce减少了不必要的磁盘和带宽IO,提高了运算效率;

2)RDD:相较于MapReduce只有Map、Reduce的操作,Spark由于采用了RDD数据模型,可以支持更多的对数据集的操作,编程模型比MapReduce更灵活;

3)基于DAG的任务调度机制:提供了一套支持 DAG 图的分布式并行计算的编程框架,减少多次计算之间中间结果写到 HDFS的开销;

4)多线程池模型:使用多线程池模型减少 Task 启动开稍,Shuffle 过程中避免不必要的 Sort 操作并减少磁盘 IO 操作。(Hadoop 的 Map 和 Reduce 之间的 Shuffle 需要 Sort)


1.4 Spark基本概念

在具体讲解Spark运行架构之前,需要先了解几个重要的概念:

1)Application 应用:用户编写的Spark应用程序,用户提交后,Spark为App分配资源将程序转换并执行。

2)Driver Program:运行Application的main()函数并且创建SparkContext。

3)SparkContext:是用户逻辑与Spark集群主要的交互接口,它会和Cluster Manager进行交互,负责计算资源的申请等。

4)Cluster Manager:资源管理器,负责集群资源的管理和调度,支持的有:Standalone,Mesos和YARN。在standalone模式中即为Master主节点,控制整个集群,监控Worker。在YARN模式中为资源管理器。

5)Worker Node:从节点,负责控制计算节点,启动Executor或Driver。在YARN模式中为NodeManager,负责计算节点的控制。

6)Executor:执行器,是为Application运行在工作节点(Worker Node)上的一个进程,该进程里面会通过线程池的方式负责运行任务,并负责将数据存在内存或者磁盘上。每个Application拥有独立的一组Executor。

与Hadoop MapReduce计算框架相比,Spark所采用的Executor有两个优点:

  • 利用多线程来执行具体的任务(Hadoop MapReduce采用的是进程模型),减少任务的启动开销;

  • Executor中有一个BlockManager存储模块,会将内存和磁盘共同作为存储设备,当需要多轮迭代计算时,可以将中间结果存储到这个存储模块里,下次需要时,就可以直接读该存储模块里的数据,而不需要读写到HDFS等文件系统里,因而有效减少了IO开销;或者在交互式查询场景下,预先将表缓存到该存储系统上,从而可以提高读写IO性能。

7)DAG:是Directed Acyclic Graph(有向无环图)的简称,反映RDD之间的依赖关系;当RDD遇到Action算子(即RDD操作中的Action操作),将之前的所有算子形成一个有向无环图(DAG)。再在Spark中转化为Job,提交到集群进行执行。一个App可以包含多个Job。

8)RDD:是弹性分布式数据集(Resilient Distributed Dataset)的简称,是分布式内存的一个抽象概念,提供了一种高度受限的共享内存模型;

9)Job作业:一个Job作业包含多个RDD及作用于相应RDD上的各种操作,在SparkContext中通过runJob()向Spark提交Job。

10)Stage阶段:是作业的基本调度单位,一个作业会分为多组任务,每组任务被称为“阶段”,或者也被称为“任务集”。

11)Task任务:运行在Executor上的工作单元;一个分区对应一个Task,Task执行RDD中对应Stage中所包含的算子。Task被封装好后放入Executor的线程池中执行。

12)DAG Scheduler:根据Job构建基于Stage的DAG,并提交Stage给Task Scheduler。

13)Task Scheduler:将Task分发给Executor执行。

14)SparkEnv:线程级别的上下文,存储运行时的重要组件的引用。


1.5 Spark各种概念之间的关系

在Spark中,一个应用(Application)由一个任务控制节点(Driver)和若干个作业(Job)构成,一个作业由多个阶段(Stage)构成,一个阶段由多个任务(Task)组成。当执行一个应用时,任务控制节点会向集群管理器(Cluster Manager)申请资源,启动Executor,并向Executor发送应用程序代码和文件,然后在Executor上执行任务,运行结束后,执行结果会返回给任务控制节点,或者写到HDFS或者其他数据库中。


2、Spark系统架构



Spark运行架构包括集群资源管理器(Cluster Manager)、运行作业任务的工作节点(Worker Node)、每个应用的任务控制节点(Driver)和每个工作节点上负责具体任务的执行进程(Executor)。其中,集群资源管理器可以是Spark自带的资源管理器,也可以是YARN或Mesos等资源管理框架。

整个 Spark 集群中,分为 Master 节点与 Worker 节点。其中Master节点上常驻 Master 守护进程和 Driver 进程,Master负责将串行任务变成可并行执行的任务集Tasks,同时还负责出错问题处理等,而 Worker 节点上常驻 Worker 守护进程,Master节点与Worker 节点分工不同,Master负载管理全部的Worker节点,而 Worker 节点负责执行任务。

每个Worker上存在一个或多个Executor进程,该对象拥有一个线程池,每个线程负责一个 Task任务的执行。根据 Executor 上 CPU-core 的数量,其每个时间可以并行多个跟Core 一样数量的 Task。Task 任务即为具体执行的 Spark 程序的任务。


3、Spark运行基本流程

3.1 Spark的基本运行流程



Step 1:当一个Spark应用被提交时,首先需要为这个应用构建起基本的运行环境,即由任务控制节点(Driver)创建一个SparkContext,由SparkContext负责和资源管理器(Cluster Manager)的通信以及进行资源的申请、任务的分配和监控等。SparkContext会向资源管理器注册并申请运行Executor的资源;

Step 2:资源管理器为Executor分配资源,并启动Executor进程,Executor运行情况将随着“心跳”发送到资源管理器上;

Step 3:SparkContext根据RDD的依赖关系(Lineage)构建DAG图,DAG图提交给DAG Scheduler(DAG调度器)进行解析,将DAG图分解成多个Stage(每个Stage阶段都是一个任务集),并且计算出各个阶段之间的依赖关系,然后把一个个“任务集”提交给底层的任务调度器(Task Scheduler)进行处理;Executor向SparkContext申请任务,任务调度器将任务分发给Executor运行,同时,SparkContext将应用程序代码发放给Executor;


备注:图中每个小块代表Partition,由小块组成的一列为一个RDD,虚线包围的为一个Stage;


Step 4:任务在Executor上运行,把执行结果反馈给任务调度器,然后反馈给DAG调度器,运行完毕后写入数据并释放所有资源。


3.2 Spark运行架构特点

Spark运行具有如下特点:

  • 每个应用都有自己专属的Executor进程,并且该进程在应用运行期间一直驻留。Executor进程以多线程的方式运行任务,减少了多进程任务频繁的启动开销,使得任务执行变得非常高效和可靠;

  • Spark运行过程与资源管理器无关,只要能够获取Executor进程并保持通信即可;

  • Executor上有一个BlockManager存储模块,类似于键值存储系统(把内存和磁盘共同作为存储设备),在处理迭代计算任务时,不需要把中间结果写入到HDFS等文件系统,而是直接放在这个存储系统上,后续有需要时就可以直接读取;在交互式查询场景下,也可以把表提前缓存到这个存储系统上,提高读写IO性能;

  • 任务采用了数据本地性和推测执行等优化机制。数据本地性是尽量将计算移到数据所在的节点上进行,即“计算向数据靠拢”,因为移动计算比移动数据所占的网络资源要少得多。而且,Spark采用了延时调度机制,可以在更大的程度上实现执行过程优化。比如,拥有数据的节点当前正被其他的任务占用,那么,在这种情况下是否需要将数据移动到其他的空闲节点呢?答案是不一定。因为,如果经过预测发现当前节点结束当前任务的时间要比移动数据的时间还要少,那么,调度就会等待,直到当前节点可用。


4、RDD

4.1 RDD是什么

RDD,Resilient Distributed Datasets,弹性分布式数据集。

所谓弹性,是指RDD可以自动进行磁盘数据迭代和内存数据迭代两种操作模式的切换,数据优先在内存中进行迭代,当内存容量有限时再将溢出部分数据缓冲到磁盘进行迭代。

所谓“分布式”,就是可以分布在多台机器上进行并行计算。

RDD可以理解为是一组只读的、可分区的分布式数据集合,数据集由多个数据的Partition(分区)组成,并由Block映射成真实数据。每个Partition(分区)代表一部分据集片段,并位于一个计算节点。




RDD本质上是Spark中的一个抽象类,所有子RDD(HadoopRDD、MapPartitionRDD、JdbcRDD等)都要继承并实现其中的方法。

abstract class RDD[T: ClassTag](

    @transient private var _sc: SparkContext,

    @transient private var deps: Seq[Dependency[_]]

  ) extends Serializable with Logging {

这个抽象类提供了很多方法和属性,其中主要包括Compute函数、Partitioner 分区策略、Partition分区列表、Dependency依赖等;

1)Compute函数

一个计算每个分区的函数,RDD的计算以分片为单位,每个RDD实现Compute函数。通俗来说,Compute用于计算每个分片,得出一个可遍历的结果,用于描述在父RDD上执行的计算。

2)Partitioner分区函数

RDD分片函数,定义如何对数据进行分区,描述分区模式和数据分片粒度。Partitioner函数决定RDD本身的分片数据,同时决定了Parent RDD Shuffle输出时的分片数据。

Spark实现两种类型的分片函数:基于哈希的HashPartitioner和基于范围的RangePartitioner。区别在于只有Key-Value类型的RDD才有分区的,非Key-Value类型的RDD分区的值是None的。

3)Partition分区列表

Partition是数据集的基本组成单位,每一个partition代表一个并行的最小划分单元。

因为在大数据处理场景中,一份数据的一般很大,为了能够并行计算,需要把数据分区存储,就是把数据分散存储在多个节点上,RDD会保存数据分区信息,以便处理数据,每个RDD有个方法Partitions,可以获取分区信息,Partiton不直接持有数据,仅仅代表了分区的位置(index的值)。

4)Dependency依赖

RDD的转换都会生成新的RDD,RDD之间形成【子->父】的依赖关系(源RDD没有依赖),Dependency依赖描述了RDD之间的依赖关系。成员rdd就是父RDD,会在构造RDD时被赋值。

abstract class Dependency[T] extends Serializable {

  def rdd: RDD[T]

}

由上述RDD、Dependcy关系可描述血缘关系(Lineage),通过依赖关系描述血缘关系(Lineage),子RDD能轻易找到父RDD的位置等信息,从而构建出RDD的转换路径,为DAG Scheduler的任务划分及任务执行时寻找依赖的数据提供依据。

*5)Partition的位置列表(优先位置Preferred Location)

每个分片的优先计算位置,定义如何最快速的获取Partition的数据,加快计算,这个是可选的,可作为本地化计算的优化选项(Spark在进行任务调度的时候,会尽可能将计算任务分配到所需数据块的存储位置,满足“移动计算优先移动数据”的理念。)


4.2 Spark算子

对RDD数据集,可以进行一系列的转换(Transformation)与执行(Action)操作,这些对RDD的操作也称为Spark算子。

1)转换(Transformation)

转换即RDD到RDD之间的计算过程,比如map,flatMap等等,转换的特点是执行转换的代码,并不立刻进行转换,一直到最后一步动作发生的时候才会真正执行转换。转换操作指定处理相互依赖关系,是数据集的逻辑操作。

2)执行(Action)

就是获取最后的结果,就像一条指令一样,引发整个过程的运转。比如Count(返回数据集的元素数),Collect(返回元素本身的集合),Save(输出数据集到存储系统),这些都是Action,当发生Action操作时,Spark会将Action之间的所有Transformation组成的Job并行计算。

常见的Action如下图所示:



4.3窄依赖(OneToOneDependency)和宽依赖(ShuffleDependency)

Spark中主要处理过程是RDD的转化过程,假如RDD1通过某种计算(map,groupByKey)转换为RDD2,那么就认为RDD2依赖RDD1,在Spark中依赖关系分为两种,一种是窄依赖(OneToOneDependency),一种是宽依赖(ShuffleDependency)也叫Shuffle 依赖。窄依赖是一个子RDD只能有一个父RDD,宽依赖是一个子RDD有多个父RDD。



上图是窄依赖,下图是宽依赖,比如map就是一种窄依赖,特点是RDD转换分区之间互不影响,即使有一个转换失败了,也不影响其他转换,只需要恢复故障转换过程即可。而groupByKey就是一种宽依赖,如图右图,RDD2的Partition1的生成,需要RDD1的Partition1和Partition2同时贡献数据,如果其中有哪一步Partition转换失败了,那么整个转换过程需要重新执行。

另外一点区别是,窄依赖的转换可以在集群的一个节点中完成,免去IO开销,而宽依赖的数据,来自所有的父Partition,这就可能产生大量IO,所以宽依赖比窄依赖更加耗费资源。


4.4 RDD示例

在Spark内计算的数据,都需要以RDD的形式存在,所以输入到Spark的数据和经过Spark计算过的数据都会形成RDD,包括即将输出的数据也会生成RDD后统一输出的。



RDD Wordcount示例:

val wordLine = sc.textFile("file:///Users/test/README.md");

val words = wordLine.flatMap(x => x.split());

val wordTuple = words.map(x => (x,1));

val wordCounts = wordTuple.reduceByKey((x,y)=>x+y)

wordCount.saveAsTextFile("file:///test/result");

第一步:加载数据,生成第一份RDD;

第二步:生成窄依赖words,进行Map转换;

第三步:生成窄依赖wordsTuple,增加每个单词的计数;

第四步:生成宽依赖wordCounts,累加每个元组;

第五步:发生Action,保存文件。


5、基于DAG的任务调度

定时任务是软件开发中经常遇到的问题。简单的定时任务只需要在固定时间触发它的执行就可以了。但是对于复杂的定时任务,可能是由多个任务组成一个任务组,它们之间存在依赖关系,一个任务执行的条件,必须是它的前置任务已经执行成功(或者没有前置任务),它才可以执行。例如下面这幅图:



图中任务的依赖关系为:

任务1:依赖2,5

任务2:依赖3,4

任务3:无依赖

任务4:无依赖

任务5:无依赖

任务6:依赖2

这个任务关系图其实就是“有向无环图”(简称DAG)这种数据结构。

图是由一系列顶点和连接顶点的边组成的数据结构。它分为有向图和无向图。有向图的边是有方向的,即A->B这条边和B->A是两条不同的边,而无向图中,A->B和B->A是共用一条边的。基于这种数据结构,我们可以用图的顶点表示一个任务,而图的边表示任务之间的依赖关系,就可以基于有向无环图来实现任务调度。


6、实时计算中的一些概念

6.1 离线和批量、实时和流式

批量:Batch By Batch

流式:One By One

批量处理每次处理一定大小的数据块(如MapReduce中的Block),一个task处理完一个数据块之后,才将处理好的中间数据发给下游。流式计算则是以数据记录为处理单位,task处理完一条数据记录后,立即发给下游。

可见,如果对于一个固定大小的数据块(有限数据)做统计,那么采用批量或者流式效果基本相同。流式计算只是可以实时得到计算的中间结果。但是对于无限数据(一直在产生数据的数据源,如消息队列),实时计算就可以快速得到结果,而不用等到将数据积攒到一定程度(有限数据块)再进行处理。

MapReduce作为离线计算的典型代表,采用的是批量计算的方式,而Storm、Flink是流式计算的实时计算框架。但这并不是代表批量计算就一定是离线计算、实时计算就一定是流式计算。事实上,Spark Streaming就是批量计算的实时计算框架。

Spark Streaming批量读取数据源中的数据,然后把每个batch转化成内部的RDD。Spark Streaming以batch为单位进行计算(默认1s产生一个batch),而不是以接收的数据record为单位,大大减少了ACK (Acknowledge Character即确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符,表示发来的数据已确认接收无误)所需的开销,显著提高了吞吐。但也因为处理数据的粒度变大,导致Spark streaming的数据延时不如Storm,Spark Streaming是秒级返回结果(与设置的batch间隔有关),Storm则是毫秒级。

所以说,离线与实时指的是数据处理的延迟;批量与流式则指的是数据的处理方式。


6.2 容灾性、吞吐量与精确性

6.2.1 容灾性

当各种灾难发生时,在保证生产系统的数据尽量少丢失的情况下,保持生产系统的业务不间断的运行。任何针对可能发生的灾难,提高系统可靠性和可用性的措施都可以称为容灾。对于分布式大数据计算系统,最常面对的容灾问题就是运行过程中运算节点挂掉的情况。

Storm容灾方式示例:

运行过程中,一个算子(bolt)因某种原因挂了,Storm如何恢复这个任务。



Storm的spout有一个buffer,会缓存接收到的record,并且Storm还有一个acker(可以认为是一个特殊的bolt任务),每条record和该record所产生的所有tuple(元组)在处理完成后都会向对应的acker发送ack消息,当acker接收到该record所有的ack消息之后,便认为该record处理成功,并通知spout从buffer中将该record移除,若receiver没有在规定的时间内接收到ack,acker则通知spout重放数据。

acker个数可以由用户指定,因为数据量比较大时,一个acker可能处理不过来所有的ack信息,成为系统瓶颈(如果可以容忍数据丢失,当然也可以关闭ack机制,可以显著提高系统性能)。

Storm采用ack机制实现了数据的重放,尽管做了很多优化,但是毕竟每条record和它产生的tuple(元组)都需要ack,对吞吐还是有较大的影响,关闭ack的话,对于某些不允许丢数据的业务来说又是不可接受的。

Storm的这种特点会导致大家认为:流式计算的吞吐不如批量计算。

批处理解决方案就比较简单,拿MapReduce举例,假如一个运行中map或reduce失败,那么任务重新提交一遍就ok(只不过重头计算又要花费大量时间)。


6.2.2 吞吐量

系统吞吐量几个重要参数:TPS、QPS、并发数、响应时间。

1)TPS

Transactions Per Second,事务数/秒,是指系统在单位时间内处理事务请求的数量,一个事务是指一个客户端向服务器发送请求然后服务器作出反应的过程。

对于无并发的应用系统而言,吞吐量与响应时间成严格的反比关系,实际上此时吞吐量就是响应时间的倒数。对于单用户的系统,响应时间(或者系统响应时间和应用延迟时间)可以很好地度量系统的性能,但对于并发系统,通常需要用吞吐量TPS作为性能指标。对于一个多用户的系统,如果只有一个用户使用时系统的平均响应时间是t,当有你n个用户使用时,每个用户看到的响应时间通常并不是n*t,而往往比n*t小很多(当然,在某些特殊情况下也可能比n*t大,甚至大很多)。不同系统的平均响应时间随用户数增加而增长的速度也不大相同,这也是采用吞吐量来度量并发系统的性能的主要原因。

2)QPS

Query Per Second,每秒查询率,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。

在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。对应fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。(类似于TPS,当只是单纯查询一个接口场景下,QPS=TPS)

3)并发数

并发用户数是指系统可以同时承载的正常使用系统功能的用户的数量。与吞吐量TPS相比,并发用户数是一个更直观但也更笼统的性能指标。并发用户数是一个非常不准确的指标,因为用户不同的使用模式会导致不同用户在单位时间发出不同数量的请求。相比而言,以同时发请求用户数作为性能指标更准确些。一般我们所说的都是指某个接口的并发数。

4)响应时间

响应时间是指系统对请求作出响应的时间,一般取平均响应时间;

其中,特定情况下,QPS(TPS)=并发数/平均响应时间

一个系统吞吐量通常由QPS(TPS)、并发数两个因素决定,每套系统这两个值都有一个相对极限值,在应用场景访问压力下,只要某一项达到系统最高值,系统的吞吐量就上不去了,如果压力继续增大,系统的吞吐量反而会下降,原因是系统超负荷工作,上下文切换、内存等等其它消耗导致系统性能下降。


6.2.3 精准性

1)at most once:最多消费一次,会存在数据丢失

2)at least once:最少消费一次,保证数据不丢,但是有可能重复消费

3)exactly once:精确一次,无论何种情况下,数据都只会消费一次,这是我们最希望看到的结果。

大部分流式系统都提供了at most once和at least once功能,但不是所有系统都能提供exactly once。


6.3 window和event time

6.3.1 window

现实生活中,大部分数据源其实是无限数据unbound data,没有边界,我们没有办法得到一个最终的统计结果,很多情况下我们会对固定时间间隔的数据进行统计,比如每5s统计一下服务器的QPS,window机制能够帮我们很好的完成这项需求。



如图(标号代表事件发生的时间),流式系统会每隔5s创建一个window,将该时间段的数据放入buffer,累加后输出结果。图中0-5s产生的数据放在第一个window中(3s处有两条数据),累加后输出count=6。

window类型也有很多种,上图是一个Tumbling Windows的例子,另外还有Sliding Windows和session window。

上图是一个比较理想的示例图,理想很丰满,现实很骨感,直接按接收时间来划分window可能会存在误差:



假设由于网络延迟,应该属于第一个窗口的数据3延迟到达,被分到了第二个窗口,这时候计算结果并不准确。怎么办呢?


6.3.2 event time和process time

假设一个流式系统目前正在接收并处理用户手机的日志,但是由于网络延迟,或者用户手机离线,导致日志没有及时发送到流式系统,流式系统观察到数据的时间和数据真正产生的时间可能存在偏差,我们把数据真正产生的时间叫做:event time,把流式系统处理该数据的时间叫做:process time。



event time和process time往往会存在延迟,这种不一致会导致数据乱序,如图所示:蓝色事件晚于黄色事件发生,但是事件的处理却先于黄色事件。

早期的流式系统并没有区分process time和event time,往往将process time等同于event time。针对这一问题,一个很直观的解决方案就是:让数据自身携带timestamp,该timestamp记录该数据产生的时间,即为event time,流式系统按数据的event time来将数据分配到对应的窗口,而不是按处理数据的时间。

window需要知道该窗口的数据都已经全部到达,然后触发计算逻辑,如何window判断时间T之前的数据是否都已经到达呢?


6.3.3 watermark

watermark机制,watermark同样也携带一个时间戳,当算子接收到watermark T后,就代表时间T之前的数据已经接收完毕,不会再有小于时间T的数据。



如图:W(17)到达后,表示后续数据的时间戳不会小于17。那可能有人会问了:那就是有一部分小于17的数据他喵的就是比w(17)还晚到了怎么办?

watermark还会配合一个allow lateness参数,window接收到watermark后,再等待一段时间才会关闭窗口,如果这段时间有些数据依然没有发送过来,那就只能忽略它们了(window的内心os:我也尝试过等待,但我还有更重要的事情要做),而且考虑到流式系统的实时性,假如可接受的时间内,数据没有传输过来,那就算等到它过来再计算,从实时性这个角度来说,这时计算的结果也有可能也已经没有意义了。

Flink对window和watermark都提供了较好的支持,Spark streaming从2.0中也开始引入watermark功能,但是支持的功能有限,并且真正的流式可以更优雅、简单的实现window和watermark,从这个角度来看,Flink是优于Spark streaming的。


7、实时计算框架的简单对比

基于吞吐量、准确性,以及对于数据延迟问题的处理能力,我们简单的给出几个常用实时计算框架的对比结论:

1)Storm:Storm提供了低延迟的计算,但是吞吐较低,并且无法保证exactly once(Storm trident采用batch的方式改善了这两点);

2)Spark Streaming:Spark streaming通过小批量的方式保证了吞吐的情况下,同时提供了exactly once语义,但是实时性不如Storm,而且由于采用micro-batch的方式,对window和event time的支持比较有限(Spark streaming2.0中引入了window和event time,还在起步阶段)。

3)Flink:Flink采用分布式快照的方式实现了一个高吞吐、低延迟、支持exactly once的流式系统,流式处理的方式也能更优雅的支持window和event time。




文章转载自胖风烟静,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论