境搭建實戰(zhàn))
1. 為什么這套組合在2020年之后依然值得折騰先把結論擺在前面Intellij idea 2020.1社區(qū)版 Maven 3.6.3 Scala 2.11.8 Spark這套組合放到今天看確實有點考古的味道但它依然是很多人入門大數據開發(fā)時繞不開的一條路。原因很直接——大量存量項目、教學資料、企業(yè)內部的老系統(tǒng)仍然跑在Scala 2.11和Spark 2.x這條線上。你如果一上來就裝最新版的Scala 2.13配Spark 3.x遇到老項目直接編譯不過回頭還得降版本重來白白浪費一整天。我自己第一次搭這套環(huán)境的時候踩的坑能寫滿兩頁紙。最典型的一個IDEA社區(qū)版新建項目時選了Scala SDK結果Maven編譯時報object apache is not a member of package org查了半天才發(fā)現是Scala版本和Spark依賴的二進制版本對不上。這種問題在教程里往往一筆帶過但實際卡住新手的就是這些細節(jié)。所以這篇內容我打算按真實搭建順序來講不是那種理想化的步驟羅列而是把每一步為什么這么做、哪里容易翻車、翻車了怎么救都攤開說清楚。適合的人群很明確剛接觸Spark、需要在Windows本地跑通第一個Scala項目的開發(fā)者或者手頭有老項目要維護、需要把環(huán)境對齊到Scala 2.11這條線的同學。整套流程走下來順利的話兩三個小時能跑通第一個WordCount不順利的話可能要在版本兼容上耗掉半天這篇就是幫你把后面那半天省下來。需要提前說明的是Windows平臺搭這套環(huán)境最大的敵人不是技術難度而是路徑、編碼、版本這三件事。下面每個章節(jié)我都會圍繞這三條主線展開。2. 動手之前先把四個組件的版本關系理清楚很多人搭環(huán)境失敗根子不在操作而在于沒搞明白這幾個組件之間到底誰依賴誰。我見過太多人把JDK、Scala、Spark、Maven當成四個獨立的東西分別裝最新版結果湊一起就炸。所以這一章先把關系講透后面操作才不會盲目。2.1 JDK、Scala、Spark三者的版本咬合關系先記住一條鐵律Spark的每個大版本都綁定了特定的Scala二進制版本。Spark 2.4.x默認編譯用的是Scala 2.11Spark 3.0之后才轉向Scala 2.12。你項目里寫的Scala代碼最終要被編譯成字節(jié)碼而這個字節(jié)碼必須和Spark運行時依賴的Scala版本二進制兼容。版本對不上輕則編譯警告重則運行時拋NoSuchMethodError而且這種錯誤往往在運行到某個特定算子時才爆出來極難排查。JDK這邊Scala 2.11.8對JDK 8的支持是最穩(wěn)的。JDK 11雖然也能跑但Scala 2.11的編譯器在JDK 11下偶爾會有反射相關的警告而且Spark 2.x對JDK 11的支持并不完整。所以我的建議很明確JDK鎖死8別貪新。這不是保守是省事。組件推薦版本為什么是這個版本JDK1.88u系列Scala 2.11.8與Spark 2.x在JDK 8下兼容性最好Scala2.11.8與Spark 2.4.x的二進制版本嚴格對應Maven3.6.3穩(wěn)定且與IDEA 2020.1的集成成熟Spark2.4.x與Scala 2.11.8配套的最后一個穩(wěn)定大版本2.2 為什么Maven版本也不能隨便選Maven 3.6.3這個版本不是隨便定的。IDEA 2020.1內置的Maven集成對3.6.x系列支持最順3.8之后Maven改了一些默認行為比如默認阻止HTTP倉庫在老項目里反而容易出問題。而且3.6.3的依賴解析速度和對settings.xml鏡像配置的處理都比較成熟。另一個容易被忽略的點Maven的本地倉庫路徑。默認在C:\Users\你的用戶名\.m2\repository如果你的C盤空間緊張或者用戶名帶中文強烈建議在settings.xml里改到一個純英文、無空格的路徑。中文路徑在Windows下引發(fā)的詭異問題我后面會專門講。2.3 環(huán)境變量配置里最容易寫錯的兩個地方環(huán)境變量這塊網上教程千篇一律但有兩個細節(jié)幾乎沒人強調第一JAVA_HOME指向的路徑不要帶\bin。很多人復制路徑時順手把bin也帶上了結果Maven和Scala都找不到JDK。正確寫法是JAVA_HOME C:\Java\jdk1.8.0_xxx然后Path里加%JAVA_HOME%\bin。第二SCALA_HOME和MAVEN_HOME同理都指向解壓后的根目錄不帶bin。配置完之后一定要新開一個命令行窗口驗證因為環(huán)境變量的更新不會影響已經打開的窗口。這個坑我踩過不止一次明明配好了卻一直報不是內部或外部命令就是因為用的是舊窗口。驗證命令很簡單java -version scala -version mvn -version三條都能正常輸出版本號且java -version顯示的是1.8這一步才算過關。如果scala -version報錯八成是Path沒配對如果mvn -version里顯示的Java版本不對說明JAVA_HOME有問題。3. 從零到跑通第一個Scala項目的完整操作鏈路這一章是重頭戲我按實際操作順序一步步來每一步都標注了為什么這么做和這里容易出什么問題。你照著走基本不會偏。3.1 IDEA社區(qū)版的一個先天限制及應對思路先說個扎心的事實Intellij idea社區(qū)版沒有內置的Scala插件支持而旗艦版才有完整的Scala和Spark支持。但社區(qū)版不是不能用只是需要手動裝Scala插件。這是很多人卡在第一步的原因——新建項目時找不到Scala選項以為社區(qū)版不支持其實只是插件沒裝。操作路徑File - Settings - Plugins在Marketplace里搜Scala安裝后重啟IDEA。注意2020.1這個版本對應的Scala插件版本也要匹配如果裝最新版插件報兼容性錯誤就去插件頁面找歷史版本選一個標注支持2020.1的。裝完插件后新建項目時就能看到Scala選項了。但這里有個選擇是建Maven項目再手動加Scala支持還是直接建Scala項目我的建議是建Maven項目因為Spark項目本質上依賴管理靠Maven直接建Scala項目反而要額外配Maven繞遠了。3.2 新建Maven項目時的坐標填寫與骨架選擇新建項目選MavenJDK選你裝好的1.8。到了填GroupId和ArtifactId這一步隨便填個有意義的名字就行比如com.demo和spark-demo。關鍵是不要勾選Create from archetype也就是不要用模板骨架。原因很簡單Scala項目的目錄結構和Java不一樣用Java的archetype生成出來的目錄還得手動改不如從空項目開始自己建目錄。項目建好后默認目錄是src/main/java我們要手動改成Scala的規(guī)范結構src/main/scala src/main/resources src/test/scala在IDEA里右鍵src/main新建Directory輸入scala即可。然后關鍵一步把這個scala目錄標記為Sources Root。右鍵目錄 -Mark Directory as-Sources Root目錄會變藍。這一步不做IDEA不會把里面的Scala文件當源碼編譯后面會報找不到類。3.3 pom.xml里必須寫對的幾段配置pom.xml是整個項目的靈魂寫錯了后面全是坑。我直接給一份經過驗證的配置然后逐段解釋。properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target scala.version2.11.8/scala.version spark.version2.4.5/spark.version encodingUTF-8/encoding /propertiesscala.version和spark.version這兩個屬性一定要顯式聲明后面依賴里用${}引用改版本時只改一處避免遺漏。依賴部分dependencies dependency groupIdorg.scala-lang/groupId artifactIdscala-library/artifactId version${scala.version}/version /dependency dependency groupIdorg.apache.spark/groupId artifactIdspark-core_2.11/artifactId version${spark.version}/version /dependency dependency groupIdorg.apache.spark/groupId artifactIdspark-sql_2.11/artifactId version${spark.version}/version /dependency /dependencies注意spark-core_2.11這個后綴_2.11就是Scala的二進制版本標識。如果你寫成spark-core_2.12而你的Scala是2.11.8編譯能過但運行必炸。這個后綴是Maven坐標的一部分不是可有可無的裝飾。構建插件部分需要scala-maven-pluginbuild plugins plugin groupIdnet.alchim31.maven/groupId artifactIdscala-maven-plugin/artifactId version3.4.6/version executions execution goals goalcompile/goal goaltestCompile/goal /goals /execution /executions /plugin /plugins /build這個插件的作用是讓Maven能編譯Scala代碼。沒有它Maven只認Java你的.scala文件會被直接忽略編譯時一個類都找不到。3.4 第一個Scala文件的編寫與運行驗證在src/main/scala下新建一個Scala Object寫個最簡單的WordCountimport org.apache.spark.{SparkConf, SparkContext} object WordCount { def main(args: Array[String]): Unit { val conf new SparkConf().setAppName(WordCount).setMaster(local[*]) val sc new SparkContext(conf) val data sc.parallelize(Seq(hello spark, hello scala, spark is fast)) val result data.flatMap(_.split( )).map((_, 1)).reduceByKey(_ _) result.collect().foreach(println) sc.stop() } }setMaster(local[*])表示用本地所有CPU核心跑這是本地開發(fā)的標準寫法。跑起來如果能看到每個單詞的計數輸出恭喜你環(huán)境通了。但這里有個Windows特有的坑運行時會報Could not locate executable null\bin\winutils.exe。這個錯誤不影響結果但看著煩。原因是Spark在Windows下需要Hadoop的winutils工具而Windows沒有。解決辦法是下載對應Hadoop版本的winutils放到一個目錄然后配置HADOOP_HOME環(huán)境變量指向它。或者更簡單在代碼里加一行System.setProperty(hadoop.home.dir, 你的路徑)。這個坑幾乎每個Windows用戶都會遇到提前知道能省不少搜索時間。4. 那些讓新手抓狂的報錯與排查思路環(huán)境搭建最耗時的從來不是順利的部分而是報錯。這一章我把幾個高頻報錯的完整排查鏈路寫出來你可以對照著復現排查思路而不是直接抄答案。4.1 依賴下載卡住或失敗的幾種成因Maven下載依賴慢是新手遇到的第一個勸退點。默認從中央倉庫拉國內網絡環(huán)境下經常卡在某個jar上不動。解決辦法是配置國內鏡像。在settings.xml的mirrors里加一段鏡像配置指向國內的Maven鏡像源。配置完之后記得在IDEA里指定這個settings.xmlSettings - Build Tools - Maven把User settings file指向你改過的那個文件。但鏡像也不是萬能的。有時候某個依賴在鏡像里沒有會報Could not find artifact。這時候先別急著換鏡像先確認你的坐標寫對了沒有——groupId、artifactId、version三個字段任何一個拼錯都會導致找不到。我見過有人把spark-core寫成sparkcore找了半天。還有一種情況是依賴下載到一半斷了本地倉庫里留了個.lastUpdated文件導致Maven認為這個依賴已經嘗試過、不再重試。解決辦法是刪掉對應目錄下的.lastUpdated文件或者直接刪掉整個依賴目錄重新拉。4.2 Scala版本不匹配引發(fā)的編譯錯誤長什么樣版本不匹配的報錯有個典型特征編譯期可能不報錯運行期才炸。比如你Scala用2.12Spark依賴用_2.11編譯時scala-maven-plugin可能只是警告但運行時一調用Spark的類就拋NoSuchMethodError或ClassNotFoundException。排查這類問題的思路是先看報錯里提到的類屬于哪個包然后去Maven依賴樹里查這個包是哪個版本。IDEA右側的Maven面板有個Dependencies視圖能看到完整的依賴樹。如果發(fā)現同一個包有多個版本那就是版本沖突需要用exclusions排除掉多余的。更隱蔽的一種Scala SDK在IDEA里的配置和pom.xml里的版本不一致。IDEA的Project Structure - Global Libraries里可能掛著一個Scala 2.12的SDK而pom里寫的是2.11.8。這種情況下IDEA的代碼提示按2.12走Maven編譯按2.11走兩邊打架。解決辦法是統(tǒng)一要么在IDEA里把Scala SDK換成2.11.8要么干脆不掛全局SDK完全靠Maven管理。4.3 中文亂碼與路徑空格的隱藏陷阱Windows下有兩個隱形殺手中文路徑和路徑空格。中文路徑的問題在于某些工具鏈在處理路徑時用的編碼不一致導致讀不到文件。比如你的項目放在D:\我的項目\spark-demoMaven或Scala編譯器可能就找不到源碼。解決辦法很簡單所有和開發(fā)相關的路徑全部用純英文、無空格。項目路徑、Maven倉庫路徑、JDK安裝路徑一個都別例外。路徑空格的問題類似。C:\Program Files\Java這種帶空格的路徑在某些腳本里會被截斷。所以JDK、Scala、Maven這些工具我建議統(tǒng)一裝在C:\dev\這類簡短無空格的目錄下。中文亂碼則主要出現在控制臺輸出。IDEA的控制臺默認編碼可能和系統(tǒng)不一致導致Scala程序里的中文輸出變成亂碼。解決辦法是在IDEA的Settings - Editor - File Encodings里把Global Encoding、Project Encoding、Default encoding for properties files全部設為UTF-8并勾選Transparent native-to-ascii conversion。同時在pom.xml里加上project.build.sourceEncodingUTF-8/project.build.sourceEncoding。4.4 一個完整的排查案例復盤說個我實際遇到的案例。某次幫人看環(huán)境現象是代碼能編譯但一運行就報java.lang.NoClassDefFoundError: scala/Product。這個報錯的意思是運行時找不到Scala的基礎類。排查鏈路是這樣的第一步確認pom里有沒有scala-library依賴——有。第二步看依賴樹里scala-library的版本——發(fā)現是2.12但pom里明明寫的2.11.8。第三步追查為什么變成2.12——原來是spark-sql的某個傳遞依賴帶進來了2.12的scala-library覆蓋了顯式聲明的版本。解決辦法是在spark-sql依賴里加exclusions把傳遞進來的scala-library排除掉強制用我們聲明的2.11.8。這個案例說明依賴沖突不一定來自你直接寫的依賴更多時候來自傳遞依賴。學會看依賴樹是排查這類問題的基本功。5. 讓開發(fā)體驗更順手的幾個配置調整環(huán)境跑通只是及格線真正提升效率的是那些順手的配置。這一章分享幾個我用了很久、確實能省時間的調整。5.1 IDEA里Scala編譯器的性能調優(yōu)Scala編譯慢是出了名的尤其是項目大了之后。IDEA里可以調整編譯器的堆內存。在Settings - Build, Execution, Deployment - Compiler里把Build process heap size從默認的700調到2048甚至更高。這個調整對編譯速度的提升是肉眼可見的尤其是依賴多的時候。另外Scala插件的Compile server選項建議開啟。它會讓編譯在一個獨立的進程里跑避免每次編譯都重新加載第二次之后的編譯速度會快很多。開啟路徑在Settings - Languages Frameworks - Scala里。5.2 Maven的離線模式與依賴緩存策略依賴都下載完之后日常開發(fā)其實不需要每次都聯網檢查更新。IDEA的Maven面板里有個Toggle Offline Mode按鈕開啟后Maven只用本地倉庫不再聯網。這在網絡不穩(wěn)定的時候特別有用能避免因為網絡問題導致的構建失敗。但要注意離線模式下如果本地缺某個依賴會直接報錯而不是去下載。所以開啟離線模式的前提是所有依賴都已經完整下載到本地。我一般是在項目第一次完整構建成功后再開離線模式。5.3 日志輸出太吵怎么辦Spark默認會打一大堆INFO級別的日志控制臺刷得飛快真正有用的信息反而被淹沒。解決辦法是在src/main/resources下放一個log4j.properties文件把日志級別調到WARNlog4j.rootCategoryWARN, console log4j.appender.consoleorg.apache.log4j.ConsoleAppender log4j.appender.console.targetSystem.err log4j.appender.console.layoutorg.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n這樣控制臺就清爽多了只輸出警告和錯誤以及你自己println的內容。這個配置雖小但對調試體驗的提升很大。5.4 打包提交時的注意事項本地跑通之后下一步往往是打包成jar提交到集群。這里有個關鍵點Spark相關的依賴不要打進jar包。因為集群上已經有Spark環(huán)境了你打進去反而會造成沖突。在pom里把spark-core、spark-sql這些依賴的scope設為provideddependency groupIdorg.apache.spark/groupId artifactIdspark-core_2.11/artifactId version${spark.version}/version scopeprovided/scope /dependencyprovided的意思是編譯時需要、打包時不帶。這樣打出來的jar體積小提交到集群也不會和集群自帶的Spark沖突。這個細節(jié)很多人第一次提交作業(yè)時都會忽略導致jar包幾百兆或者運行時類沖突。6. 關于這套環(huán)境的一些個人體會搭這套環(huán)境的過程本質上是在和版本兼容這四個字較勁。我自己的經驗是把版本關系當成一張網來看而不是一條線。JDK、Scala、Spark、Maven任意兩個之間都有兼容性約束牽一發(fā)而動全身。所以每次要升級其中任何一個都要先確認其他三個是否還兼容。另一個體會是Windows平臺做大數據開發(fā)確實比Linux麻煩主要就麻煩在路徑、編碼、缺少原生工具這幾件事上。但本地開發(fā)用Windows、提交運行用Linux這個組合其實挺常見把Windows這邊的坑趟平了日常開發(fā)效率并不低。最后分享一個小習慣每搭好一套環(huán)境就把pom.xml和settings.xml備份一份。下次換機器或者重裝系統(tǒng)直接拿過來用能省掉大量重復勞動。環(huán)境搭建這種事做一次就夠了沒必要每次都從零開始。