Scala内核Spark阻塞排查

背景

在Jupyter Scala内核(Almond Scala内核),当运行Spark SQL时会偶发的卡主(Jupyter lab还显示running状态,但到Spark ui中查看已经运行结束了)

由于排查内核问题,成本较高,所以想先简单调研下其他的Jupyter Scala内核实现

其他内核实现简单调研

目前开源的Jupyter Scala内核:参考链接arrow-up-right

Almond (当前我们所选方案)

GitHubarrow-up-right 官方文档arrow-up-right

启动内核后可以根据自己需要随意配置Spark并启动,输出数据比较美观,文档丰富,社区较为活跃

IScala

GitHubarrow-up-right

依赖于IPython,Scala 2.10.2,没有使用Spark相关案例,项目不活跃,最近一次提交已经是七年前

Toree

GitHubarrow-up-right

安装内核的时候就要指定好Spark配置,启动内核就相当于执行spark-submit,用户使用不友好不方便,输出数据展示不好看

安装

总结

Toree和IScala无法灵活配置Spark,输出样式不美观,社区活跃度不高,所以只能继续使用Almond,开始尝试修复Almond当前bug

尝试排查问题

使用 papermillarrow-up-right 执行打debug日志

命令

发现内核卡在某个stage,更新task非常慢,偶尔超时直接内核挂掉,有时一直卡住,因为使用的是papermill,所以直接排除了Jupyter lab端导致的问题

后来发现当一个stage中task过多的时候就基本会稳定复现这个问题,比如下面这段代码

代码

直接本地调试(进入到调试状态鬼知道经历了什么!!)打日志发现,是发送的消息超过限制了

scala-kernel-debug

由于jupyter client和内核层消息框架用的是zeromq,所以简单了解了一下zeromq的消息机制,由于使用的pub/sub模式,直接找到对应的 介绍文档arrow-up-right

zeromq-pub/sub

所以当zero sub端接收消息超出限制(jupyter server端配置1000msgs/sec),就会丢弃后面的消息,所以很明显问题就是出在了丢弃消息上,也就是执行状态的消息被丢失了,解决这个问题有几种思路

  1. 调大jupyter server端的1000msgs/sec配置

  2. 降低消息输出速率

  3. 1 Jupyter官方设置这个默认值肯定有他的深意,不想随意调整默认值

  4. 2 找到产出消息的源头,我们是否可以降低这个消息的频率,避免最终执行状态的消息被丢弃

到Almond代码中找到对应的Spark监听类

ProgressSparkListener.scala

所以Almond-Spark进度条当一个stage中的task开始或者结束都会执行elem.update(),调用publish.html("spark progress info..."),更新当前的进度信息,并发送消息到Jupyter Client,在回头看我们之前执行的代码

这个会产生一个有5000个task的stage,也就是说会发送一万条的消息,当spark执行task比较快的时候,比如说每秒有1000个以上的任务有启停动作,就会导致jupyter丢弃相关的消息,导致最终执行状态的消息也有可能被丢弃,所以Jupyter Lab显示一直running状态

对方案2(降低产出消息速率)进行两种尝试:

  1. 直接不发送进度信息,直接注释掉

  2. 降低发送进度信息速率,定时更新进度,每秒更新一次

再次实验,两种方案都可以完美解决,Git地址arrow-up-right

附:编译环境准备

coursier文档arrow-up-right

Almond文档arrow-up-right

http://dblab.xmu.edu.cn/blog/maven-network-problem/arrow-up-right

图1

sbt-build-error

依赖冲突,必须exclude一下依赖,再添加合适的依赖版本

dependencies-conflict

exclude依赖

exclude-dependencies

添加合适版本的依赖

add-dependencies

最后更新于