接触 Spark 的第一周,我还在死磕日志。任务一跑挂,就开始翻 driver 日志、翻 executor 日志,但集群上几十个节点,日志散得到处都是,翻到最后也说不清问题出在哪个阶段。直到有人提醒我:Spark 自带的 Web UI 才是第一排查入口。这份使用指导就是写给新手的,完整讲清楚 Spark Web UI 怎么打开、每个页面看什么、哪些信号是坑,顺便分享一些我实际操作中踩过的小坑。你只需要会跑最简单的spark-submit或pyspark,就能照着这篇文章把整套界面用起来。
1. 先打破一个误区:日志不是第一排查入口,Web UI 才是
1.1 分散在集群各处的日志,怎么看都是盲人摸象
Spark 应用一旦提交到集群,Driver 负责调度,Executor 则分散在多个节点上真正干活。所谓"看日志",默认情况下看到的是 Driver 端的输出;但真正执行计算的 Task,日志会写到各个 Executor 所在节点的本地目录里。如果没有做集中式日志聚合,你面对的就是一堆散落在不同机器上的文件,查一次问题得先搞清楚去哪台机器找哪个用户目录,费时费力。
更麻烦的是,一个简单的groupBy().count(),底层可能被拆成多个 Stage、几十上百个 Task。每个 Task 的执行状态、输入输出大小、GC 耗时,如果只靠日志来判断,几乎不可能拼出全貌。Web UI 解决的就是这个痛点:Spark 运行时会把所有 Executor 的状态、Task 进度、Shuffle 读写、内存占用等信息汇总到统一的页面上,你不需要一台台机器去翻,打开浏览器就能看到整体运行情况。
所以我的建议很直接:遇到问题,先开 Web UI,按 Job、Stage、Task 的顺序去定位,最后才用日志验证具体异常。把 Web UI 当成仪表盘,把日志当成修车时的检测仪。一上来就扎进日志里,容易迷失方向。
1.2 Web UI 到底帮你汇总了什么信息
默认的 Spark Web UI 提供这些标签页:
- Jobs:展示当前应用触发的所有 Job 列表,包含每个 Job 的状态、耗时、Stage 数量。
- Stages:展示每个 Job 被拆成的 Stage 明细,以及每个 Stage 下面所有 Task 的执行情况。
- Storage:展示缓存到内存或磁盘的 RDD、DataFrame 信息。
- Environment:展示当前应用的 Spark 配置、JVM 参数、环境变量,排查配置是否生效特别有用。
- Executors:展示每个 Executor 的资源使用、任务数、GC 时间、Shuffle 读写量。
- SQL:展示通过 SQL 或 DataFrame API 执行的查询计划与执行指标。
对新手来说,前三个标签页(Jobs、Stages、Executors)加上 SQL 页,基本覆盖了 90% 的排查场景。Storage 页只有在主动使用cache()、persist()时才需要关注;Environment 页更多是配置对账时用。
1.3 用看仪表盘的心态去看 Spark 作业
习惯了看日志的人,拿到 Web UI 后容易陷入另一个极端:每一个数字都要点开研究,最后反而被信息淹没。我建议新手先建立一条固定观察链路:
- 先看 Jobs 列表,确认任务到底触发了几次 Action,哪一次最慢。
- 点进最慢的 Job,看它包含几个 Stage,哪个 Stage 耗时占比最高。
- 进入耗时最高的 Stage,看 Task 列表,比较不同 Task 的耗时和 Shuffle Read / Write。
- 如果涉及 SQL,再去 SQL 页面看执行计划,确认是否有异常 Exchange 或大面积扫描。
这条链路本质上是"从粗到细、从全局到局部"。等你看多了,会发现大部分问题在第三步就能暴露:要么数据倾斜、要么并行度不足、要么某个 Executor 出了状况。这时候再结合日志确认根因,速度会比以前快很多。
2. 打开 Web UI 的三种入口与常见端口问题
2.1 本地交互模式:pyspark 启动后 4040 就是驾驶舱
本地调试是最快的学习方式。你只需要在装有 Spark 的机器上执行:
pyspark --master local[2]启动完成后,控制台会输出一行类似这样的信息:
Spark context Web UI available at http://127.0.0.1:4040在浏览器打开http://127.0.0.1:4040就能看到实时界面。如果你是在远程服务器上跑pyspark,记得做端口转发,比如:
ssh -L 4040:localhost:4040 username@server_ip否则你在自己电脑上访问localhost:4040是打不开的。
这里有一个新手很容易困惑的现象:如果当前机器上已经有一个 SparkContext 占用了 4040 端口,新启动的会话会自动尝试 4041、4042……对应的启动信息里也会写明具体的端口。所以如果你同时开着多个pyspark窗口,访问 4040 看不到内容,不妨检查一下是否已经递增到了 4041 或更高。
2.2 集群提交模式:从 YARN 页面点进实时 UI
当应用通过spark-submit --master yarn提交时,你不会直接在本地看到 4040 页面,而是要去 YARN 的 ResourceManager 界面查看。进入 ResourceManager 页面后,找到你的 Application ID,点击对应的 Tracking URL,通常会跳转到 ApplicationMaster 或 Spark UI 页面。
这里要记住一个关键区别:YARN 页面上的链接是实时 UI,应用一旦运行结束,这个链接大概率会失效。如果任务已经跑完,但你又想看当时的执行细节,就得靠 History Server,也就是下一节要讲的内容。
另外,--deploy-mode client和cluster模式下,Tracking URL 指向的位置不同。client 模式下 UI 仍然在提交节点的 Driver 上;cluster 模式下 UI 在 YARN 启动的 ApplicationMaster 容器里。新手不用死记,只要知道入口在 ResourceManager 页面就行。
2.3 配置 History Server:让跑完的作业也能回放
Spark Web UI 的生命周期和 SparkContext 绑定,应用退出后页面随之消失。为了对已结束的应用做复盘、性能对比和问题追溯,需要开启事件日志,并用 History Server 加载这些日志来重建 UI。
在spark-defaults.conf中加入以下配置:
spark.eventLog.enabled=true spark.eventLog.dir=file:///tmp/spark-events spark.history.fs.logDirectory=file:///tmp/spark-events spark.history.ui.port=18080然后启动 History Server:
$SPARK_HOME/sbin/start-history-server.sh浏览器访问http://localhost:18080,就能看到已完成的 Spark 应用列表。实际生产环境里,spark.eventLog.dir一般配置成 HDFS 路径,这样集群里所有节点都能把事件日志写到同一个地方,History Server 也便于统一读取。
三种入口的对照关系如下:
| 入口 | 适用场景 | 核心注意事项 |
|---|---|---|
| 本地 4040 | 单机调试、学习 | 多个上下文会占 4041、4042;远程需端口转发 |
| YARN 页面 | 集群运行中的实时监控 | 应用结束后链接失效 |
| History Server 18080 | 已结束应用的复盘 | 必须提前开启 event log,否则无数据可读 |
2.4 端口打不开的自查顺序
Web UI 打不开时,不要急着怀疑 Spark 出了问题,先按顺序排查:
- 确认 SparkContext 是否真的创建成功。如果
pyspark或应用本身已经报错退出,UI 自然不存在。 - 确认访问地址是否正确。服务器上使用
localhost,远程访问要换成服务器 IP 或做隧道转发。 - 检查端口是否在监听。执行
netstat -tlnp | grep 4040或lsof -i:4040,查看有没有进程绑定。 - 检查防火墙和安全组。云服务器上常见的情况是端口被安全组规则拦住了。
- 检查是否存在代理环境变量干扰。有些终端配了
http_proxy,访问本地地址也会走代理,导致连不上。
这套流程基本能覆盖 95% 的"打不开"问题。我之前遇到过最隐蔽的一种情况:应用挂在后台运行,但终端代理把所有请求都转发到了代理服务器,浏览器访问localhost被代理接管,绕来绕去反而超时。关掉代理后立刻恢复正常。
3. 新手最该盯的三块面板:Jobs、Stages 和 Executors
3.1 Jobs 页:从 Action 维度看任务全貌
Jobs 页面展示的是当前应用里所有被 Action 触发的作业。show()、count()、save()、collect()这类操作都会产生一个 Job。页面上能看到 Job ID、描述、提交时间、耗时、状态和对应的 Stage 数量。
新手看 Jobs 页,核心是搞清两件事:第一,你的业务逻辑到底生成了多少个 Job;第二,哪个 Job 最慢、最可疑。点击某个 Job,可以进入 Stages 视图,进一步展开调度细节。
有一个常见误区需要澄清:一个 Spark 应用(Application)可以包含多个 Job,一个 Job 又可以包含多个 Stage,而 Stage 的划分依据是 Shuffle 边界。所以如果你看到 Job 数量比预期多,不要慌,先看是不是代码里有多处 Action 操作。每一处count()、show()都会独立触发任务调度。
3.2 Stages 页:真正决定你作业性能的地方
Stages 是 Web UI 里信息量最大、也是最需要花时间读的一块。每个 Stage 代表一组可以并行计算的 Task,它们之间通过 Shuffle 进行数据交换。页面顶部会显示当前 Stage 的 DAG 摘要、总 Task 数、成功与失败数量、数据本地性级别。
点进任意 Stage,下方会出现 Task 明细表。新手重点关注这些列:
| 列名 | 含义 | 观察重点 |
|---|---|---|
| Duration | Task 执行耗时 | 如果少数 Task 耗时远大于中位数,考虑数据倾斜 |
| GC Time | 垃圾回收时间 | 偏高说明内存压力大,可能频繁 Full GC |
| Shuffle Read | 从其他 Task 读取的数据量 | 单个 Task 独大往往是倾斜信号 |
| Shuffle Write | 写出到下游阶段的数据量 | 数据量异常大说明中间结果膨胀 |
| Locality Level | 数据本地性 | PROCESS_LOCAL 最优,NODE_LOCAL 次之 |
不要一开始就试图理解 DAG 里的每一个算子,先对比 Task 之间的数据。如果 100 个 Task 里有 99 个几百毫秒就跑完,剩下一个跑了 5 分钟,那基本可以断定是数据倾斜,不需要再看任何日志。
3.3 Executors 页:看谁背锅、谁内存紧张
Executors 页面展示每个 Executor 的地址、执行的 Task 数量、失败任务数、GC 耗时、Shuffle 读写量、Storage Memory 占用和峰值内存。
这里有一件事新手容易误解:页面上显示的 Memory 并不是 Executor 进程的全部物理内存,而是 Spark 管理的堆内内存中已使用部分。JVM 堆外、元空间、YARN 容器开销并不直接反映在这个数字里。所以当任务报物理内存超限时,你去 Executors 页看到的 Used Memory 可能还是正常的,这点要特别警惕。
另一个值得关注的信号是 Dead Executor。如果某个 Executor 状态变成 Dead,页面里对应记录消失或显示为结束状态,往往意味着它被 YARN 判定超内存而终止。结合 GC Time 异常升高,基本可以锁定是内存配置或内存压力问题。
3.4 三个面板的联动读法
Jobs、Stages、Executors 不是孤立的三张表,而是互相联动的。从 Jobs 进去看 Stage,从 Stage 进去看 Task,从 Task 行里的 Executor 字段又能跳转回 Executors 页面。新手建议养成这样的观察习惯:
- 宏观判断用 Jobs:确认慢点集中在哪个业务动作上。
- 中观定位用 Stages:确认慢点集中在哪个 Shuffle 阶段或哪个计算算子。
- 微观确认用 Task 列表:对比单任务耗时、读写量、GC,锁定数据分布异常。
这套联动逻辑熟练之后,你在群里帮别人排查 Spark 问题时,往往一句"看某 Stage 最大耗时是不是比中位数高一个数量级"就能命中要害。
4. 用 SQL 面板顺藤摸瓜:从执行计划里看查询底色
4.1 哪些操作会出现在 SQL 面板
只要代码里使用了 Spark SQL 或 DataFrame API,Web UI 顶部通常会出现 SQL 标签页,且每条查询会生成一行记录,包含提交时间、查询描述、状态和耗时。点击查询 ID,就能进入该查询的详细执行计划页面。
新手经常忽略这个页面,但它比 Stages 页更接近问题本质:Stages 告诉你"哪里慢",SQL 执行计划告诉你"为什么这么设计"。比如表面上看某个 Job 很慢,实际原因可能是某个 Join 没有走广播策略,导致参与 shuffle 的数据量巨大,这些信息都能在 SQL 页面找到线索。
4.2 读懂 Plan 里的 Scan、Exchange、Aggregate
以一段简单的聚合查询为例:
SELECT region, sum(amount) FROM orders GROUP BY region在 SQL 详情页面,你会看到从下往上排列的物理执行计划节点,简单版本大致是:
- Scan parquet orders:读取订单表。
- Filter:过滤不需要的数据。
- HashAggregate:先在每个分区内做部分聚合。
- Exchange:按 region 字段进行 Shuffle,把相同 region 的数据路由到同一个 Task。
- HashAggregate:再次聚合,得到最终结果。
这里的 Exchange 就是 Stage 划分的关键。看到 Exchange,就意味着发生了 Shuffle,前后是两个不同的 Stage。新手至少要做到:能认出 Scan、Exchange、Aggregate、Join 四种节点,这样别人讨论执行计划时你就不会一头雾水。
4.3 通过 Exchange 数量检查分区设置
Exchange 节点在界面上会显示 Shuffle 的分区数。Spark 默认的 shuffle 分区数是 200,也就是spark.sql.shuffle.partitions的默认值。如果业务数据量很小,比如只有几 MB,却生成了 200 个分区,那绝大多数分区里只有零星几条数据,任务之间会严重不平衡,性能反而下降。
通过 SQL 页面观察到 Exchange 分区数异常后,可以结合两个方向调整:一是调低或调高spark.sql.shuffle.partitions,匹配数据量;二是如果某个倾斜 key 特别大,考虑加盐或单独处理。不要看到数字就改,先理解你当前的数据规模对应多少分区合理。
4.4 判断 Join 方式是否合理
执行计划里,两张表 Join 时会出现BroadcastHashJoin或SortMergeJoin。前者表示小表被广播到所有 Executor,不产生大规模 Shuffle;后者表示两个大表都要按 Join key 排序,伴随明显的 Exchange 和 Sort。
如果小表只有几百 KB,但执行计划里走的是 SortMergeJoin,那大概率是广播阈值配置未生效或者小表实际大小超过了默认的 10MB 阈值。遇到这类情况,可以在 SQL 页面确认计划后,再考虑调整spark.sql.autoBroadcastJoinThreshold,或使用提示强制广播。
看 SQL 面板的目的不是让你变成执行计划专家,而是让你在慢查询出现时能快速定位"是哪一步换数据换得太多""是不是 Join 策略不合理"。这比单纯盯着日志猜测要靠谱得多。
5. 危险信号提前读:数据倾斜、内存告急与任务重试
5.1 数据倾斜在 UI 上的长相
数据倾斜是 Spark 新手最容易遇到、也最容易凭直觉误判的问题。从 Web UI 上看,最典型的特征是:某个 Stage 里绝大多数 Task 很快就结束,但一两个 Task 耗时特别长,同时这两三个 Task 的 Shuffle Read 或输入数据量比其他 Task 大一个数量级。
在 Stage 任务表中,直接看 Duration 中位数与最大值之间的差距。如果中位数是 2 秒,最大值是 20 分钟,不用怀疑,基本就是倾斜。处理手段包括:
- 增加 Shuffle 分区数,让大 key 平均分摊到更多 Task。
- 对热点 key 加盐,进行两阶段聚合。
- 能广播的小表就广播,避免大表与大表在大 key 上碰撞。
- 极端情况下单独拆分热点 key 做特殊逻辑。
UI 的作用是尽早确认"到底是不是倾斜",而不是在猜测中做调优。有了数据支撑,方案才有针对性。
5.2 常见 OOM 与 Executor Lost 的 UI 特征
内存问题在 Web UI 上的信号不是单一出现的,而是多指标组合:某个 Executor 的 GC Time 持续增长;Task 列表里反复出现失败记录;Executors 页面出现 Dead Executor 或 Executor 不再响应;偶尔伴随 Shuffle Spill 数值上升。
Shuffle Spill 表示数据写到了磁盘,它本身不是致命错误,但如果一个本来应该几百 MB 就能完成的任务反复 Spill,说明执行内存非常紧张,已经触发了频繁的落盘与读盘。此时再看 Executor 的峰值内存,如果接近或超过配置上限,大概率会紧接着报 OOM。
一个常见组合是:spark.executor.memory设置了较大值,但没有预留足够的 YARN 容器额外开销,或者堆内与堆外内存比例不合理。从 Executors 页看到 Dead Executor 后,第一步不是马上加内存,而是先看这个 Executor 之前运行的 Task 数据量、GC 时间和 Spill 情况。很多时候问题根源是并行度不足、数据倾斜或缓存滥用。
5.3 推测执行与重复任务到底是不是 bug
某些集群会开启推测执行,也就是当一个 Task 明显落后于同 Stage 的其他 Task 时,Spark 会在另一台 Executor 上重新提交同一个 Task,谁先跑完就算谁成功。这时候在 Stage 任务列表里会看到同一个 Partition 出现两次,分别在不同 Executor 上执行。
新手第一次看到这种情况容易以为 Spark 重复计算了,其实这是正常的容错机制。但如果推测执行频繁触发,说明节点之间存在严重的性能差异或资源配置不均。从 UI 上看,你只需要注意 Task ID 是否出现多个副本,以及 Executor 节点是否集中在某一台机器上。如果某台 Executor 频繁成为"慢节点",那问题可能出在那台机器的磁盘、网络或资源争用上,而不是程序本身。
5.4 从事件时间线确认并行度
Stage 页面有一块 Event Timeline,按时间轴展示每个 Task 的启动与结束区间。新手可以从中直观地看出当前 Stage 的真实并行度。
如果你分配了 100 个并行 slot,但时间轴上同一时刻只有 2、3 个任务在运行,说明并行度被某个环节限制住了。常见原因有两个:一是前置阶段的数据没有被充分分区,读入时只有少数文件;二是 Shuffle 分区数远小于 Executor 并发能力。看到这样的时间线,再去调整spark.sql.shuffle.partitions或输入文件的分片大小,就会有的放矢。
6. 作业跑完还能接着看:History Server 的正确用法
6.1 为什么实时 UI 一关就没了
Spark Web UI 是运行在 Driver 进程里的,应用一旦结束,Driver 退出,端口自然关闭。所以只要没提前配置事件日志,作业跑完后再想回看刚才的界面就不可能了。
History Server 的工作原理是预先开启spark.eventLog.enabled,Spark 会把每个应用的执行事件持久化到指定目录,History Server 再读取这些事件文件,在浏览器里重新渲染出与实时 UI 几乎一致的界面。因此它非常适合做两件事:凌晨跑挂的定时任务复盘,以及不同版本代码之间的性能对比。
6.2 配置步骤与常见坑
再次给出完整的本地配置参考:
# spark-defaults.conf spark.eventLog.enabled=true spark.eventLog.dir=file:///tmp/spark-events spark.history.fs.logDirectory=file:///tmp/spark-events spark.history.ui.port=18080配置完成后,启动 History Server:
$SPARK_HOME/sbin/start-history-server.sh然后访问http://localhost:18080。
实际使用中有几个坑很常见:
- 忘记创建事件日志目录。本地使用
file:///tmp/spark-events时,目录不存在会导致应用启动失败,先执行mkdir -p /tmp/spark-events。 - 集群使用时没有统一路径。多节点环境下必须把事件日志写到 HDFS 或共享存储,否则每个节点只能看到自己的日志,History Server 读不全。
- 事件日志累积占用空间。长时间不清理会导致磁盘或 HDFS 目录膨胀,需要按应用 ID 定期清理过期目录。
- History Server 权限不足。日志目录的属主与启动 History Server 的用户不一致时,界面里会是空白列表。
6.3 用 History Server 做事故复盘和性能对比
一旦 History Server 正常运转,你就能像翻相册一样查看历史应用。复盘时推荐对比两方面的数据:一是同一应用中各 Stage 的耗时占比,二是相似任务在不同运行时的 Shuffle 和 GC 表现。
比如你优化了某个聚合逻辑,跑完两个版本后,在 History Server 里分别打开两个应用,对比 Shuffle Write 总量和关键 Stage 的耗时。如果写出去的数据量减少、Stage 耗时下降,说明优化有效;如果只是换了一种执行计划但 Shuffle 数据没变,那优化可能只停留在表面。
实时 UI 和 History Server 的定位差异可以看这张表:
| 对比项 | 实时 Web UI | History Server |
|---|---|---|
| 数据来源 | 运行中的 SparkContext | 持久化的事件日志 |
| 可用时间 | 应用运行期间 | 应用结束后长期可查 |
| 主要用途 | 实时监控、现场排查 | 事后复盘、性能对比 |
| 前提条件 | 无 | 开启 event log 并配置日志目录 |
7. 最小化上手指引:一次 pyspark 任务的完整 UI 阅读
7.1 本地启动并记录 UI 地址
先在本地准备一个可用的 Spark 环境,然后执行:
mkdir -p /tmp/spark-events pyspark --master local[2]启动后确认输出里的 Web UI 地址,记住这个端口。如果机器上已有多个 Spark 会话,记得留意端口递增的情况。
7.2 跑一段带 Shuffle 的示例代码
在pyspark交互式环境里粘贴下面的代码:
df = spark.createDataFrame( [ (1, "华北", 100), (2, "华南", 200), (3, "华北", 150), (4, "华南", 80), ], ["id", "region", "amount"] ) df.groupBy("region").sum("amount").show()这段代码会触发一个 Action,也就是show()。数据量非常小,但足够让你看到完整的 Job、Stage、Task 三级结构,以及groupBy引入的 Shuffle 过程。
7.3 按 Job → Stage → Task 的顺序走一遍
打开 Web UI 的 Jobs 标签页,你会看到刚才触发的 Job。点击 Job ID 进入 Stage 页面,可以看到两个 Stage:第一个是每个分区内的局部聚合,第二个是经过 Exchange 后的最终聚合。
再点进第二个 Stage,查看 Task 明细。这里能看到 Shuffle Read 和 Shuffle Write 数值。由于数据量很小,耗时可能只有几十毫秒,但你已经可以看到关键指标了。为了进一步熟悉 Shuffle,可以再执行一句:
df.repartition(10).count()刷新页面后,你会发现这个 Job 多出几个 Stage,其中repartition会强制产生一次 Shuffle。这个练习的目的就是在实际 UI 里建立"哪些算子会产生 Shuffle"的直觉。
7.4 主动制造一次失败,看看 UI 的报错样貌
为了熟悉故障画面,可以故意读取一个不存在的文件:
spark.read.parquet("/tmp/not_exist_file.parquet").show()此时回到 Jobs 页面,会看到新 Job 的状态变成 Failed。点进去后,Stage 页面会出现失败记录,Task 列表中也能看到具体任务执行失败。这种主动"搞破坏"的练法,能让你在真实任务报错时不至于手忙脚乱,因为你已经知道该去哪个页面找信息。
7.5 复盘路径
跑完这一系列操作后,你可以试着不看页面,自己复述一遍观察路径:从 Jobs 列表现异常,到 Stages 看批次划分,再到 Task 列表看单任务指标,最后结合 SQL 页面看执行计划。只要这条链路能顺畅走通,Spark Web UI 的基本功就算扎实了。
日志永远是用来证明原因的,Web UI 则是用来缩小范围、定位方向的。学会先把范围圈定,再决定去哪一层查细节,你就不会在堆栈输出里迷路了。