Java程式中不通過hadoop jar的方式訪問hdfs
阿新 • • 發佈:2019-02-01
一般情況下,我們使用Java訪問hadoop distributed file system(hdfs)使用hadoop的相應api,新增以下的pom.xml依賴(這裡以hadoop2.2.0版本為例):
在其中使用FileSystem就可以訪問hdfs:
從其中可以看出整個Configuration的載入過程,如果我們在構造FileSystem的時候,載入服務端中的兩個核心檔案,比如將程式碼寫成下面這種,是否能夠最終成功呢?
<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-mapreduce-client-core</artifactId> <version>2.2.0</version> </dependency>
FileSystem fileSystem = null;
try {
Configuration conf = new Configuration();
fileSystem = FileSystem.get(conf);
BufferedReader bufferedReader = = new BufferedReader(new InputStreamReader(fsInputStream));
但是需要注意的一點就是,只能在配置了hadoop環境的伺服器上,通過hadoop jar的方式啟動,hadoop jar的方式啟動實際上呼叫的是org.apache.hadoop.util。RunJar作為入口。
RunJar會自動將此環境中的hadoop classpath設定,並初始化hadoop的環境變數等資訊。
但是這種方式只能夠寫一個shell指令碼來實現呼叫,其中使用hadoop jar的方式來呼叫,但是如果我們想要在比較特殊的環境下啟動java程序,譬如在web server下(例如Tomcat)呼叫獲取hdfs的資訊,則不能夠成功;或比如通過java -jar/-cp的方式來進行呼叫,得到的FileSystem.get(conf)並不是我們想要的。比如如果在本機執行該方法,就會得到本機磁碟相關檔案系統,只有通過hadoop jar的方式呼叫返回DistributedFileSystem。
FileSystem類中還有一個方法,可以根據相應的URI來得到對應的FileSystem:
從hadoop file system環境中查詢對應hadoop defaultFS配置:/** * Get a filesystem instance based on the uri, the passed * configuration and the user * @param uri of the filesystem * @param conf the configuration to use * @param user to perform the get as * @return the filesystem instance * @throws IOException * @throws InterruptedException */ public static FileSystem get(final URI uri, final Configuration conf, final String user) throws IOException, InterruptedException {
<property> <name>fs.defaultFS</name> <value>hdfs://ns1</value>就會曝出以下的錯誤,提示缺少一個類定義:
通過在網上查詢的說法,此種情況下需要額外新增hadoop-auth的相關依賴:Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/hadoop/util/PlatformName at org.apache.hadoop.security.UserGroupInformation.getOSLoginModuleName(UserGroupInformation.java:303) at org.apache.hadoop.security.UserGroupInformation.<clinit>(UserGroupInformation.java:348) at org.apache.hadoop.fs.FileSystem$Cache$Key.<init>(FileSystem.java:2590) at org.apache.hadoop.fs.FileSystem$Cache$Key.<init>(FileSystem.java:2582) at org.apache.hadoop.fs.FileSystem$Cache.get(FileSystem.java:2448) at org.apache.hadoop.fs.FileSystem.get(FileSystem.java:367) at com.xxxx.HdfsMain.main(HdfsMain.java:21) Caused by: java.lang.ClassNotFoundException: org.apache.hadoop.util.PlatformName at java.net.URLClassLoader$1.run(URLClassLoader.java:366) at java.net.URLClassLoader$1.run(URLClassLoader.java:355) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:354) at java.lang.ClassLoader.loadClass(ClassLoader.java:423) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) at java.lang.ClassLoader.loadClass(ClassLoader.java:356) ... 7 more
<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-auth</artifactId> <version>2.2.0</version> </dependency>但是jar包新增進去後仍然不能夠奏效,
Exception in thread "main" java.io.IOException: No FileSystem for scheme: hdfs
at org.apache.hadoop.fs.FileSystem.getFileSystemClass(FileSystem.java:2421)
at org.apache.hadoop.fs.FileSystem.createFileSystem(FileSystem.java:2428)
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)
經過檢視原始碼,可以看出SERVICE_FILE_SYSTEMS中並沒有存在hdfs中scheme:
public static Class<? extends FileSystem> getFileSystemClass(String scheme,
Configuration conf) throws IOException {
if (!FILE_SYSTEMS_LOADED) {
loadFileSystems();
}
Class<? extends FileSystem> clazz = null;
if (conf != null) {
clazz = (Class<? extends FileSystem>) conf.getClass("fs." + scheme + ".impl", null);
}
if (clazz == null) {
clazz = SERVICE_FILE_SYSTEMS.get(scheme);
}
if (clazz == null) {
throw new IOException("No FileSystem for scheme: " + scheme);
}
return clazz;
}
可以手動採用Configuration設定fs.[filesystemname].impl的方式,手動將DistributedFileSystem註冊進去:
conf.set("fs.hdfs.impl", DistributedFileSystem.class.getName());
這樣啟動後發現仍然有坑,因為ns1並不是真實的網路地址,地址配置在hdfs-site.xml檔案中,我們的NameNode是兩個隨時可互相切換的HA伺服器:
<property> <name>dfs.nameservices</name> <value>ns1</value> </property> <property> <name>dfs.ha.namenodes.ns1</name> <value>nn1,nn2</value> </property> <property> <name>dfs.namenode.rpc-address.ns1.nn1</name> <value>xxx1.cn:8020</value> </property> <property> <name>dfs.namenode.rpc-address.ns1.nn2</name> <value>xxx2.cn:8020</value> </property>於是將fileSystem設定成其中的一臺伺服器,
fileSystem = FileSystem.get(URI.create("hdfs://xxx1.cn:8020"), conf)這樣終於成功了,注意,如果你連線上的是一個standby伺服器,是不能夠Read成功的!只有連線上active狀態的伺服器才能成功,那麼如何確定哪臺伺服器當時是active呢,可以通過命令:
hdfs haadmin -getServiceState nn1
standby
hdfs haadmin -getServiceState nn2
active
這表示nn2是active NameNode伺服器。
那麼問題就來了,如果這樣的話,當NameNode發生自動切換時,是不能夠智慧地發生切換操作的,如何避免這個問題?
在Hadoop技術內幕 hadoop common和hdfs架構設計原理與實現一書中,專門有一章節介紹hadoop hdfs configuration的設定。
從其中可以看出整個Configuration的載入過程,如果我們在構造FileSystem的時候,載入服務端中的兩個核心檔案,比如將程式碼寫成下面這種,是否能夠最終成功呢?
String uri = “/file/config";
Configuration conf = new Configuration();
conf.addResource(new Path("/home/xxxx/hadoop/etc/hadoop/core-site.xml"));
conf.addResource(new Path("/home/xxxx/hadoop/etc/hadoop/hdfs-site.xml"));
fileSystem = FileSystem.get(conf);
FileStatus[] fs = fileSystem.listStatus(new Path(uri));
for (FileStatus f : fs) {
System.out.println(f.getPath());
}
答案是可以的,甚至都不需要採用帶URI的函式。雖然繞了個大圈回到了原點,解決辦法也非常簡單,但通過這個過程的分析,可以使得我們可以熟悉整個FileSystem初始化的過程,也算是收穫不小吧。