主流流处理框架比较.docx
- 文档编号:6336254
- 上传时间:2023-01-05
- 格式:DOCX
- 页数:16
- 大小:26.16KB
主流流处理框架比较.docx
《主流流处理框架比较.docx》由会员分享,可在线阅读,更多相关《主流流处理框架比较.docx(16页珍藏版)》请在冰豆网上搜索。
主流流处理框架比较
分布式流处理是对无边界数据集进行连续不断的处理、聚合和分析。
它跟MapReduce一样是一种通用计算,但我们期望延迟在毫秒或者秒级别。
这类系统一般采用有向无环图
(DAG)。
DAG是任务链的图形化表示,我们用它来描述流处理作业的拓扑。
如下图,数据从
sources流经处理任务链到sinks。
单机可以运行DAG,但本篇文章主要聚焦在多台机器上运行DAG的情况。
关注点
当选择不同的流处理系统时,有以下几点需要注意的:
运行时和编程模型:
平台框架提供的编程模型决定了许多特色功能,编程模型要足够处理各种应用场景。
这是一个相当重要的点,后续会继续。
函数式原语:
流处理平台应该能提供丰富的功能函数,比如,map或者filter这类易扩展、处理单条信息的函数;处理多条信息的函数aggregation;跨数据流、不易扩展的操作join。
状态管理:
大部分应用都需要保持状态处理的逻辑。
流处理平台应该提供存储、访问和更新状态信息。
消息传输保障:
消息传输保障一般有三种:
atmostonce,atleastonce和exactlyonce。
Atmostonce的消息传输机制是每条消息传输零次或者一次,即消息可能会丢失;Atleastonce意味着每条消息会进行多次传输尝试,至少一次成功,即消息
传输可能重复但不会丢失;Exactlyonce的消息传输机制是每条消息有且只有一次,即消息传输既不会丢失也不会重复。
容错:
流处理框架中的失败会发生在各个层次,比如,网络部分,磁盘崩溃或者节点宕机等。
流处理框架应该具备从所有这种失败中恢复,并从上一个成功的状态(无脏数据)重新消费。
性能:
延迟时间(Latency),吞吐量(Throughput)和扩展性(Scalability)是流处理应用中极其重要的指标。
平台的成熟度和接受度:
成熟的流处理框架可以提供潜在的支持,可用的库,甚至开发问答帮助。
选择正确的平台会在这方面提供很大的帮助。
运行时和编程模型
运行时和编程模型是一个系统最重要的特质,因为它们定义了表达方式、可能的操作和将来的局限性。
因此,运行时和编程模型决定了系统的能力和适用场景。
实现流处理系统有两种完全不同的方式:
一种是称作原生流处理,意味着所有输入的记录一旦到达即会一个接着一个进行处理。
第二种称为微批处理。
把输入的数据按照某种预先定义的时间间隔(典型的是几秒钟)分成短小的批量数据,流经流处理系统。
两种方法都有其先天的优势和不足。
首先以原生流处理开始,原生流处理的优势在于它的表达方式。
数据一旦到达立即处理,这些系统的延迟性远比其它微批处理要好。
除了延迟性外,原生流处理的状态操作也容易实现,后续将详细讲解。
一般原生流处理系统为了达到低延迟和容错性会花费比较大的成本,因为它需要考虑每条记录。
原生流处理的负载均
衡也是个问题。
比如,我们处理的数据按key分区,如果分区的某个key是资源密集型,那这个分区很容易成为作业的瓶颈。
接下来看下微批处理。
将流式计算分解成一系列短小的批处理作业,也不可避免的减弱系统的表达力。
像状态管理或者join等操作的实现会变的困难,因为微批处理系统必须操
作整个批量数据。
并且,batchinterval会连接两个不易连接的事情:
基础属性和业务逻辑。
相反地,微批处理系统的容错性和负载均衡实现起来非常简单,因为微批处理系统仅发送
每批数据到一个worker节点上,如果一些数据出错那就使用其它副本。
微批处理系统很容易建立在原生流处理系统之上。
编程模型一般分为组合式和声明式。
组合式编程提供基本的构建模块,它们必须紧密结合
来创建拓扑。
新的组件经常以接口的方式完成。
相对应地,声明式API操作是定义的高阶函数。
它允许我们用抽象类型和方法来写函数代码,并且系统创建拓扑和优化拓扑。
声明式API经常也提供更多高级的操作(比如,窗口函数或者状态管理)。
后面很快会给
出样例代码。
主流流处理系统
有一系列各种实现的流处理框架,不能一一列举,这里仅选出主流的流处理解决方案,并且支持ScalaAPI。
因此,我们将详细介绍ApacheStorm,Trident,SparkStreaming,Samza和ApacheFlink。
前面选择讲述的虽然都是流处理系统,但它们实现的方法包含了
各种不同的挑战。
这里暂时不讲商业的系统,比如GoogleMillWheel或者AmazonKinesis,也不会涉及很少使用的IntelGearPump或者ApacheApex。
ApacheStorm最开始是由NathanMarz和他的团队于2010年在数据分析公司BackType开发的,后来BackType公司被Twitter收购,接着Twitter开源Storm并在2014年成为Apache顶级项目。
毋庸置疑,Storm成为大规模流数据处理的先锋,并逐渐成为工业标准。
Storm是原生的流处理系统,提供low-level的API。
Storm使用Thrift来定义topology和
支持多语言协议,使得我们可以使用大部分编程语言开发,Scala自然包括在内。
Trident是对Storm的一个更高层次的抽象,Trident最大的特点以batch的形式进行流处理。
Trident简化topology构建过程,增加了窗口操作、聚合操作或者状态管理等高级操作,
这些在Storm中并不支持。
相对应于Storm的Atmostonce流传输机制,Trident提供了Exactlyonce传输机制。
Trident支持Java,Clojure和Scala。
当前Spark是非常受欢迎的批处理框架,包含SparkSQL,MLlib和SparkStreaming。
Spark的运行时是建立在批处理之上,因此后续加入的SparkStreaming也依赖于批处理,
实现了微批处理。
接收器把输入数据流分成短小批处理,并以类似Spark作业的方式处理
微批处理。
SparkStreaming提供高级声明式API(支持Scala,Java和Python)。
Samza最开始是专为
公司开发的流处理解决方案,并和
的
Kafka一起贡
献给社区,现已成为基础设施的关键部分。
Samza的构建严重依赖于基于log的Kafka,两者紧密耦合。
Samza提供组合式API,当然也支持Scala。
最后来介绍ApacheFlink。
Flink是个相当早的项目,开始于2008年,但只在最近才得到注意。
Flink是原生的流处理系统,提供highlevel的API。
Flink也提供API来像Spark一样进行批处理,但两者处理的基础是完全不同的。
Flink把批处理当作流处理中的一种特殊情况。
在Flink中,所有的数据都看作流,是一种很好的抽象,因为这更接近于现实世界。
快速的介绍流处理系统之后,让我们以下面的表格来更好清晰的展示它们之间的不同:
WordCount
Wordcount之于流处理框架学习,就好比helloworld之于编程语言学习。
它能很好的展示各流处理框架的不同之处,让我们从Storm开始看看如何实现Wordcount:
TopologyBuilderbuilder=newTopologyBuilder();builder.setSpout("spout",newRandomSentenceSpout(),5);builder.setBolt("split",newSplit(),8).shuffleGrouping("spout");builder.setBolt("count",newWordCount(),12).fieldsGrouping("split",newFields("word"));
...
Map
publicvoidexecute(Tupletuple,BasicOutputCollectorcollector){
Stringword=tuple.getString(0);
Integercount=counts.containsKey(word)?
counts.get(word)+1:
1;counts.put(word,count);
collector.emit(newValues(word,count));
}
首先,定义topology。
第二行代码定义一个spout,作为数据源。
然后是一个处理组件bolt,分割文本为单词。
接着,定义另一个bolt来计算单词数(第四行代码)。
也可以看到魔数5,8和12,这些是并行度,定义集群每个组件执行的独立线程数。
第八行到十五行是实际的WordCountbolt实现。
因为Storm不支持内建的状态管理,所有这里定义了一个局部状态。
按之前描述,Trident是对Storm的一个更高层次的抽象,Trident最大的特点以batch的形式进行流处理。
除了其它优势,Trident提供了状态管理,这对wordcount实现非常有用。
publicstaticStormTopologybuildTopology(LocalDRPCdrpc){
FixedBatchSpoutspout=...
TridentTopologytopology=newTridentTopology();
TridentStatewordCounts=topology.newStream("spout1",spout)
.each(newFields("sentence"),newSplit(),newFields("word"))
.groupBy(newFields("word"))
.persistentAggregate(newMemoryMapState.Factory(),
newCount(),newFields("count"));
...
}
如你所见,上面代码使用higherlevel操作,比如each(第七行代码)和groupby(第八行代码)。
并且使用Trident管理状态来存储单词数(第九行代码)。
下面是时候祭出提供声明式API的ApacheSpark。
记住,相对于前面的例子,这些代码相当简单,几乎没有冗余代码。
下面是简单的流式计算单词数:
valconf=newSparkConf().setAppName("wordcount")
valssc=newStreamingContext(conf,Seconds
(1))
valtext=...
valcounts=text.flatMap(line=>line.split(""))
.map(word=>(word,1))
.reduceByKey(_+_)
counts.print()
ssc.start()
ssc.awaitTermination()
每个SparkStreaming的作业都要有StreamingContext,它是流式函数的入口。
StreamingContext加载第一行代码定义的配置conf,但更重要地,第二行代码定义batchinterval(这里设置为1秒)。
第六行到八行代码是整个单词数计算。
这些是标准的函数
式代码,Spark定义topology并且分布式执行。
第十二行代码是每个SparkStreaming作业最后的部分:
启动计算。
记住,SparkStreaming作业一旦启动即不可修改。
接下来看下ApacheSamza,另外一个组合式API例子:
classWordCountTaskextendsStreamTask{
overridedefprocess(envelope:
IncomingMessageEnvelope,collector:
MessageCollector,
coordinator:
TaskCoordinator){
valtext=envelope.getMessage.asInstanceOf[String]
valcounts=text.split("").foldLeft(Map.empty[String,Int]){
(count,word)=>count+(word->(count.getOrElse(word,0)+1))
}
collector.send(newOutgoingMessageEnvelope(newSystemStream("kafka","wordcount"),counts))
}
Samza的属性配置文件定义topology,为了简明这里并没把配置文件放上来。
定义任务的输入和输出,并通过Kafkatopic通信。
在单词数计算整个topology是WordCountTask。
在Samza中,实现特殊接口定义组件StreamTask,在第三行代码重写方法process。
它的
参数列表包含所有连接其它系统的需要。
第八行到十行简单的Scala代码是计算本身。
Flink的API跟SparkStreaming是惊人的相似,但注意到代码里并未设置batchinterval。
valenv=ExecutionEnvironment.getExecutionEnvironment
valtext=env.fromElements(...)
valcounts=text.flatMap(_.split(""))
.map((_,1))
.groupBy(0)
.sum
(1)
counts.print()
env.execute("wordcount")
上面的代码是相当的直白,仅仅只是几个函数式调用,Flink支持分布式计算。
结论
上面给出了基本的理论和主流流处理框架介绍,下篇文章将会更深入的探讨其它关注点。
希望你能对前面的文章感兴趣,如果有任何问题,请联系我讨论这些主题。
在上篇文章中,我们过了下基本的理论,也介绍了主流的流处理框架:
Storm,Trident,SparkStreaming,Samza和Flink。
今天咱们来点有深度的topic,比如,容错,状态管理或者性能。
除此之外,我们也将讨论开发分布式流处理应用的指南,并给出推荐的流处理框架。
容错性
流处理系统的容错性与生俱来的比批处理系统难实现。
当批处理系统中出现错误时,我们只需要把失败的部分简单重启即可;但对于流处理系统,出现错误就很难恢复。
因为线上许多作业都是7x24小时运行,不断有输入的数据。
流处理系统面临的另外一个挑战是状态一致性,因为重启后会出现重复数据,并且不是所有的状态操作是幂等的。
容错性这么难实现,那下面我们看看各大主流流处理框架是如何处理这一问题。
ApacheStorm:
Storm使用上游数据备份和消息确认的机制来保障消息在失败之后会重新
处理。
消息确认原理:
每个操作都会把前一次的操作处理消息的确认信息返回。
Topology的数据源备份它生成的所有数据记录。
当所有数据记录的处理确认信息收到,备份即会被
安全拆除。
失败后,如果不是所有的消息处理确认信息收到,那数据记录会被数据源数据
替换。
这保障了没有数据丢失,但数据结果会有重复,这就是at-leastonce传输机制。
Storm采用取巧的办法完成了容错性,对每个源数据记录仅仅要求几个字节存储空间来跟
踪确认消息。
纯数据记录消息确认架构,尽管性能不错,但不能保证exactlyonce消息传输机制,所有应用开发者需要处理重复数据。
Storm存在低吞吐量和流控问题,因为消息确认机制在反压下经常误认为失败。
SparkStreaming:
SparkStreaming实现微批处理,容错机制的实现跟Storm不一样的方法。
微批处理的想法相当简单。
Spark在集群各worker节点上处理micro-batches。
每个micro-batches一旦失败,重新计算就行。
因为micro-batches本身的不可变性,并且每个micro-batches也会持久化,所以exactlyonce传输机制很容易实现。
Samza:
Samza的实现方法跟前面两种流处理框架完全不一样。
Samza利用消息系统
Kafka的持久化和偏移量。
Samza监控任务的偏移量,当任务处理完消息,相应的偏移量
被移除。
消息的偏移量会被checkpoint到持久化存储中,并在失败时恢复。
但是问题在于:
从上次checkpoint中修复偏移量时并不知道上游消息已经被处理过,这就会造成重复。
这
就是atleastonce传输机制。
ApacheFlink:
Flink的容错机制是基于分布式快照实现的,这些快照会保存流处理作业
的状态(本文对Flink的检查点和快照不进行区分,因为两者实际是同一个事物的两种不
同叫法。
Flink构建这些快照的机制可以被描述成分布式数据流的轻量级异步快照,它采
用Chandy-Lamport算法实现。
)。
如果发生失败的情况,系统可以从这些检查点进行恢复。
Flink发送checkpoint的栅栏(barrier)到数据流中(栅栏是Flink的分布式快照机制中一个核心的元素),当checkpoint的栅栏到达其中一个operator,operator会接所有收输入流中对应的栅栏(比如,图中checkpointn对应栅栏n到n-1的所有输入流,其仅仅是整个输入流的一部分)。
所以相对于Storm,Flink的容错机制更高效,因为Flink的操作
是对小批量数据而不是每条数据记录。
但也不要让自己糊涂了,Flink仍然是原生流处理
框架,它与SparkStreaming在概念上就完全不同。
Flink也提供exactlyonce消息传输机制。
状态管理
大部分大型流处理应用都涉及到状态。
相对于无状态的操作(其只有一个输入数据,处理过程和输出结果),有状态的应用会有一个输入数据和一个状态信息,然后处理过程,接着输出结果和修改状态信息。
因此,我们不得不管理状态信息,并持久化。
我们期望一旦因某种原因失败,状态能够修复。
状态修复有可能会出现小问题,它并不总是保证exactlyonce,有时也会出现消费多次,但这并不是我们想要的。
据我们所知,Storm提供at-leastonce的消息传输保障。
那我们又该如何使用Trident做到exactlyonce的语义。
概念上貌似挺简单,你只需要提交每条数据记录,但这显然不是那
么高效。
所以你会想到小批量的数据记录一起提交会优化。
Trident定义了几个抽象来达到exactlyonce的语义,见下图,其中也会有些局限。
SparkStreaming是微批处理系统,它把状态信息也看做是一种微批量数据流。
在处理每个微批量数据时,Spark加载当前的状态信息,接着通过函数操作获得处理后的微批量数据结果并修改加载过的状态信息。
1
Samza实现状态管理是通过Kafka来处理的。
Samza有真实的状态操作,所以其任务会持有一个状态信息,并把状态改变的日志推送到Kafka。
如果需要状态重建,可以很容易的
从Kafka的topic重建。
为了达到更快的状态管理,Samza也支持把状态信息放入本地key-value存储中,所以状态信息不必一直在Kafka中管理,见下图。
不幸的是,Samza只提供at-leastonce语义,exactlyonce的支持也在计划中。
Flink提供状态操作,和Samza类似。
Flink提供两种类型的状态:
一种是用户自定义状态;另外一种是窗口状态。
如图,第一个状态是自定义状态,它和其它的的状态不相互作用。
这些状态可以分区或者使用嵌入式Key-Value存储状态[文档一和二]。
当然Flink提供exactly-once语义。
下图展示Flink长期运行的三个状态。
单词计数例子中的状态管理
单词计数的详细代码见上篇文章,这里仅关注状态管理部分。
让我们先看Trident:
publicstaticStormTopologybuildTopology(LocalDRPCdrpc){
FixedBatchSpoutspout=...
TridentTopologytopology=newTridentTopology();
TridentStatewordCounts=topology.newStream("spout1",spout)
.each(newFields("sentence"),newSplit(),newFields("word"))
.groupBy(newFields("word"))
.persistentAggregate(newMemoryMapState.Factory(),newCount(),newFields("count"));
...
}
在第九行代码中,我们通过调用persistentAggregate创建一个状态。
其中参数Count存储单词数,如果你想从状态中处理数据,你必须创建一个数据流。
从代码中也可以看出实现
起来不方便。
SparkStreaming声明式的方法稍微好点:
//InitialRDDinputtoupdateStateByKey
valinitialRDD=ssc.sparkContext.parallelize(List.empty[(String,Int)])
vallines=...
valwords=lines.flatMap(_.split(""))
valwordDstream=words.map(x=>(x,1))
valtrackStateFunc=(batchTime:
Time,word:
String,one:
Option[Int],
state:
State[Int])=>{
valsum=one.getOrElse(0)+state.getOption.getOrElse(0)
valoutput=(word,sum)
state.update(sum)
Some(output)
}
valstateDstream=wordDstream.trackStateByKey(
StateSpec.function(trackStateFunc).initialState(initialRDD))
首先我们需要创建一个RDD来初始化状态(第二行代码),然后进行transformations(第五行和六行代码)。
接着在第八行到十四行代码,我们定义函数来处理单词数状态。
函数计算并更新状态,最后返回结果。
第十六行和十七行代码,我们得到一个状态信息流,其中包含单词数。
接着我们看下Samza,
classWordCountTaskextendsStreamTaskwithInitableTask{
privatevarstore:
CountStore=_
definit(config:
Config,context:
TaskContext){
this.store=context.getStore("wordcount-store")
.asInstanceOf[KeyValueStore[String,Integer]]
}
overridedefpro
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 主流 处理 框架 比较