首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Hadoop 實戰二:HDFS Java API 操作與 RPC 遠程調用機制深度解析

     編輯:Java 綜合教學

Hadoop 實戰二:HDFS Java API 操作與 RPC 遠程調用機制深度解析

基於 Hadoop 偽分布式環境,本文實現 HDFS 核心文件操作 (上傳/下載/列表/移動/目錄管理) 完整 Java 客戶端代碼,並深度解析 Hadoop RPC 遠程調用底層原理、動態代理生成流程、FileSystem 獲取源碼鏈路,為理解分佈式文件系統客戶端交互打下堅實基礎。

一、HDFS Java API 完整操作類


package cn.itcast.hadoop.hdfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.apache.commons.compress.utils.IOUtils;
import org.junit.Before;
import org.junit.Test;

import java.io.*;
import java.net.URI;

public class HdfsClient {
    private FileSystem fs;
    private static final String HDFS_URI = "hdfs://zpfbuaa:9000/";

    @Before
    public void init() throws IOException {
        Configuration conf = new Configuration();
        conf.set("fs.defaultFS", HDFS_URI);
        // 可選:Kerberos 認證、代理用戶等
        fs = FileSystem.get(URI.create(HDFS_URI), conf);
    }

    /** 1. 上傳文件 (流式複製,大文件友好) */
    @Test
    public void upload() throws IOException {
        try (FSDataOutputStream os = fs.create(new Path("/user/hadoop/my.jar"));
             FileInputStream is = new FileInputStream("/home/hadoop/download/my.jar")) {
            IOUtils.copy(is, os); // Commons IO 工具類
        }
    }

    /** 2. 便捷上傳 API (內部同流式複製) */
    @Test
    public void upload2() throws IOException {
        fs.copyFromLocalFile(new Path("/home/hadoop/download/my.jar"), 
                             new Path("/aaa/bbb/ccc/my3.jar")); // 父目錄需存在或自動創建
    }

    /** 3. 下載文件 */
    @Test
    public void download() throws IOException {
        try (FSDataInputStream is = fs.open(new Path("/jdk-7u65-linux-i586.tar.gz"));
             FileOutputStream os = new FileOutputStream("/home/hadoop/download/jdk.tar.gz")) {
            IOUtils.copy(is, os);
        }
    }

    /** 4. 列出文件/目錄 (遞歸) */
    @Test
    public void listFiles() throws IOException {
        System.out.println("=== listFiles (遞歸, 含文件元數據) ===");
        RemoteIterator<LocatedFileStatus> it = fs.listFiles(new Path("/"), true);
        while (it.hasNext()) {
            LocatedFileStatus f = it.next();
            System.out.printf("文件: %s, 大小: %d, 塊: %s%n", 
                f.getPath(), f.getLen(), Arrays.toString(f.getBlockLocations()));
        }

        System.out.println("\n=== listStatus (單級, 區分文件/目錄) ===");
        FileStatus[] statuses = fs.listStatus(new Path("/"));
        for (FileStatus s : statuses) {
            System.out.println(s.getPath().getName() + (s.isDirectory() ? " [DIR]" : " [FILE]"));
        }
    }

    /** 5. 創建多級目錄 */
    @Test
    public void mkdirs() throws IOException {
        fs.mkdirs(new Path("/aaa/bbb/ccc/ddd"));
    }

    /** 6. 刪除文件/目錄 (遞歸刪除) */
    @Test
    public void delete() throws IOException {
        // 第二參數 true: 遞歸刪除非空目錄
        fs.delete(new Path("/aaa/bbb"), true);
    }

    /** 7. 移動/重命名 (同文件系統內即時完成) */
    @Test
    public void rename() throws IOException {
        fs.rename(new Path("/old/path"), new Path("/new/path"));
    }

    /** 8. 讀取文件內容 (小文件) */
    @Test
    public void cat() throws IOException {
        try (FSDataInputStream in = fs.open(new Path("/user/hadoop/wordcount/input.txt"))) {
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String line;
            while ((line = br.readLine()) != null) System.out.println(line);
        }
    }
}

二、Hadoop RPC 遠程調用機制深度解析

1. RPC 架構角色

角色職責示例類
Client Stub (Proxy)本地動態代理,攔截方法調用,序列化參數,發送請求LoginController 中 RPC.getProxy() 返回
Server Stub (Skeleton)服務端接收請求,反序列化,調用真實實現,返回結果RPC.Server 內部 Handler
Protocol Interface定義服務契約,雙端共享,含版本號 versionIDLoginServiceInterface
Implementation業務邏輯實現LoginServiceImpl

2. 核心代碼示例

共享接口 (含版本號)


public interface LoginServiceInterface {
    long versionID = 1L; // 版本號,客戶端/服務端必須一致
    String login(String username, String password);
}

服務端實現與啟動


public class LoginServiceImpl implements LoginServiceInterface {
    @Override
    public String login(String username, String password) {
        return username + " logged in successfully!";
    }
}

public class RpcServer {
    public static void main(String[] args) throws IOException {
        Configuration conf = new Configuration();
        RPC.Builder builder = new RPC.Builder(conf);
        builder.setBindAddress("zpfbuaa")      // 綁定 IP
               .setPort(10000)                 // 綁定端口
               .setProtocol(LoginServiceInterface.class) // 協議接口
               .setInstance(new LoginServiceImpl());     // 實現實例
        
        RPC.Server server = builder.build();
        server.start(); // 非阻塞啟動
        System.out.println("RPC Server started on port 10000");
    }
}

客戶端調用


public class LoginController {
    public static void main(String[] args) throws IOException {
        // 獲取動態代理
        LoginServiceInterface proxy = RPC.getProxy(
            LoginServiceInterface.class, 
            LoginServiceInterface.versionID, // 版本號
            new InetSocketAddress("zpfbuaa", 10000),
            new Configuration()
        );
        
        // 像調用本地方法一樣調用遠程服務
        String result = proxy.login("zpfbuaa", "123456789");
        System.out.println("Server response: " + result);
    }
}

3. RPC 調用完整流程 (9 步)

  1. 客戶端調用 proxy.login(...)。
  2. 動態代理 (InvocationHandler) 攔截,構建 Invocation 對象 (方法名、參數、版本號)。
  3. 客戶端 Connection 發送序列化後的 Invocation 到服務端 (TCP, 默認 Hadoop Writable 序列化)。
  4. 服務端 Listener 線程接收請求,交給 Handler 線程池處理。
  5. Handler 反序列化 Invocation,根據接口類找到實現實例。
  6. 服務端動態代理調用實際實現 impl.login(...)。
  7. 實現類執行邏輯,返回結果。
  8. 服務端序列化結果,發送回客戶端。
  9. 客戶端反序列化結果,返回給調用者。

4. 關鍵設計亮點

  • 版本號機制:versionID 確保客戶端/服務端協議兼容,升級時可平滑過渡。
  • 動態代理雙端對稱:客戶端代理發起調用,服務端代理接收分發,解耦網絡通信與業務邏輯。
  • Failover 支持:RPC.getProxy 可配置多地址,自動重試。
  • 可插拔序列化:默認 Writable,支持 Protobuf/Avro 等高性能格式。

三、FileSystem.get() 源碼鏈路追蹤 (斷點調試圖解)

調用 FileSystem.get(conf) 到返回 DistributedFileSystem 實例的關鍵調用鏈:


FileSystem.get(Configuration)
  → FileSystem.get(URI, Configuration)
    → FileSystem.createFileSystem(URI, Configuration)  // 反射實例化
      → DistributedFileSystem 初始化
        → initialize(URI, Configuration)
          → DFSClient 初始化 (核心客戶端)
            → NameNode 連接建立 (RPC Client)
            → LeaseRenewer 租約續期線程啟動
            → 心跳/緩存初始化

關鍵斷點位置:

  • org.apache.hadoop.fs.FileSystem#get(URI, Configuration) (第 1 步)
  • org.apache.hadoop.fs.FileSystem#createFileSystem (反射加載實現類)
  • org.apache.hadoop.hdfs.DistributedFileSystem#initialize (DFSClient 創建)
  • org.apache.hadoop.hdfs.DFSClient#<init> (NameNode RPC 代理創建)

四、生產環境最佳實踐

  1. 連接池化:FileSystem 線程不安全,多線程環境用 ThreadLocal 或連接池 (如 Hadoop-FS-Pool)。
  2. 緩衝流包裝:大文件讀寫必須用 BufferedInputStream/OutputStream 或 IOUtils.copyLarge。
  3. 路徑處理:始終使用 Path 對象,避免字符串拼接路徑分隔符問題。
  4. 異常處理:捕獲 FileNotFoundException, IOException, AccessControlException (權限)。
  5. Kerberos 安全集群:配置 UserGroupInformation.loginUserFromKeytab() 登錄。
  6. 關閉資源:fs.close() 釋放 DFSClient、LeaseRenewer、RPC 連接。

五、從 RPC 看 HDFS 架構

HDFS 所有元數據操作 (mkdir, rename, delete, getBlockLocations) 均通過 ClientProtocol (RPC) 發送給 NameNode;數據讀寫則通過 DataTransferProtocol (TCP 直連 DataNode)。RPC 是 NameNode 服務化的基石,其高性能序列化、動態代理、版本控制、Failover 機制,直接決定了 HDFS 元數據操作的吞吐與可用性。

掌握 HDFS Java API 與 RPC 內部機制,不僅能編寫健壯的大數據應用,更能在排查 NameNode 高負載、客戶端超時、租約過期等疑難雜症時,從源碼層面精準定位根因。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved