1. 程式人生 > >Java程式中不通過hadoop jar的方式訪問hdfs

Java程式中不通過hadoop jar的方式訪問hdfs

一般情況下,我們使用Java訪問hadoop distributed file system(hdfs)使用hadoop的相應api,新增以下的pom.xml依賴(這裡以hadoop2.2.0版本為例):  
<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就可以訪問hdfs:
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:
/**
 * 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 {
  從hadoop file system環境中查詢對應hadoop defaultFS配置:
<property>
    <name>fs.defaultFS</name>
    <value>hdfs://ns1</value>
   就會曝出以下的錯誤,提示缺少一個類定義:
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
  通過在網上查詢的說法,此種情況下需要額外新增hadoop-auth的相關依賴:
<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初始化的過程,也算是收穫不小吧。