用一个例子串起来spark shuffle全过程

【背景】

最近看了很多spark shffle相关的东西,脑子嗡嗡地,似懂非懂,所以决定用一个常见例子来把知识点串起来。

假设现在有一个表a是用户表(其中a_non_cncn数据a_cncn数据,b是全地区地址表

准备的sql如下:

select a.name,a.region,b.address from 

(select * from a_non_cn

where age>10

union all

select * from a_cn

where age>10

)a

left join b

on a.id=b.id and a.region=b.region

limit 20

这里面包含了where/limit/union/join等,应该算比较普遍了

假设a_non_cn表有90个数据分区,a_cn表有10个数据分区,b表有200个数据分区,表都很大无法做broadcast join,做的是 sort merge join

运行环境:executor是用动态分配,0~100个,每个executor内存4G、core1默认情况下一个task对应cpu的一个核(spark.task.cpus为1因此同一时刻最大可以并行执行的 Task 数目=100*1=100

【具体过程】

读取a表

一、首先stage0a_non_cna_cn进行scanfilterproject常规操作进行union

一般union分区两个之和(不涉及shuffle),90+10=100

union由于左边而且最终只取limit 20,所以增加一个limit算子

大致如下图(stage0)::

  1. map阶段的task数量一般分区数量
  2. 同一个stagetask可以并行依赖关系不同stage的task可以并行有依赖关系不同stage的task不可以并行,必须等上一个依赖的stage完成,下一个stage才可以执行
  3. 钨丝计划tungsten中WholeStageCodegen的优化会体现在这里,例如ColumnarToRow、Filter、Project、LocalLimit这几个算子会被重写成同一个函数,减少函数调用

读取b表

表b也是同样操作但是注意由于b是left join的右所以不会limit算子

大致如下(stage1):

sort merge join

三、开始进入absort merge join,可以分成3个过程;

1.shuffle write

a/b表根据会Join keys(id,region)进行Shuffle重分区。

partition算法是可以自定义的,默认的算法是根据keys哈希到不同的bucket中去

显然,我们可以对参与 Join 的表按照 Keys 进行 Bucket 来避免 Shuffle Sort Merge Join  Shuffle 操作,因为 Bucket 的表事先已经按照 Keys 进行分区排序,所以做 Shuffle Sort Merge Join 的时候就无需再进行分区和排序了。

a表有100个分区,b表有200个分区,他们是不同的stage而且没有依赖关系,所以可以并行执行。

上面说到我们最大并发task是100,假设a得到50taskb得到50task

采用的是 Sorted-Based Shuffle,因此对于a会有50个shufflemaptask(executor中的线程)依次执行a表100个分区数据的 计算,得到50*2个文件(数据文件和索引文件),数据文件中先按partition排序,再按本身数据的keys排序(在这里就是按id、region字段) 。

同理,50个shufflemaptask依次执行b表200个分片数据的 计算,得到50*2个文件(数据文件和索引文件),数据文件中先按partition排序,再按本身数据的keys排序(在这里就是按id、region字段) 。

我们web ui往往看到task数目分区很多原因这些task不一定同时执行而且有些task可能推测执行会有重复

这些文件的产生,是通过executor本地的BlockManager,同时会上报消息到driver上的BlockManagerMaster,因此在所有的map task执行完毕后,Driver中就掌握了所有的磁盘小文件的地址。

在BlockManager中,根据blockId来进行区分。BlockId不仅是文件名,更是后续BlockManager定位文件的索引,以当前task的shuffle id,分区id(partition id)以及reduceid构造了BlockId。

具体定义可以org.apache.spark.network.shuffle.ExternalShuffleBlockResolver#getBlockData().

注意点:shuffle结果是写到本地存储而不是内存,避免reduce时拉取数据出现异常

2.shuffle read

 一定是先完成 Mapper 端所有的 Tasks,才会进行 Reducer 端的 Shuffle 过程。

在reduce task执行之前,会通过本Executor中MapOutPutTrackerWorker向Driver端的MapOutputTrackerMaster获取磁盘小文件的地址(请求参数:会将当前rdd的shuffle id和分区号,组装成blockId,从对应的BlockManager进行拉取)。

获取到磁盘小文件的地址后,如果是非本地executor数据,则会通过BlockManager中的ConnectionManager连接数据所在executor节点上的ConnectionManager,然后通过BlockTransferService去数据所在executor上拉取数据,根据申请的blockid寻到对应的本地文件以流的形式返回。 

如果是本地executor的数据,那么本地的BlockManager通过fetchLocalBlocks()方法根据BlockId尝试拉取数据。因为是本地,直接可以通过上述的getFiles()方法获取文件,不需要BlockManager之间的网络通信即可获取对应的数据。

Reduce task过来的数据首先会放在 Reducer 端的内存缓存区+ 磁盘 (数据结构类使用了 ExternalAppendOnlyMap)

这一步ab按照keysid,region)相同数据拉到同一个节点

注意在 Reduce 端是没有进行排序(而MapReduce的map和reduce都会排序,排序是MapReduce的灵魂),所以结果默认不是有序的。

3.merge

对来自不同表的排序好的分区数据进行JOIN

即分别遍历两个有序序列,碰到相同join keymerge输出,否则取更小一边,网上找了个示意图:

整个流程可以总结如下图

  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值