Hadoop as a Service(3)Gradle vs. HDInsight x Hadoop
|
在第二篇文章「Hadoop as a Service(2)在 HDInsight Cluster 執行 MapReduce 程式」裡頭,我們一共執行了三次的 MapReduce 程式:
其實,不知道大家有沒有注意到,第 2 次跟第 3 次這兩次,雖然都是執行同一個程式,但是執行的方式不太一樣?
第 2 次的時候,我們透過 注意到這個差異了嗎? MapReduce 程式第 1 次的執行方式一般來說,MapReduce 程式寫好之後,我們總是會先包裝成一個 JAR 檔案,再 Submit 到 Hadoop Cluster,透過 hadoop jar hadoop-mapreduce-examples.jar wordcount
/example/data/gutenberg /example/data/wordcountoutput
<property>
<name>mapreduce.application.classpath</name>
<value>
%HADOOP_CONF_DIR%,
%HADOOP_COMMON_HOME%/share/hadoop/common/*,
%HADOOP_COMMON_HOME%/share/hadoop/common/lib/*,
%HADOOP_HDFS_HOME%/share/hadoop/hdfs/*,
%HADOOP_HDFS_HOME%/share/hadoop/hdfs/lib/*,
%HADOOP_MAPRED_HOME%/share/hadoop/mapreduce/*,
%HADOOP_MAPRED_HOME%/share/hadoop/mapreduce/lib/*,
%HADOOP_YARN_HOME%/share/hadoop/yarn/*,
%HADOOP_YARN_HOME%/share/hadoop/yarn/lib/*
</value>
<description>
CLASSPATH for MR applications. A comma-separated list of CLASSPATH entries
</description>
</property>
二方面應該也會處理一些權限的問題,比方說透過 如果還要再找一些理由的話,那就是我們用來撰寫 MapReduce 程式與執行 MapReduce 程式所使用的機器,通常不會是同一台,所以為了 Submit 上傳檔案方便,包成 JAR 檔案也是一個比較好的作法。 MapReduce 程式第 2 次的執行方式第 2 次執行 MapReduce 程式的時候,我們是透過 Gradle 裡頭 gradlew run 這裡面其實有一個小問題,但是也間接證明了兩件事。 小問題是,我們並沒有提供執行 Hadoop 程式所需的相關 JAR 檔案,對吧?再往前推,如果我們沒有提供相關 JAR 檔案,那應該連編譯都會出問題吧? 沒錯,我們有提供相關 JAR 檔案: dependencies {
compile 'org.apache.hadoop:hadoop-client:2.3.0'
}
根據 Gradle 的說明,因為
小問題知道了,那間接證明的兩件事,又是什麼呢? 建立 HDInsight Cluster 的時候,我選定的 Hadoop 版本是 2.2.0,但是 popcorny 在 第二件事其實是第一件事衍伸出來的,我們的 Client 端,使用的是 Apache 提供的原始 Hadoop 相關 JAR 檔案,但是我們的 Server 環境,使用的是 Hortonworks 移植到 Windows 平台、再經由 Microsoft 整合了https://www.codedata.com.tw/wp-content/uploads/2014/07/ Azure Blob Storage 的重重修改版本,能夠順利執行,其實也證明了 HDInsight 跟 Hadoop 之間的相容性看起來很不錯呦!
MapReduce 程式第 3 次的執行方式第 3 次執行 MapReduce 程式的時候,我們是透過 Gradle 裡頭 gradlew jar
hadoop jar build/libs/hadoop-wordcount-master.jar
/example/data/gutenberg /example/data/wordcountoutput2
其實,故事的一開始不是這樣的。 當時面臨兩個問題:一是如何表示 HDInsight Cluster 上的資料位置,二是如何把這些位置當作參數傳遞給 Gradle? 先解決第二個問題,也就是如何把這些位置當作參數傳遞給 Gradle。 因為我也只是個 Gradle 的門外漢,所以只好上網路上找了一下資料,發現最簡單的方式,就是用 run {
if (project.hasProperty('args')) {
args project.args.split('\\s+')
}
}
Gradle 指令就模仿第 1 次執行 MapReduce 程式的寫法,改成: gradlew run -Pargs="/example/data/gutenberg /example/data/wordcountoutput2" 看到 不過 Hadoop 似乎把這樣的路徑寫法當成 Local Native File System,
因為我們的 Server 端環境,是架在 Azure 上的 HDInsight Cluster,底層是 Azure Blob Storage 封裝而成的 HDFS。如果要存取架在 Azure Blob Storage 上的這個 HDFS 檔案系統裡面的資料,比方說
所以剛剛第一個問題,如何表示 HDInsight Cluster 上的資料位置,答案就很簡單,事實上我們在第 1 次執行 MapReduce 程式的時候就碰過這個問題了。沒關係,看不懂 gradlew run -Pargs="wasb:///example/data/gutenberg wasb:///example/data/wordcountoutput2"
還是不行,那再試一下第三種寫法: gradlew run -Pargs="wasb://[email protected]/example/data/gutenberg wasb://[email protected]/example/data/wordcountoutput2" 仍然不行,因為看不懂
等等,在 HDInsight Cluster 裡頭看不懂 冷靜下來,從「MapReduce 程式第 2 次的執行方式」那一段,我們不是很開心地用 Apache Hadoop Client 在 HDInsight Cluster 裡頭執行嗎?那是不是說,我們的 MapReduce 程式不知道什麼是 既然有了這樣的想法,那就來驗證一下。參考 HDInsight Cluster 剛剛的 /*
dependencies {
compile 'org.apache.hadoop:hadoop-client:2.3.0'
}
*/
project.ext.HADOOP_HOME = "$System.env.HADOOP_HOME".replaceAll('\\\\', '/')
project.ext.HADOOP_CLASSPATH = "C:/apps/dist/azureLogging"
dependencies {
compile fileTree(dir: HADOOP_CLASSPATH, include: '*.jar')
compile fileTree(dir: HADOOP_HOME, include: '*.jar')
compile fileTree(dir: HADOOP_HOME + '/share/hadoop/common', include: '*.jar')
compile fileTree(dir: HADOOP_HOME + '/share/hadoop/common/lib', include: '*.jar')
compile fileTree(dir: HADOOP_HOME + '/share/hadoop/hdfs', include: '*.jar')
compile fileTree(dir: HADOOP_HOME + '/share/hadoop/hdfs/lib', include: '*.jar')
compile fileTree(dir: HADOOP_HOME + '/share/hadoop/mapreduce', include: '*.jar')
compile fileTree(dir: HADOOP_HOME + '/share/hadoop/mapreduce/lib', include: '*.jar')
compile fileTree(dir: HADOOP_HOME + '/share/hadoop/tools', include: '*.jar')
compile fileTree(dir: HADOOP_HOME + '/share/hadoop/tools/lib', include: '*.jar')
compile fileTree(dir: HADOOP_HOME + '/share/hadoop/yarn', include: '*.jar')
compile fileTree(dir: HADOOP_HOME + '/share/hadoop/yarn/lib', include: '*.jar')
}
然後不要急著執行 Gradle,先到目前 HDInsight Cluster 的使用者 Home Directory 底下,以我們的範例就是
要殺就殺的徹底一點,
再從頭執行一次 Gradle: gradlew
這時會下載 Gradle 相關程式。然後再執行: gradlew run -Pargs="wasb:///example/data/gutenberg wasb:///example/data/wordcountoutput2" 這時候如果仔細觀察,會發現 Gradle 並沒有像之前一樣去下載 Apache Hadoop Client 相關 JAR 檔案,那就表示我們的 Dependency 設定生效了。
但是再往下看,雖然這次因為使用 HDInsight 本身的 JAR 檔案,所以終於看懂了 Exception in thread "main" java.lang.IllegalArgumentException: Cannot initialize WASB file system, URI authority not recognized.
at org.apache.hadoop.fs.azurenative.NativeAzureFileSystem.initialize(NativeAzureFileSystem.java:460)
at org.apache.hadoop.fs.FileSystem.createFileSystem(FileSystem.java:2433)
at org.apache.hadoop.fs.FileSystem.access$200(FileSystem.java:88)
at org.apache.hadoop.fs.FileSystem$Cache.getInternal(FileSystem.java:2467)
at org.apache.hadoop.fs.FileSystem$Cache.get(FileSystem.java:2449)
at org.apache.hadoop.fs.FileSystem.get(FileSystem.java:367)
at org.apache.hadoop.fs.Path.getFileSystem(Path.java:287)
at org.apache.hadoop.mapreduce.lib.input.FileInputFormat.addInputPath(FileInputFormat.java:466)
at idv.popcorny.WordCount.main(WordCount.java:38)
完整版的指令呢: gradlew run -Pargs="wasb://[email protected]/example/data/gutenberg wasb://[email protected]/example/data/wordcountoutput2"
一樣不行,但是會給更多的提示訊息: org.apache.hadoop.fs.azure.AzureException: Uploads to to public accounts using anonymous access is prohibited.
at org.apache.hadoop.fs.azurenative.AzureNativeFileSystemStore.storeEmptyFolder(AzureNativeFileSystemStore.java:1394)
at org.apache.hadoop.fs.azurenative.NativeAzureFileSystem.mkdirs(NativeAzureFileSystem.java:1104)
at org.apache.hadoop.fs.FileSystem.mkdirs(FileSystem.java:1933)
at org.apache.hadoop.mapreduce.lib.output.FileOutputCommitter.setupJob(FileOutputCommitter.java:291)
at org.apache.hadoop.mapred.LocalJobRunner$Job.run(LocalJobRunner.java:368)
在「MapReduce 程式第 1 次的執行方式」那一段,我們曾經提到, 不過,這個雷比較像是 Azure Blob Storage 的 HDFS Wrapper 沒有處理好。同樣的 MapReduce 程式,如果 Submit 到 Hortonworks Sandbox 裡頭,透過同樣的 Gradle 指令寫法去執行的話: gradlew run -Pargs="hdfs://sandbox.hortonworks.com:8020/example/data/gutenberg
hdfs://sandbox.hortonworks.com:8020/example/data/wordcountoutput2"
是可以順利執行的。 MapReduce 程式第 4 次的執行方式HDInsight 的 Feature 不是我們瞬間可以解決的,那該怎麼辦呢? 反正跳過 上一篇文章最後的作法,其實就是比較醜的解法:
但是,有沒有辦法透過 Gradle,把這兩個步驟合而為一呢? 有的:自己寫一個 Gradle Task,把這兩件事包在一起。 apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'application'
mainClassName = "idv.popcorny.WordCount"
repositories {
mavenCentral();
}
jar {
manifest {
attributes 'Main-Class': "$mainClassName"
}
}
dependencies {
compile 'org.apache.hadoop:hadoop-client:2.3.0'
}
task hadoop(dependsOn: jar, type: Exec) {
if (System.properties['os.name'].toLowerCase().contains('windows')) {
project.ext.HADOOP_HOME = "$System.env.HADOOP_HOME".replaceAll('\\\\', '/')
project.ext.HADOOP_CMD = HADOOP_HOME + '/bin/hadoop.cmd'
commandLine 'cmd', '/c', HADOOP_CMD, 'jar', "$jar.archivePath"
}
else {
project.ext.HADOOP_HOME = "/usr/lib/hadoop"
project.ext.HADOOP_CMD = HADOOP_HOME + '/bin/hadoop'
commandLine HADOOP_CMD, 'jar', "$jar.archivePath"
}
if (project.hasProperty('args')) {
args project.args.split('\\s+')
args.each { commandLine.push(it) }
}
standardOutput = new ByteArrayOutputStream()
project.ext.output = {
return standardOutput.toString()
}
}
這時候,我們只要執行底下的 Gradle 指令: gradlew hadoop -Pargs="/example/data/gutenberg /example/data/wordcountoutput2" 就可以順利透過
|

Java 學習之路












