Zeppelin跟SparkR使用spark 1.5+的分析平台建置

用Ambari跟Zeppelin來玩Apache Spark這篇文章的時候,我想要用Zeppelin跟Spark去搭建一個給資料科學家用的分析平台

簡單來說,我希望使用R跟Python的資料科學家們可以透過Zeppelin去使用Spark的運算資源跟Hadoop的儲存資源
這樣的好處是對於資料科學家來說接觸資料的方法變容易了,在以前我合作過的資料科學家他們要分析資料的時候
必須登入伺服器之後將資料下載回本地端,在用RStudio還是iPython等做分析
並且如果使用本機資源的話就常常面臨本地資源不足的問題
而如果想要減輕這個負擔,就必須使用RStudio-Server等方案

而最近在研究如何用一個平台讓使用不同程式語言的資料科學家可以透過一個平台,

  1. 做跨語言(Python/R/Scala)的分析
  2. 使用分散式運算(Spark)的資源而不是本地資源
  3. 能夠易於接觸數據所在環境

而後研究了兩個免費的方案

  1. 以iPython為基礎建起來的Jupyter
  2. Apache Zeppelin

Jupyter跟zeppelin的比較可以參考:
https://www.linkedin.com/pulse/comprehensive-comparison-jupyter-vs-zeppelin-hoc-q-phan-mba-

Jupyter的優點是支援非常多語言,且以iPython為基底讓使用iPython的人可以無痛接軌,且支援多人協同運作
但是Jupyter跟R尤其是SparkR的目前使用上支援度沒那麼好

而Zeppelin有出一個Zeppelin-wth-R的版本,他跟R/SparkR互相支援的非常好,ggplot2等繪圖函式都可以無痛使用
但是Zeppelin現階段版本對於multi-users並不支援,有個NFLabs這家公司似乎使用了zeppelin+docker的方式去支援multi-users,可以參考:ZeppelinHub

但是我這邊user沒有這麼多,我這裡的資料科學家說起來也才20多個人
後來決定直接去修改Zeppelin的zeppelin-daemon.sh這支啟動scipt去做到支援同一機器上開multiple zeppelin instances,透過啟動sciprt的修改做到了一定程度的環境隔離又可以支援多zeppelin給多user
對我這邊現況來說也足夠了

最後是完成了 Zeppelin(R/Python/Scala) + Spark 1.6 + Yarn Hadoop這樣一個分析平台
使用者透過Zeppelin去存取Yarn Hadoop上的資料(HDFS/Hive)
再透過Spark去對這些資列做運算分析,同時可以做到R/Scala/Python的跨語言協作

上面都是閒聊,主要是想記錄Zeppelin使用SparkR去連接Spark的時候遇到的一些問題
首先Zeppelin目前版本(0.5.6)是不支援R/SparkR的
且稍早的版本(0.5.5)只支援到Spark 1.5.1,若使用了超出版本的spark會出現下面的錯誤

ava.lang.IllegalArgumentException
    at org.apache.zeppelin.spark.SparkVersion.fromVersionString(SparkVersion.java:60)
    at org.apache.zeppelin.spark.SparkInterpreter.open(SparkInterpreter.java:477)
    at org.apache.zeppelin.interpreter.ClassloaderInterpreter.open(ClassloaderInterpreter.java:74)
    at org.apache.zeppelin.interpreter.LazyOpenInterpreter.open(LazyOpenInterpreter.java:68)
    at org.apache.zeppelin.interpreter.LazyOpenInterpreter.interpret(LazyOpenInterpreter.java:92)
    at org.apache.zeppelin.interpreter.remote.RemoteInterpreterServer$InterpretJob.jobRun(RemoteInterpreterServer.java:292)
    at org.apache.zeppelin.scheduler.Job.run(Job.java:170)
    at org.apache.zeppelin.scheduler.FIFOScheduler$1.run(FIFOScheduler.java:118)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)

雖然有些客制版(0.6.0-snapshot)修改了版本控制繞過了檢查
但真要使用最新spark還使得搭配Zeppelin 0.5.6版本的Zeppelin-with-R

這邊遇到的最大問題是SparkR + Zeppelin這個組合
在一開始的實驗,使用了Zeppelin/SparkR + Spark 1.4.1
使用local mode的時候跑很順的,切換到yarn-client mode就開始出問題了

zeppelin執行pyspark的時候必須好好設定spark.executorEnv.PYTHONPAT這個屬性
參考:https://mail-archives.apache.org/mod_mbox/incubator-zeppelin-users/201512.mbox/%3CCAG7inXrGP0rxyMG5-28nFTSJ6+kMhH=VrvKwUBLPmf1z8SbCCAmail.gmail.com%3E@

最後是我卡最久的地方Zeppelin + SparkR
第一個問題是R必須安裝在每一台的Yarn Node的上
因為執行SparkR的時候會需要用到 RScript去執行他sparkR打包的腳本

再來SparkR在Saprk 1.4.1是不支援Yarn cluster mode的,SparkR會跟你說在Yarn Node上找不到SparkR
https://issues.apache.org/jira/browse/SPARK-6797
這個問題得升到Spark 1.5之後才能解決

但是Zeppelin + Spark 1.4.1的組合之下
其實只要好好設定SPARK_HOME,就能讓Yarn Node透過SPARK_HOME去找到SparkR的位置
也就是夠過依賴Yarn的Spark-Client去執行SaprkR
但這樣基本上就失去了Yarn設計上的好處,使用Yarn就是為了不要讓Client去依賴特定cluster跟lib做到資源管理
現在sparkR卻得依賴cluster上的spark-client,這樣太不聰明了

後來乖乖升級到Spark 1.5.2/1.6.0
SparkR直接跳出了java.net.SocketTimeoutException: Accept timed out的錯誤

java.net.SocketTimeoutException: Accept timed out
        at java.net.PlainSocketImpl.socketAccept(Native Method)
        at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)

參考:https://issues.apache.org/jira/browse/SPARK-12239

正確來說這不是sparkR的錯誤,這是Zeppelin+SparkR的錯誤,而使用RStudio + SparkR也會有一樣的錯誤
單純使用sparkR是不會有問題的,後來追蹤了一下yarn的log看到了下面的訊息

.../sparkr/SparkR/worker/daemon.R': No such file or directory

原來在Saprk 1.4時代因為會透過SPARK_HOME去找SparkR的相關Lib
但是到了Saprk 1.5+以後的時候就不再使用SPARK_HOME去找SparkR的相關Lib了
取而代之的是會將SparkR的相關Lib打包成sparkr.zip之後上傳到Yarn Node上去執行
如果直接使用SparkR是不會有問題的,但是使用Zeppelin+SparkR或是RStudio + SparkR
sparkr.zip是不會被上傳的,所以出現了上面的錯誤訊息

解決方法:
在Zeppelin所在機器所安裝的Spark-client裡面,預設加上spark.yarn.dist.archives屬性去強迫上傳sparkr.zip
我的SPARK_HOME是 /home/zeppelin/spark

因此修改/home/zeppelin/spark/conf/spark-defaults.conf

vim /home/zeppelin/spark/conf/spark-defaults.conf

...

#補上spark.yarn.dist.archives去強迫上傳
spark.yarn.dist.archives=/home/zeppelin/spark/R/lib/sparkr.zip#sparkr 

附帶一提,sparkr.zip預設是沒這個檔案的,當執行sparkR之後才會產生
透過用#可以決定zip檔上傳到Yarn Node之後解壓的folder name
上面這個例子sparkr.zip上傳到Yarn node之後會被解壓縮放到sparkr這個目錄裡面
這也是SparkR執行時會存取的目錄

如此一來就能讓zeppelin去使用sparkR 1.5以後的版本了

comments powered by Disqus