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

📅 发布时间:2026/10/21 9:25:32
External Shuffle Service(spark3_shuffle):原理、配置与排障
External Shuffle Servicespark3_shuffle原理、配置与排障目录ESS 解决什么问题架构与工作原理生产故障复盘本案Hadoop 集群侧配置NodeManagerSpark Client 侧配置多版本共存spark_shuffle spark3_shuffleSpark 4.0 迁移差异排障手册1. ESS 解决什么问题正常路径下map 端 executor 把 shuffle 文件写在自己容器挂载的本地磁盘上只有该 executor 进程活着才能对外提供读取。一旦 executor 死亡OOM 被 YARN 杀、Dynamic Allocation 回收、抢占、speculation本节点上的 shuffle 文件就无人认领reduce 端 FetchFailed只能整 stage 重算。ESSExternal Shuffle Service的核心设计是把 shuffle 文件的读服务与 executor 的生命周期解耦一个常驻在每个 NodeManager 上的服务executor 死了它还在继续替死掉的 executor 提供本节点 shuffle 文件的读取。三个直接受益场景场景依赖 ESS 的原因Dynamic Allocationclassic 模式空闲 executor 被回收后它的 shuffle 数据仍可读不开 ESS 时 classic 动态分配无法工作需改用spark.dynamicAllocation.shuffleTracking.enabledtrue替代executor 被杀 / 节点故障恢复已写完的 shuffle 数据不用重算代价从整 stage 重试降为重算丢失的分区Push-based shuffle3.2merge 目录元数据由 executor 注册时提交给 ESSmerge 数据读取也走 ESS见第 2 节注册顺序注意ESS 不是 Spark 强制组件。spark.shuffle.service.enabledfalse默认 静态资源、不开 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.ExternalShuffleServiceSpark 3.4 支持 standalone 模式守护非 YARNStandalone/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不直连写端的 executor2.3 注册链路源码级以 3.3 为准4.0 完全同构行号见括号executor 启动 → BlockManager.initialize。core/src/main/scala/org/apache/spark/storage/BlockManager.scala:526-5314.0: L550-556shuffle.service.enabledtrue时先构造shuffleServerId BlockManagerId(executorId, hostName, externalShuffleServicePort)——端口取客户端配置的spark.shuffle.service.port然后调用registerWithExternalShuffleServer()driver 不注册。注册先于 BlockManagerMaster 注册顺序不可反。push-based shuffle 的 merge 目录元数据要随注册报给 ESSSPARK-39647BlockManager.scala:520-525注释4.0 同。同步注册 重试。BlockManager.scala:579-6104.0:registerWithExternalShuffleServer()L601-636按spark.shuffle.registration.maxAttempts默认 3重试间隔硬编码 5s全失败抛unableToRegisterWithExternalShuffleServerError→YarnCoarseGrainedExecutorBackend自杀 → container exit code 1。秘钥随 container 启动按名字投递。AM 侧resource-managers/yarn/src/main/scala/org/apache/spark/deploy/yarn/ExecutorRunnable.scala:117-1214.0:configureServiceDataL127-140以spark.shuffle.service.name为 key 把 secret 作为 serviceData 放进ContainerLaunchContextNM按名字路由给对应 aux-service 实例。名字不匹配时秘钥送不到服务实例——yarn 侧会打 WARN“send ServiceData and read Service Meta Data may have issues unless the refer to the name in the config”未开鉴权时注册仍能靠端口连通开了spark.authenticatetrue后名字错直接导致 shuffle 拉取失败。注册信息持久化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 隐式 LevelDB3.4 起可用spark.shuffle.service.db.backend选 LEVELDB/ROCKSDB4.0 默认 ROCKSDB见第 7 节。目录按 service 名隔离这是多版本共存的基础之一。2.4 一致性原则三方对齐ESS 能用起来的唯一硬约束是三方一致一致项配置处yarn-site.xmlyarn.nodemanager.aux-services列表中的名字↔yarn.nodemanager.aux-services.名字.class的前缀客户端spark.shuffle.service.name3.2.0 引入↔ NM 注册的名字一字不差客户端spark.shuffle.service.port↔ 该 service 实例监听端口多实例并存时必须错开名字本身随意对齐即可不配 name 时默认spark_shuffle、不配 port 时默认 7337core/.../internal/config/package.scala3.3 L716-727 / 4.0 L815-826默认值两代相同。3. 生产故障复盘本案现象Spark 3.3 任务application_1791943697415_0001所有落在 mater01127.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)定位过程客户端日志锁定调用链BlockManager.initialize → registerWithExternalShuffleServer → TransportClientFactory.createClientConnection refused语义 目标端口无监听防火墙丢包通常是 timeout。查 NodeManager 日志发现该集群 ESS 实际状态YarnShuffleService: Started YARN shuffle service for Spark on port 7447恢复目录/nm-aux-services/spark3_shuffle/——Spark 3 的 ESS 活着但监听 7447注册名 spark3_shuffle。根因NM 侧 Spark 3 ESS spark3_shuffle : 7447客户端未配置spark.shuffle.service.name/port按默认spark_shuffle : 7337去连 → 端口不匹配 → Connection refused。日志中另一组ClassNotFoundException: org.apache.spark.network.yarn.YarnShuffleService属于spark_shuffleSpark 2 那套初始化失败与 Spark 3 任务无关。结论表问题结论证据强度直接报错executor 注册 ESS 重试耗尽自杀container exit 1确定根因客户端默认 7337 与集群 spark3_shuffle 实际监听 7447 不匹配确定NM 日志 源码修复客户端补spark.shuffle.service.namespark3_shufflespark.shuffle.service.port7447已确认方案预防项spark.yarn.shuffle.stopOnFailuretruedocs/running-on-yarn.md——ESS 初始化失败时 NM 主动不注册资源避免 container 被调度到 ESS 坏掉的节点。4. Hadoop 集群侧配置NodeManager每台 NM 执行官方流程见 docs/running-on-yarn.md「Configuring the External Shuffle Service」章节jar 落位把spark-version-yarn-shuffle.jar构建产物common/network-yarn/target发行版在yarn/目录加入 NM classpath。多版本共存时各版本的 jar 都要落。yarn-site.xml 注册propertynameyarn.nodemanager.aux-services/namevaluespark_shuffle,spark3_shuffle/value/propertypropertynameyarn.nodemanager.aux-services.spark_shuffle.class/namevalueorg.apache.spark.network.yarn.YarnShuffleService/value/propertypropertynameyarn.nodemanager.aux-services.spark3_shuffle.class/namevalueorg.apache.spark.network.yarn.YarnShuffleService/value/property每实例端口错开通过spark-shuffle-site.xmlESS 专用配置文件放 Hadoop conf 目录或 NM 环境变量为每个实例指定端口如 Spark 2 → 7337、Spark 3 → 7447。恢复目录按 service 名自动隔离无需额外配置。NM 堆放大yarn-env.sh的YARN_HEAPSIZE默认 1000MB适当调大避免 shuffle 高峰 GC。滚动重启全部 NodeManageraux-service 随 NM 启动无法单独重载。可选spark.yarn.shuffle.stopOnFailuretrue见第 3 节、4.0 的spark.yarn.shuffle.server.recovery.disabled见第 7 节。相关键全集4.0 实测core/.../internal/config/package.scala:771-8263.3 同名子集语义一致键默认说明spark.shuffle.service.enabledfalse客户端开关ESS 是否为硬依赖spark.shuffle.service.port7337客户端连的端口ESS 侧监听端口需另行对齐spark.shuffle.service.namespark_shuffle3.2.0客户端 serviceData 路由名spark.shuffle.service.removeShuffletrue3.3.0ESS 是否在 shuffle 不再需要时清理块4.0 起默认真正生效见第 7 节spark.shuffle.service.fetch.rdd.enabledfalseESS 是否代理 RDD block 读取spark.shuffle.service.db.enabledtrue仅 standalone 形态的持久化开关spark.shuffle.service.db.backend4.0ROCKSDB3.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.enabledtrue\--confspark.shuffle.service.namespark3_shuffle\--confspark.shuffle.service.port7447\...优先级高→低代码SparkConf.set--conf--properties-filespark-defaults.confSPARK_CONF_DIR优先于$SPARK_HOME/conf。落地方式写进 Spark 3 任务提交网关的spark-defaults.conf或平台提交模板所有任务默认继承。确认实际生效的那份文件grep-rshuffle.service$SPARK_CONF_DIR/spark-defaults.conf2/dev/null\||grep-rshuffle.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 3aux-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默认即中显式namespark3_shuffleport7447要点两个实例是两个独立进程内对象各自持有端口、恢复目录、注册表互不感知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-140YarnShuffleService类与默认名不变4.0YarnShuffleService.java:219, 338spark.shuffle.useOldFetchProtocol兼容键仍在。spark3_shuffle 7447的显式配置可直接沿用NM 侧 yarn-shuffle.jar 整套替换为 4.0 版本即可。三处差异#差异证据影响1ESS 本地 db 默认 LEVELDB → ROCKSDBLEVELDB deprecated且不自动迁移旧数据docs/core-migration-guide.md L35package.scala:805-813升级 ESS jar 重启后新建 RocksDB store旧 LevelDB 保留但不读 → ESS 重启时无法恢复重启前注册的 executor → 运行中任务 fetch 失败。对策①升级窗口避开运行中任务②过渡期在 ESS 的spark-shuffle-site.xml显式设db.backendLEVELDB仍支持deprecated24.0 起默认由 ESS 清理 deallocated executor 的 shuffle 块spark.shuffle.service.removeShuffle回退设 falsedocs/core-migration-guide.md L47键 3.3 已存在且默认 true从未显式配置的集群行为升级属正向优化清理更及时3新增spark.yarn.shuffle.server.recovery.disabled默认 false与YarnShuffleServiceMetricsmetrics2 观测4.0package.scala:2773YarnShuffleService.java纯新增默认行为不变8. 排障手册8.1 症状 → 根因速查症状根因方向executorConnection refused注册 ESSBlockManager.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-E7337|7447ps-ef|grep-iExternalShuffleService|grep-vgrep# 2. NM 日志确认每个实例的初始化结果与端口grep-iEYarnShuffleService|spark3?_shuffle|AuxServices\$(ls-tr/var/log/hadoop-yarn/*nodemanager*2/dev/null)|tail-30# 3. 远程探测executor 视角复现nc-zvNM_IP7447# 4. 排除防火墙 REJECT有监听但仍 refused 时查sudoiptables-L-n|grep-i7447# 5. 恢复目录确认名字隔离的证据ls/var/lib/hadoop-yarn/yarn-nm-recovery/nm-aux-services/8.3 修复后验证清单ss -lntp确认目标端口 LISTEN客户端带齐nameport重跑任务executor 日志出现Registering executor with the local external shuffle service.且无 refusedYARN UI 确认任务正常完成多版本共存时顺带确认另一版本的 ESS 实例未受影响。