Spark论文思想之-RDD的一般性

5.1 介绍

  众所周知,RDD是一种内存抽象,通过这种抽象降低延迟,使得基于RDD的系统能够获得和专业系统相匹配的计算功能,并且更加高效。本篇文章旨在将这种内在的原因阐释清楚。
  但是,从始至终,应该认识到的是Spark虽然在一定程度上被广泛使用,是因为其能满足大部分需求。决然不是说Spark最优秀。因为,对于网络延迟高度敏感的应用,Spark是并不适用的,也就是说Spark不能满足准实时的应用。但Spark绝对可以称为大数据计算里边相当优秀的计算框架。其数据结构抽象更是值得思考借鉴一番。这里主要从两个角度对RDD进行展开。
  首先,是其表达性,RDD可以模拟除了之前说的准实时性需求的任何分布式系统,并且在大多数场景下都具有高效性。这种高效可以说是通过在MapReduce批处理作业之间提供数据共享来实现的。其次,从系统的角度讲,RDD允许用户在多数通用的资源瓶颈上进行控制,特别是网络和存储I/O上。

5.2 表达性视角

  青出于蓝,而胜于蓝。RDD继承了MapReduce的思想,同时又在这一思想上插上一双翅膀。

5.2.1 MapReduce可以作什么

  事实上,MapReduce可以做任何分布式计算。因为分布式简单来讲就是由节点组成的本地计算在加上适时地信息交换组成。再看看MapReduce模型,map提供本地计算,reduce提供全部节点间的信息交换。然后,不管什么计算都可分步进行,整个步骤走完最终会形成统一结果。因此,MapReduce可以作用于每一步上,经过一系列的MapReduce作用后,最终形成结果。故MapReduce可进行任何分布式计算。
  只不过问题在于,这种计算效率不够高。原因在于,每个MapReduce形成的结果需要借助于外部存储,这样才能被下一个MapReduce使用,又要多份复制存储,所以写的过程是很慢的。另外,MapReduce任务本身具有延迟。这是其本身具有的固有特性,在前边的文章中提到过。
  而基于RDD的Spark系统,一来通过尽可能地将中间结果放在内存,作为一系列批处理的共享数据,来避免做缓慢写出及做复制。二来其本身没有固有高延迟,之前文章的结果也提到,在100多个节点的商业集群上,Spark仅仅带来100ms延迟。
  这种固有延迟怎么来的?在任务获得CPU使用权开始执行之前,有调度、提交、启动等阶段,根据不同实现方式,这个过程持续的时间也不相同。所以,不同的系统也就会有不一样的延迟。对于Spark来将,这个100ms左右的延迟基本可以满足大量计算密集型的批处理任务。

5.2.2 沿袭图和容错

  首先明确一点:在运算符链条每一步中的RDD,对该链条中上一步RDD的沿袭图的大小是固定的。
在Spark 2.4.0版本中RDD主要有以下五个属性组成:

  • 一个分区清单
  • 一个用于计算每个分片的函数
  • 一个对其它RDD的依赖清单
  • 可选:一个key-value型RDD的分区器
  • 可选:一个计算分片的首选位置的清单

  要进行容错,一是要将不同版本的中间状态进行保存,这些状态便是不同转换作用于其上游数据产生的结果。二是要将进行外传数据进行复制保存。在遇到宽依赖的时候,上游map计算数据的状态是要外传给reduce进行进一步处理。但是,如果一个程序需要进行几十个转换操作的步骤,最后再执行宽依赖操作进行输出。那需要存储的内容就非常庞大了,据此看来,容错是代价比较大的。所以Spark还提供了一个异步检查点功能,用于限制这种链条很长的应用的存储增长。论文中给出了一个大致的标准,就是在检查点带宽比内存带宽慢10倍的情况下,通常每10个转换操作之后进行一次检查点设置。

5.2.3 和BSP比较

  BSP即同步并行模型,有个特点,专门针对真实硬件最突出的方面(通信有延迟、同步昂贵)同时用来进行数学分析又足够简单,可通过几个成本因子来调优(要进行通信传输的步骤数量、每步的本地计算量、每一步上每个处理器要进行的通信传输量)。通常利用这一模型来设计并行算法,谷歌的Pregel便基于此。但是Pregel仅仅支持整个系统的checkpoint和回滚进行容错,随着集群规模的增加以及失败的常见性,这个就HOLD不住了。但是Pregel的限制性恢复,允许在开发中将外传信息记录成logs并且可并行恢复丢失状态,这点倒是像RDD。
另一方面,由于RDD具有基于迭代式的接口,所以非常利于将不同lib编写的计算以pipeline形式组织起来,使程序组合变得便利。RDD还提供了让用户能够使用的高层抽象的正确组合(例如:将状态划分多个协分区的数据集,或者是设置窄依赖、宽依赖模式,使得在每一步上并不都需要all-to-all的外传通信),同时还能保持其接口的简单通用。

5.3 系统角度

  这里主要是针对系统瓶颈来说的,当然在大数据集群里边,主要的瓶颈要算是通信和存储了。RDD的数据分区以及数据本地性给予应用足够的控制来对这些瓶颈资源进行优化。

5.3.1 瓶颈资源

下面一组数据有助于了解商业集群的硬件特性:

  • 每个节点的本地内存带宽50GB/s,同时还有很多硬盘,Hadoop集群通常会有12-24个,通常意味着有2GB/s的带宽,假设20个磁盘,每磁盘100M/s。
  • 每节点具有10Gbps(1.3GB/s)的外部链路,这比内存带宽慢大概40倍,也比该节点的磁盘聚合带宽慢大概2倍。
  • 20-40节点又被组织成一个机架,也就是说具有20-40Gbps的机架间带宽,这又比机架内的网络性能要慢大概10倍。

  列出以上特性,主要是要强调在大数据计算里边关心的网络通信以及数据放置问题。前面已经提到了RDD的高层抽象,其允许应用控制这些层面,在运行时移动计算而不是移动数据,MapReduce中的map就是这么做的。并且在RDD接口中定义有一个“preferred locations” API。RDD在分区和协分区上对用户提供控制。MapReduce的数据共享总是隐含着数据的跨网络传输。相反,只有在应用调用一个跨集群节点的操作或者对一个数据集进行checkpoint的时候,RDD才会进行网络传输。
  从系统的角度讲,如果大多数应用都是带宽瓶颈型的,那节点内的效率问题比起控制网络通信来讲就会少很多。在Spark应用中,如果内存够容纳数据,那瓶颈就在于带宽。如果内存容不下,那瓶颈就在于网络I/O,此时决定性能的最重要的因素就是任务调度时的数据本地行了。其实这里很好理解,任务在数据刚好存放的节点执行再好不过。前边多次提到,RDD确定性添加的唯一开销就是网络延迟。当然,Spark基于MapReduce引擎做了很多优化,使得延迟这一指标变得很小,小到能够支持流式处理。

5.3.2 容错成本

  MapReduce中,如果在每一个map完成之后都将数据即刻发给reduce,也就是说map一完成,reduce便将数据拉取过去,那么最好的情况便是当最后一个map处理完并且reduce拉取之后所有的拉取工作都已经完成。
  Spark的实现有一道屏障,在宽依赖stage,reduce任务直到所有的map任务都完成之后才启动。这是出于对避免容错恢复的复杂性考虑的,如果记录被以管道的方式从mapper直接推送到reducer。
但是,Spark也不在这个方面提供竞争性。因为这个可能可以增加适度的好处,而重要性远不及避免中间状态的复制以及在靠近数据的节点放置map任务。进一步地,如果是运行时间支配的组件(例如:mapper花很长的时间从磁盘读取数据或者是shuffle远比计算慢),这种情况下,mapper完成直接传送给reducer的益处便大大降低。
  尽管错误不总是发生,但Spark将作业划分成细粒度的,独立的任务在集群设置中还有其他的重要好处。一个是落后者缓解,落后者在集群上更加常见。另一方面是多租户的动态资源共享,使每个用户都能获得比较好的交互性能。Spark的设计是将容错和弹性放在首要方面进行考虑的,这些特性对于多租户集群以及大查询来讲,都是比较容易扩展的。

5.4 限制与扩展

以前多少都提到过Spark的局限性以及其不适用的地方。

5.4.1 延迟

  这个延迟来自于哪里?由于RDD操作是确定性的并且在整个集群上是同步的。因此,在每一个时间步上启动计算,本质上有更多延迟,所以有很多时间步的计算会比较缓慢。
  所以,虽然Spark Streaming官方给出的延迟低,能够适应很多应用。但这种应用更多地基于人为事件的时间跨度,所以RDD低至100ms的延迟远低于这种事件。

5.4.2 All-to-All的通信模式

  RDD跟MapReduce一样,都可以在集群中使用reduce进行任何点对点的通信。通常情况下都是这样的,但是真实网络中也有其它高效原语,比如广播和网络内聚合。这在数据密集型的应用中起的作用更大。但这种原语操作,如果使用reduce进行点对点信息传输,代价太大。所以,RDD被直接扩展成支持这种原语,例如Spark的broadcast操作。

5.4.3 异步

  RDD进行全节点shuffle的reduce操作是同步的,以此保证操作的确定性。但是正因如此,如果每个节点的数据量并不均衡或者是节点负载不一样,也会使计算变得缓慢。也许后续开发者会对这一问题做些有趣的探索,如果RDD支持异步计算并且还能支持容错,也就是说节点能够异步发送消息,使得批计算能够继续,即使有节点缓慢的情况下。比如节点可以log在每次迭代时收到的message ID,而不是log消息本身,这样的话代价就小多了。而且在一个失败节点重建一个不一样的丢失状态的意义不大,因为后续计算可能已经使用了丢失状态。
  对于像统计优化算法,即使状态毁坏仍然可以继续,而不用抛弃整个进度。对于这样的算法,可以以一种特别的模式在基于RDD的无需执行容错恢复的系统上运行,只需要将结果进行checkpoint供后续的计算使用。

5.4.4 细粒度更新

  RDD并不适合细粒度的更新操作,因为对于每个操作的沿袭图的记录开销太大。比如你想进行分布式的key-value存储,RDD系统并不适合。即使是对于一个粗粒度的key-value批更新操作,Spark系统的延迟也不是足够低。如果能像卡尔文分布式数据库一样,对事务进行批处理并确定性地执行它们,以实现更好的大规模性能和可靠性。

5.4.5 不可变性和版本跟踪

  前边提到过,不可变性是用来进行沿袭图追踪,为了能够在老版本的数据集上恢复状态。但是,由于不可变性,基于RDD的系统就需要复制更多的数据,这增加了开销。所以,可以在运行时用其他的办法和基于RDD的可变状态追踪这些依赖。而且前边也提到过,可以用增量来代替。如果实现,对于流处理和细粒度的更新或将可行。

5.5 总结

  这篇文章主要介绍了RDD在其表达性方面、系统层面表现出的通用性。目前大多数并行计算系统都在致力于定义跟真实机器相似的新模型,并让MapReduce更加通用化以支持更高效地计算。但在数据共享、延迟、容错、落后者处理方面总是不尽相同。总之,没有一个系统是完美的,所有的并行计算系统都是在成本和效率之间进行取舍。

猜你喜欢

转载自blog.csdn.net/weixin_43878293/article/details/91643408
今日推荐