spark-push-shuffle

本篇博客主要是翻译论文vldb_magnet_final,这篇论文是针对Spark shuffle的优化,提出了基于push的shuffle服务。

当前Spark shuffle

当前Spark已经提供了一个external shuffle service,简称ESS,用于帮助shuffle。整体流程就是map task写shuffle数据到本地磁盘,reduce task获取shuffle数据通过ESS服务,这样就可以释放map task所在的executor。
但是有一个很严重的性能问题,大量的小文件随机读
在reduce task拉取shuffle数据(简称block),会经常遇到小block,那么无论是用ESS还是默认的shuffle,都会产生大量的小文件随机读,这严重制约了磁盘IO

基于push的Spark ESS

核心问题已经发现了,下面就是如何围绕着这个问题优化,并且顺便提升其他隐藏的优化点

基于push的shuffle

我们知道已有的shuffle都是基于拉的,map task写,reduce task拉,没有中间服务。这导致的问题就是大量的block都是野生状态,没有一个统一的服务来管理维护他们。而基于push的思路就是,在map task写block的时候写到ESS,让ESS做block的合并和管理,这样整个map task写的流程都是基于网络的顺序io,而block在被ESS合并之后,reduce task读也可以看做是基于网络的顺序io。可以看到这里把磁盘的瓶颈转移了部分压力到网络,因为通常来说网络都是没有到瓶颈的。

数据传输和task并发执行

听起来范围很大,其实是针对reduce task拉取shuffle数据以及task执行来说。比如mapreduce有一个优化shuffle的思路是在map shuffle完成之前就启动reduce来拉取shuffle数据,这在shuffle数据量大的场景可以提高任务的执行速度,但是也可能带来负面影响,如果shuffle的数据倾斜导致有一个map task一直没有完成,那么reduce就会占用无用的资源。
目前Spark处于DAG调度层面的复杂度考虑没有做这个优化,但是可以通过异步rpc的方式来实现这个思路。我们可以理解为shuffle数据拉取和task执行是由两个线程执行,那么当数据拉取线程是异步的时候,就可以让task并发执行,一个是数据生产者一个是数据消费者。

可靠性

可靠性其实就是容错,包括shuffle中各个阶段的错误。首次明确一点,shuffle的block数据会被保留两份

  • 如果在push前出现那么map task重试
  • 如果push出错,那么使用之前的逻辑,shuffle block依旧在map task节点
  • 如果ESS合并block出错,那么还是会使用原始的shuffle block数据
  • 如果获取合并后的block出错,那么依然使用原始的shuffle block数据

shuffle数据倾斜

每个map task shuffle的数据量都不一样,如果遇到数据倾斜那么一个map task写的数据会远远大于其他。这里基于push的ESS提供block分块push功能,也就是将大block拆成小块推送给ESS

灵活的executor部署

基于资源动态申请这个功能,我们可以基于ESS的数据信息,让reduce task的executor申请到数据存在的机器,也就是数据本地化。当然这只是一种优化的场景,在数据节点和计算节点隔离的集群下,是无法试下这样优化的。

可能存在但不是问题的issue

大量的小数据量随机写,虽然基于push的ESS解决了大量小文件随机读的问题,但是随机写的问题没有解决,这是因为ESS没有对数据做缓存。但是这篇论文通过压测数据以及理论分析,小数据量的随机写操作带来的影响远远小于随机读。这是因为在操作系统层面对数据写入做了缓存,简单理解就是操作系统只有在数据满足一个page或者某些条件下才会触发写操作。因此这个问题已经被操作系统优化了。

总结一下

基于push的ESS提供了一种效率更高,扩展性更好,可靠性更稳定的shuffle方式。通过合并shuffle的block数据以及数据本地化机制等策略缩短任务的执行时间。

ulysses wechat
订阅+