本文主要參考了如下兩篇博文:
Windows下IntelliJ IDEA中調試Spark Standalone
怎么解決java.lang.NoClassDefFoundError錯誤
一、通過IDEA連接遠程集群運行應用就是個大坑。。
寫在2017年10月3日凌晨0點57分:
最初這篇博文的名字是“IDEA開發(fā)Spark應用并提交遠程Spark 2.1.0 standalone集群運行”,當時以不能SSH免密碼登錄遠程主機為由,直接連接本地集群運行應用,后來又嘗試了一下通過IDEA連接部署在實驗室服務器上的Spark集群來運行應用,然后就發(fā)現(xiàn)這里實在是坑太多。如果部署模式是“client”,則會在本地主機起一個driver進程,而從本地主機序列化分發(fā)jar包到集群各從節(jié)點要求本地主機能夠SSH免密碼登錄各從節(jié)點?。。ó斎贿\行時只需要網絡互通即可)如果部署模式是“cluster”,然后IDEA運行項里配置“VM options”為-Dspark.submit.deployMode=cluster,結果很不幸,driver依然在本機運行,然后執(zhí)行器和驅動器無法互相通信。
通過spark-submit腳本,設置部署模式為cluster,這樣driver會在某一個worker上啟動,然而我們的jar包依然在本機,還是需要序列化分發(fā)到各節(jié)點,然而這樣就需要本地主機可以免密碼SSH登錄所有集群節(jié)點!!
當然本地開發(fā)測試還是可以通過IDEA連接本地集群運行應用,畢竟借助IDEA,我們只需點一下“運行”即可,還是方便很多。所以啊,本文決定把題目和內容改一下,把遠程改成本地!
二、前言
雖然之前也有許多博文講如何基于IDEA來開發(fā)運行Spark應用,但是他們大多數都以本地模式運行(例如:conf.setMaster("local[*]")),而本地模式只是在本機以多線程方式運行Spark應用,這與standalone模式運行有很大差別。
此外,雖然也有一些博文介紹了如何使用IDEA將開發(fā)的Spark應用提交到本地Spark集群執(zhí)行,但是這些博文涉及的Spark版本都比較老,較新版本的Spark(例如2.1.0及以后)取消了lib目錄,也就是說新版本沒有assembly jar包(例如:spark-assembly-1.5.2-hadoop2.6.0.jar),取而代之的是jars目錄,這就使得在新版本里,依賴包的添加發(fā)生了一些變化。
綜上所述,還是很有必要重新寫一篇關于IDEA提交Spark應用到本地集群的博文,這篇博文關注點在于如何讓IDEA連接Spark 2.1.0 standalone集群。
這篇博文首先介紹了幾種Spark應用開發(fā)和運行的方式,然后介紹了如何通過IDEA連接Spark standalone集群來開發(fā)運行Spark應用,最后對可能發(fā)生的錯誤給出了解決方法。
注意:由于大部分情況下,我們不能隨意訪問遠程主機,例如不能直接ssh免密碼登錄遠程主機,所以本文只是將IDEA連接到了本機的Spark 2.1.0 standalone集群,也就是將Spark應用提交到本地集群運行。
三、開發(fā)環(huán)境
操作系統(tǒng):CentOS 7
Spark:2.1.0
IDEA:社區(qū)版2016.3.1
Scala:2.11.8
JDK:1.8.0_111
四、Spark應用開發(fā)運行綜述
1、開發(fā)
用Scala開發(fā)Spark應用,官方推薦使用SBT項目構建工具,當然由于Scala基于JVM,所以使用Maven也是可以的。有時我們想在項目中混合使用Java和Scala,這時Maven就成了最佳選擇,當然這個時候就需要在pom.xml中配置Java和Scala的編譯工具。
可以純粹使用項目構建工具SBT或者Maven來完成Spark開發(fā)(不借助IDE),包括按照模板創(chuàng)建項目、編譯、構建項目、打包和發(fā)布等等。當然也可以借助IDEA集成的SBT和Maven插件來高效完成上述工作(IDE+項目構建工具)。
當然這篇博文只使用IDEA來完成上述工作,沒有使用項目構建工具。
2、運行
開發(fā)完項目后當然就要運行我們的Spark應用了,運行也有好幾種方式。
(1)第一種方式主要用于開發(fā)測試完畢,準備線上運行。借助IDEA或者使用項目構建工具將Spark應用打成Jar包,接著上傳到服務器(windows使用FileZilla,Linux使用scp命令),然后通過spark-submit腳本連接集群(通過master的URL可以連接到不同集群)并運行Spark應用。
(2)第二種方式是本地測試。打包后直接提交本地Spark standalone集群(偽分布式,本機運行master和worker)。
(3)開發(fā)時會頻繁地運行Spark應用,前面兩種方式就顯得比較麻煩(需要打包、上傳、提交),第三種方式用于開發(fā)過程中。通過IDEA連接Spark standalone集群,只需點擊IDEA的“運行”按鈕即可運行Spark應用(不需要手動打包,不需要上傳到服務器,不需要手動運行spark-submit腳本)。當然也可以以local模式運行(可以在應用程序中使用conf.setMaster("local[*]")或者在IDEA運行項中配置“VM options”為-Dspark.master=local[*],然后點擊IDEA的“運行”按鈕),此時在本地以多線程方式運行Spark應用,local模式不需要開啟Spark集群。
下面介紹如何通過IDEA連接Spark standalone集群,來快速開發(fā)調試Spark應用。
五、通過IDEA將開發(fā)的Spark應用提交到本地Spark 2.1.0 standalone集群運行
1、前提要求(版本信息見“開發(fā)環(huán)境”)
(1)需要有一個Spark standalone集群,不需要hdfs,能啟動運行即可。
(2)IntelliJ IDEA、Scala插件、Java JDK、Scala SDK都已安裝配置完畢。
2、創(chuàng)建項目
新建一個Scala項目。啟動IntelliJ IDEA,選擇New Project,然后選擇Scala,點擊下一步,接著配置項目名稱、SDK版本和項目位置等。注意:IDEA中項目的概念類似于Eclipse中的工作區(qū)(workspace),而IDEA中模塊(module)的概念類似于Eclipse中的項目。
3、添加依賴的Jar包
如前文所述,較新版本的Spark(例如2.1.0及以后)取消了lib目錄,取而代之的是jars目錄,所以這里依賴包的添加相比于舊版本發(fā)生了一些變化。
首先按如下步驟(圖3、圖4和圖5)為我們開發(fā)的Spark應用添加依賴,把Spark 2.1.0中的jars目錄整個添加進來(這樣jars目錄里所有的jar包都會被添加到classpath中),并調整它的scope為“編譯”(這樣我們后面打的包會包括jars目錄里所有的jar包)。
看到這里,讀者可能會心生疑惑,我們開發(fā)的應用事實上用不著這么多的jar包,例如關于機器學習或者流計算的jar包我們肯定用不上,另外,這些jar包Spark集群中都有,為什么還要把scope設為“編譯”呢?
如果是手動打包然后通過spark-submit腳本運行應用的話,確實只需添加幾個jar包即可,而且scope還應該設為“provided”,這樣可以減少最終生成jar包的大小。然而通過IDEA提交Spark應用到本地集群,還就必須按前述步驟做,否則會報java.lang.NoClassDefFoundError的錯誤(不把整個jars目錄添加到classpath中會報錯,不把它的scope設為“編譯”也會報錯)。
java.lang.ClassNotFoundException和java.lang.NoClassDefFoundError這兩種錯誤看起來有點像,但它們是完全不同的。NoClassDefFoundError是指JVM運行時無法在classpath中找到對應的類進行加載;而ClassNotFoundException是指編譯的時候classpath中找不到對應的類。
4、完成代碼
在src目錄下新建一個名為WordCount的object,這是我們的main class。接著在源文件中輸入Spark的HelloWorld程序——WordCount。因為這里連接的是本地Spark 2.1.0 standalone集群,所以直接從本地讀取文件。
有兩點需要注意:
(1)因為要提交Spark應用到本地集群運行,所以需要配置master的URL,可以在應用程序中使用conf.setMaster("spark://host:port")或者在IDEA運行項中配置“VM options”為-Dspark.master=spark://host:port,“host”就是master所在的主機名,如果沒有在/etc/hosts中建立主機名和IP的映射關系,則直接使用IP。
(2)通過conf.setJars(List("/path/to/xxx.jar")),告訴Spark集群我們要提交的包含作業(yè)代碼的jar包在哪里,記住路徑中千萬別包含中文,不然會出錯。
5、配置打包
即便是通過IDEA提交Spark應用到本地集群執(zhí)行,我們還是需要配置打包。Spark應用之所以能夠分布式運行,正是因為jar包被分發(fā)到了各個worker之中。
我們將應用程序打成可執(zhí)行jar包,并為它設置main class,然后再把“構建項目自動打包”的選項勾上,這樣才能做到點擊“運行”就提交到本地集群運行。
6、配置IDEA運行項
這里結合spark-submit腳本,說明一下IDEA是怎么做到一鍵提交Spark應用到本地集群執(zhí)行。當我們點擊“運行”按鈕,IDEA會自動構建項目,并重新打包;然后根據應用程序中的conf.setJars(List("/path/to/xxx.jar")),可以找到需要提交到集群的jar包;根據conf.setMaster("spark://host:port")或者IDEA運行項中“VM options”的-Dspark.master=spark://host:port連接到Spark standalone集群,這相當于配置spark-submit腳本的--master;然后IDEA運行項中的“Main class”指定了Spark應用的main class,這相當于配置spark-submit腳本的--class;如果還想為Spark應用做一些其他配置,可以通過conf.set("xxx", "XXX")或者“VM options”添加一些其他配置(形如-Dxxx.yyy=zzz);傳給Spark應用main方法的參數在“Program arguments”中輸入。
所以使用IDEA連接集群,通過IDEA將Spark應用提交本地集群執(zhí)行,其實就是IDEA替我們完成了打包和執(zhí)行spark-submit腳本的工作。
7、啟動Spark集群
在點擊“運行”按鈕之前,需要確保目標本地Spark standalone集群已經啟動。
注意:我們開發(fā)時使用的Spark版本要和本地集群一致。
8、運行,觀察IDEA控制臺結果,觀察Spark Web UI。
終于到了最后一步,點擊“運行”按鈕,見證奇跡吧(2333333......)。
可以看到,控制臺成功輸出了詞頻統(tǒng)計。也可以打開Spark Web UI,詳細看一下應用執(zhí)行情況。
六、可能出現(xiàn)的錯誤
1、可能會出現(xiàn)如下錯誤:創(chuàng)建SparkContext時出現(xiàn)異常
java.net.BindException: Cannot assign requested address: Service 'sparkDriver' failed after 16 retries (starting from 0)! Consider explicitly setting the appropriate port for the service 'sparkDriver '(for example spark.ui.port for SparkUI) to an available port or increasing spark.port.maxRetries.
解決方法:在IDEA運行項“VM options”中加入“-Dspark.driver.host=127.0.0.1”和“-Dspark.driver.port=7077” 這兩項, 配置driver進程在本機的7077端口運行。
2、還可能出現(xiàn)這個錯誤:Hadoop權限錯誤
org.apache.hadoop.security.AccessControlException: Permission denied: user=yourUsername, access=WRITE, inode="/user/otherUsername/":otherUsername:supergroup:drwxr-xr-x
這是因為,HDFS中每個用戶都有自己的home目錄(與Linux系統(tǒng)一致),用戶自己的目錄只有該用戶擁有寫權限,遠程連接Spark集群運行應用時,若使用本機用戶嘗試對其他用戶在HDFS中的home目錄進行寫操作,會出現(xiàn)這個問題。
解決辦法:
(1)修改Hadoop 配置文件hdfs-site.xml,設置dfs.permissions.enabled為false即可,這樣不會再有權限限制,具體如圖14所示。當然,開發(fā)測試完了最好還是把這個權限限制機制打開。
(2)使用和遠程集群一致的用戶,或者在同名用戶home目錄下進行寫操作。
轉載請注明如下內容:
文章來自簡書,作者:就是楊宗
原文鏈接:http://www.lxweimin.com/p/b4e4658c459c