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