news 2026/10/21 9:25:27

External Shuffle Service(spark3_shuffle):原理、配置与排障

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
External Shuffle Service(spark3_shuffle):原理、配置与排障

External Shuffle Service(spark3_shuffle):原理、配置与排障

目录

  1. ESS 解决什么问题
  2. 架构与工作原理
  3. 生产故障复盘(本案)
  4. Hadoop 集群侧配置(NodeManager)
  5. Spark Client 侧配置
  6. 多版本共存:spark_shuffle + spark3_shuffle
  7. Spark 4.0 迁移差异
  8. 排障手册

1. ESS 解决什么问题

正常路径下,map 端 executor 把 shuffle 文件写在自己容器挂载的本地磁盘上,只有该 executor 进程活着才能对外提供读取。一旦 executor 死亡(OOM 被 YARN 杀、Dynamic Allocation 回收、抢占、speculation),本节点上的 shuffle 文件就"无人认领",reduce 端 FetchFailed,只能整 stage 重算。

ESS(External Shuffle Service)的核心设计是把 shuffle 文件的"读服务"与 executor 的生命周期解耦:一个常驻在每个 NodeManager 上的服务,executor 死了它还在,继续替死掉的 executor 提供本节点 shuffle 文件的读取。

三个直接受益场景:

场景依赖 ESS 的原因
Dynamic Allocation(classic 模式)空闲 executor 被回收后,它的 shuffle 数据仍可读;不开 ESS 时 classic 动态分配无法工作(需改用spark.dynamicAllocation.shuffleTracking.enabled=true替代)
executor 被杀 / 节点故障恢复已写完的 shuffle 数据不用重算,代价从"整 stage 重试"降为"重算丢失的分区"
Push-based shuffle(3.2+)merge 目录元数据由 executor 注册时提交给 ESS,merge 数据读取也走 ESS(见第 2 节注册顺序)

注意:ESS 不是 Spark 强制组件。spark.shuffle.service.enabled=false(默认)+ 静态资源、不开 Dynamic Allocation 时可以完全不用;但 executor 一死 shuffle 数据即丢。启用 ESS 后它是硬依赖——本节点的 ESS 不在,executor 直接注册失败自杀(见第 3 节故障)。

2. 架构与工作原理

2.1 部署形态(两种,功能等价)

形态运行位置适合
YARN aux-service(推荐)NodeManager JVM 内部,类org.apache.spark.network.yarn.YarnShuffleServiceYARN 集群标准做法,随 NM 生命周期管理
独立进程每节点单独拉起org.apache.spark.deploy.ExternalShuffleService(Spark 3.4+ 支持 standalone 模式守护)非 YARN(Standalone/K8s)或需要与 NM 解耦运维时

aux-service 名(如spark_shuffle/spark3_shuffle)只是 yarn-site.xml 里的注册名,不是代码里的固定值——4.0 中YarnShuffleService.java:219的super("spark_shuffle")仅是构造默认值,NM 实际按 yarn-site.xml 注册的名字实例化,并按这个名字隔离恢复目录、路由 serviceData。

2.2 整体拓扑

Driver(AM) ── startContainer(serviceData=按service name投递secret) ──▶ NodeManager │ spark3_shuffle (ESS, :7447) 恢复目录: .../nm-aux-services/spark3_shuffle/ ▲ 注册(BlockManager.initialize) executor ──────────────────────────────────────────────────────────────┘ │ MapStatus 里记录的 shuffle 地址 = ESS 的 BlockManagerId(host, servicePort) ▼ reduce 端 fetch shuffle 数据 ──▶ 全部经 ESS,不直连写端的 executor

2.3 注册链路(源码级)

以 3.3 为准(4.0 完全同构,行号见括号):

  1. executor 启动 → BlockManager.initialize。core/src/main/scala/org/apache/spark/storage/BlockManager.scala:526-531(4.0: L550-556):shuffle.service.enabled=true时先构造shuffleServerId = BlockManagerId(executorId, hostName, externalShuffleServicePort)——端口取客户端配置的spark.shuffle.service.port,然后调用registerWithExternalShuffleServer()(driver 不注册)。
  2. 注册先于 BlockManagerMaster 注册,顺序不可反。push-based shuffle 的 merge 目录元数据要随注册报给 ESS(SPARK-39647,BlockManager.scala:520-525注释;4.0 同)。
  3. 同步注册 + 重试。BlockManager.scala:579-610(4.0:registerWithExternalShuffleServer()L601-636):按spark.shuffle.registration.maxAttempts(默认 3)重试,间隔硬编码 5s;全失败抛unableToRegisterWithExternalShuffleServerError→YarnCoarseGrainedExecutorBackend自杀 → container exit code 1。
  4. 秘钥随 container 启动按名字投递。AM 侧resource-managers/yarn/src/main/scala/org/apache/spark/deploy/yarn/ExecutorRunnable.scala:117-121(4.0:configureServiceDataL127-140):以spark.shuffle.service.name为 key 把 secret 作为 serviceData 放进ContainerLaunchContext,NM按名字路由给对应 aux-service 实例。名字不匹配时秘钥送不到服务实例——yarn 侧会打 WARN(“send ServiceData and read Service Meta Data may have issues unless the refer to the name in the config”);未开鉴权时注册仍能靠端口连通,开了spark.authenticate=true后名字错直接导致 shuffle 拉取失败。
  5. 注册信息持久化(recovery)。ESS 把 appId → executor localDirs 映射持久化到yarn.nodemanager.recovery.dir/nm-aux-services/<service名>/(本案实测:/var/lib/hadoop-yarn/yarn-nm-recovery/nm-aux-services/spark3_shuffle/registeredExecutors.ldb)。3.3 隐式 LevelDB;3.4 起可用spark.shuffle.service.db.backend选 LEVELDB/ROCKSDB,4.0 默认 ROCKSDB(见第 7 节)。目录按 service 名隔离,这是多版本共存的基础之一。

2.4 一致性原则:三方对齐

ESS 能用起来的唯一硬约束是三方一致:

一致项配置处
yarn-site.xmlyarn.nodemanager.aux-services列表中的名字↔yarn.nodemanager.aux-services.<名字>.class的前缀
客户端spark.shuffle.service.name(3.2.0 引入)↔ NM 注册的名字(一字不差)
客户端spark.shuffle.service.port↔ 该 service 实例监听端口(多实例并存时必须错开)

名字本身随意,对齐即可;不配 name 时默认spark_shuffle、不配 port 时默认 7337(core/.../internal/config/package.scala,3.3 L716-727 / 4.0 L815-826,默认值两代相同)。

3. 生产故障复盘(本案)

现象:Spark 3.3 任务(application_1791943697415_0001)所有落在 mater01(127.0.0.1)的 container 失败,executor 日志:

ERROR YarnCoarseGrainedExecutorBackend: Executor self-exiting due to : Unable to create executor due to Unable to register with external shuffle server due to : Failed to connect to mater01.catlbattery.com/127.0.0.1:7337 Caused by: java.net.ConnectException: Connection refused at BlockManager.registerWithExternalShuffleServer(BlockManager.scala:596)

定位过程:

  1. 客户端日志锁定调用链BlockManager.initialize → registerWithExternalShuffleServer → TransportClientFactory.createClient,Connection refused语义 = 目标端口无监听(防火墙丢包通常是 timeout)。
  2. 查 NodeManager 日志,发现该集群 ESS 实际状态:YarnShuffleService: Started YARN shuffle service for Spark on port 7447,恢复目录/nm-aux-services/spark3_shuffle/——Spark 3 的 ESS 活着,但监听 7447,注册名 spark3_shuffle。
  3. 根因:NM 侧 Spark 3 ESS =spark3_shuffle : 7447,客户端未配置spark.shuffle.service.name/port,按默认spark_shuffle : 7337去连 → 端口不匹配 → Connection refused。
  4. 日志中另一组ClassNotFoundException: org.apache.spark.network.yarn.YarnShuffleService属于spark_shuffle(Spark 2 那套)初始化失败,与 Spark 3 任务无关。

结论表:

问题结论证据强度
直接报错executor 注册 ESS 重试耗尽自杀,container exit 1确定
根因客户端默认 7337 与集群 spark3_shuffle 实际监听 7447 不匹配确定(NM 日志 + 源码)
修复客户端补spark.shuffle.service.name=spark3_shuffle+spark.shuffle.service.port=7447已确认方案

预防项:spark.yarn.shuffle.stopOnFailure=true(docs/running-on-yarn.md)——ESS 初始化失败时 NM 主动不注册资源,避免 container 被调度到 ESS 坏掉的节点。

4. Hadoop 集群侧配置(NodeManager)

每台 NM 执行(官方流程见 docs/running-on-yarn.md「Configuring the External Shuffle Service」章节):

  1. jar 落位:把spark-<version>-yarn-shuffle.jar(构建产物common/network-yarn/target,发行版在yarn/目录)加入 NM classpath。多版本共存时各版本的 jar 都要落。
  2. yarn-site.xml 注册:
<property><name>yarn.nodemanager.aux-services</name><value>spark_shuffle,spark3_shuffle</value></property><property><name>yarn.nodemanager.aux-services.spark_shuffle.class</name><value>org.apache.spark.network.yarn.YarnShuffleService</value></property><property><name>yarn.nodemanager.aux-services.spark3_shuffle.class</name><value>org.apache.spark.network.yarn.YarnShuffleService</value></property>
  1. 每实例端口错开:通过spark-shuffle-site.xml(ESS 专用配置文件,放 Hadoop conf 目录)或 NM 环境变量为每个实例指定端口,如 Spark 2 → 7337、Spark 3 → 7447。恢复目录按 service 名自动隔离,无需额外配置。
  2. NM 堆放大:yarn-env.sh的YARN_HEAPSIZE(默认 1000MB)适当调大,避免 shuffle 高峰 GC。
  3. 滚动重启全部 NodeManager(aux-service 随 NM 启动,无法单独重载)。
  4. 可选:spark.yarn.shuffle.stopOnFailure=true(见第 3 节)、4.0 的spark.yarn.shuffle.server.recovery.disabled(见第 7 节)。

相关键全集(4.0 实测core/.../internal/config/package.scala:771-826,3.3 同名子集语义一致):

键默认说明
spark.shuffle.service.enabledfalse客户端开关,ESS 是否为硬依赖
spark.shuffle.service.port7337客户端连的端口;ESS 侧监听端口需另行对齐
spark.shuffle.service.namespark_shuffle(3.2.0+)客户端 serviceData 路由名
spark.shuffle.service.removeShuffletrue(3.3.0+)ESS 是否在 shuffle 不再需要时清理块;4.0 起默认真正生效(见第 7 节)
spark.shuffle.service.fetch.rdd.enabledfalseESS 是否代理 RDD block 读取
spark.shuffle.service.db.enabledtrue仅 standalone 形态的持久化开关
spark.shuffle.service.db.backend4.0=ROCKSDB(3.4.0 引入;3.3 无此键,隐式 LEVELDB)见第 7 节
spark.shuffle.registration.timeout/.maxAttempts5000ms / 3客户端注册重试

5. Spark Client 侧配置

spark.shuffle.service.name/port是 SparkConf 级参数,只配在客户端,yarn-site.xml 里写无效(Hadoop 不认识)。

spark-submit\--confspark.shuffle.service.enabled=true\--confspark.shuffle.service.name=spark3_shuffle\--confspark.shuffle.service.port=7447\...

优先级(高→低):代码SparkConf.set>--conf>--properties-file>spark-defaults.conf(SPARK_CONF_DIR优先于$SPARK_HOME/conf)。

落地方式:写进 Spark 3 任务提交网关的spark-defaults.conf或平台提交模板,所有任务默认继承。确认实际生效的那份文件:

grep-r"shuffle.service"$SPARK_CONF_DIR/spark-defaults.conf2>/dev/null\||grep-r"shuffle.service"$SPARK_HOME/conf/spark-defaults.conf2>/dev/null

也可从运行中任务反查:YARN UI → application → Environment 页签搜shuffle.service。

版本约束:spark.shuffle.service.name3.2.0 起生效;Spark 2 任务没有也不读这个参数,天然走默认spark_shuffle。

6. 多版本共存:spark_shuffle + spark3_shuffle

双版本并行的标准姿势(本案集群即此形态):

维度Spark 2Spark 3
aux-service 注册名spark_shufflespark3_shuffle(名字自定义,与 client 对齐即可)
监听端口73377447(必须错开)
恢复目录.../nm-aux-services/spark_shuffle/.../nm-aux-services/spark3_shuffle/(按名隔离,自动)
yarn-shuffle jarSpark 2 发行包Spark 3/4 发行包
客户端配置无需 name/port(默认即中)显式name=spark3_shuffle+port=7447

要点:

  • 两个实例是两个独立进程内对象,各自持有端口、恢复目录、注册表,互不感知;spark_shuffle实例挂掉不影响spark3_shuffle(本案 Spark 2 实例 CNFE 起不来,Spark 3 任务照常可跑,反之亦然)。
  • client 侧spark.shuffle.service.name写错的服务名 = 秘钥投递错对象;未开鉴权时靠端口兜底仍能注册,开鉴权后必挂。所以 name/port 建议同时配齐,不要只修端口。
  • 两套 spark-defaults.conf 建议分开维护,避免把 4.0 专属参数混进 Spark 2 的全局配置。

7. Spark 4.0 迁移差异

总体结论:机制与 3.3 完全同源,配置方式无差异,仅默认行为有三处变化。

兼容面(已核验):enabled/port/name三键键名、默认值、语义不变(4.0package.scala:815-826);注册链路同构(4.0BlockManager.scala:554, 601-636,重试 3 次/5s 不变);serviceData 投递主路径不变(4.0ExecutorRunnable.scala:127-140);YarnShuffleService类与默认名不变(4.0YarnShuffleService.java:219, 338);spark.shuffle.useOldFetchProtocol兼容键仍在。spark3_shuffle + 7447的显式配置可直接沿用;NM 侧 yarn-shuffle.jar 整套替换为 4.0 版本即可。

三处差异:

#差异证据影响
1ESS 本地 db 默认 LEVELDB → ROCKSDB,LEVELDB deprecated,且不自动迁移旧数据docs/core-migration-guide.md L35;package.scala:805-813升级 ESS jar 重启后新建 RocksDB store,旧 LevelDB 保留但不读 → ESS 重启时无法恢复重启前注册的 executor → 运行中任务 fetch 失败。对策:①升级窗口避开运行中任务;②过渡期在 ESS 的spark-shuffle-site.xml显式设db.backend=LEVELDB(仍支持,deprecated)
24.0 起默认由 ESS 清理 deallocated executor 的 shuffle 块(spark.shuffle.service.removeShuffle,回退设 false)docs/core-migration-guide.md L47键 3.3 已存在且默认 true;从未显式配置的集群行为升级,属正向优化(清理更及时)
3新增spark.yarn.shuffle.server.recovery.disabled(默认 false)与YarnShuffleServiceMetrics(metrics2 观测)4.0package.scala:2773;YarnShuffleService.java纯新增,默认行为不变

8. 排障手册

8.1 症状 → 根因速查

症状根因方向
executorConnection refused注册 ESS(BlockManager.registerWithExternalShuffleServer)后自杀、container exit 1目标端口无监听:ESS 未起 /端口不匹配(最常见)/ 防火墙 REJECT
注册连上了但 shuffle fetch 报 executor 找不到 / 鉴权失败spark.shuffle.service.name与 NM 注册名不匹配,serviceData(秘钥)投错对象
NM 日志ClassNotFoundException: org.apache.spark.network.yarn.YarnShuffleService对应实例的 yarn-shuffle.jar 不在 NM classpath(注意区分是哪个实例,另一实例可能正常)
ESS 重启后运行中任务大量 FetchFailedrecovery 未生效(4.0 切 RocksDB 后旧 LevelDB 不迁移,见第 7 节)

8.2 标准排查命令(NM 节点自行执行)

# 1. 端口监听确认(区分 aux-service 内嵌 vs 独立进程)ss-lntp|grep-E'7337|7447'ps-ef|grep-iExternalShuffleService|grep-vgrep# 2. NM 日志确认每个实例的初始化结果与端口grep-iE"YarnShuffleService|spark3?_shuffle|AuxServices"\$(ls-tr/var/log/hadoop-yarn/*nodemanager*2>/dev/null)|tail-30# 3. 远程探测(executor 视角复现)nc-zv<NM_IP>7447# 4. 排除防火墙 REJECT(有监听但仍 refused 时查)sudoiptables-L-n|grep-i7447# 5. 恢复目录确认(名字隔离的证据)ls/var/lib/hadoop-yarn/yarn-nm-recovery/nm-aux-services/

8.3 修复后验证清单

  1. ss -lntp确认目标端口 LISTEN;
  2. 客户端带齐name+port重跑任务;
  3. executor 日志出现Registering executor with the local external shuffle service.且无 refused;
  4. YARN UI 确认任务正常完成;多版本共存时顺带确认另一版本的 ESS 实例未受影响。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 9:25:14

鸿蒙应用包结构拆解:HAP/HAR/HSP与多模块打包避坑指南

先说一个前几天真实遇到的问题。某开发者把应用从单模块拆成多模块&#xff0c;没动任何业务逻辑&#xff0c;只是把公共部分抽了几个共享包出来。结果release包一打&#xff0c;体积反而比原来大了将近一倍。折腾了一下午&#xff0c;最后发现原因特别基础——共享代码被打进了…

作者头像 李华
网站建设 2026/10/21 9:24:32

开放式代码评审:从流程设计到工具链落地的工程实践指南

代码评审这件事&#xff0c;我做了快十年。从最早的邮件发补丁、QQ群里喊人“帮我看一眼”&#xff0c;到后来用Git平台的Pull Request、MR功能&#xff0c;再到给团队搭一整套带自动化检查的评审流水线&#xff0c;绕了不少路。今天想聊的“open-code-review”&#xff0c;并不…

作者头像 李华
网站建设 2026/10/21 9:24:30

AE影视后期特效制作教学设计:从课程骨架到实例教案的完整拆解

简介&#xff1a;这是一份面向影视后期专业教师与初学者的AE教学设计教案&#xff0c;围绕《AE影视后期特效制作实例教程》核心内容展开&#xff0c;系统覆盖影视特效岗位能力要求、特效分类与作用&#xff0c;以及帧、帧率、时间码、景别和镜头等视频基础概念。包体为单个PDF文…

作者头像 李华
网站建设 2026/10/21 9:23:54

随机森林真的不怕多重共线性?实验揭示特征重要性失真的真相

最近在做一个特征筛选项目&#xff0c;有个同事跟我说&#xff1a;“树模型不怕多重共线性&#xff0c;你就放心把相关特征都丢进去吧。”这话对吗&#xff1f;对&#xff0c;但又不完全对。随机森林对多重共线性的“容忍度”确实远高于线性回归&#xff0c;但“容忍”不等于“…

作者头像 李华
网站建设 2026/10/21 9:22:59

2026医疗行业趋势:医保改革、创新药出海与国产替代三大主线解析

2026年这个节点&#xff0c;医疗行业到了一个特别微妙的观察窗口。我这一年多来陆续攒了230多份研报和政策文件&#xff0c;横跨医保改革、创新药、国产替代三条主线&#xff0c;反复看下来发现&#xff0c;单看任何一条线都可能得出偏颇的结论&#xff0c;但把三条线放到一起&…

作者头像 李华
网站建设 2026/10/21 9:20:53

从零搭建开源代码评审工具:规则引擎与大模型语义评审实践

1. 从零搭建代码评审工具&#xff1a;为什么我要造这个轮子代码评审这件事&#xff0c;做过团队协作开发的人都懂——它既是保证代码质量最有效的手段&#xff0c;也是最容易流于形式的环节。我所在的团队前后用过不少方案&#xff0c;从最原始的邮件发patch&#xff0c;到后来…

作者头像 李华