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

Java 分布式計算核心技術 RMI 深度解析:Stub/Skeleton、Registry、Jini 擴展與微服務演進

     編輯:Java 程式設計入門

為什麼需要 RMI?

單機 JVM 內方法調用:棧幀壓入、程序計數器跳轉、共享堆積物件。跨 JVM/跨機器調用:必須將調用語義 (對象、方法、參數) **序列化** 經網路傳輸,遠端**反序列化**執行,結果再序列化返回。RMI (Remote Method Invocation) 封裝了這套**編碼/解碼、網路通訊、服務發現**的樣板代碼,讓開發者像呼叫本地方法一樣呼叫遠端方法。

RMI 核心角色與調用流程

角色職責關鍵類/介面
遠端介面定義可遠程調用的方法簽名,延伸 java.rmi.Remote,方法拋 RemoteException自定義介面
遠端實現實際業務邏輯,繼承 UnicastRemoteObject (自動導出為遠端物件)自定義類
Stub (客戶端存根)客戶端代理,持有遠端引用,負責參數編碼、網路發送、結果解碼rmic 自動生成 _Stub 類
Skeleton (服務端骨架)服務端接收請求、解碼、分派調用真實實現、編碼返回 (JDK 1.2+ 內建於運行時,無需生成)運行時內建
RMI Registry名稱服務,綁定「名稱 ↔ Stub」,客戶端查找獲取 StubLocateRegistry, Naming

調用序列圖 (文字版)

Client           Stub (Client)          Network          Skeleton (Server)        Remote Impl
  │                  │                    │                    │                      │
  ├─ sayHello() ───► │                    │                    │                      │
  │                  ├─ 編碼參數 ────────► │                    │                      │
  │                  │                    ├─ TCP/JRMP ───────► │                      │
  │                  │                    │                    ├─ 解碼、分派 ───────► │
  │                  │                    │                    │                    ├─ 真實執行
  │                  │                    │                    │ ◄──── 返回值 ───────┤
  │                  │                    │ ◄──── 編碼結果 ────┤                      │
  │                  ├─ 解碼結果 ──────── │                    │                      │
  │ ◄──── 返回值 ────┤                    │                    │                      │

完整實戰代碼

1. 遠端介面


package com.demo.rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface HelloService extends Remote {
    String sayHello(String name) throws RemoteException;
    // 所有方法必須拋 RemoteException (或父類 IOException)
    // 參數/返回值必須可序列化 (實現 Serializable 或為基本型別)
}

2. 遠端實現


package com.demo.rmi;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
    protected HelloServiceImpl() throws RemoteException { super(); } // 必須拋 RemoteException

    @Override
    public String sayHello(String name) throws RemoteException {
        return "Server says: Hello, " + name + "! (Time: " + new java.util.Date() + ")";
    }

    public static void main(String[] args) {
        try {
            // 1. 創建實現實例
            HelloService service = new HelloServiceImpl();

            // 2. 啟動 Registry (嵌入式,或單獨啟動 rmiregistry)
            java.rmi.registry.LocateRegistry.createRegistry(1099);

            // 3. 綁定名稱
            java.rmi.Naming.rebind("rmi://localhost:1099/HelloService", service);
            System.out.println("RMI 服務已啟動,綁定名稱: HelloService");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3. 客戶端調用


package com.demo.rmi.client;

import com.demo.rmi.HelloService;
import java.rmi.Naming;

public class HelloClient {
    public static void main(String[] args) {
        try {
            // 1. 查找 Stub (需類路徑包含 HelloService 介面、Stub 類)
            HelloService service = (HelloService) Naming.lookup("rmi://localhost:1099/HelloService");

            // 2. 調用如同本地方法
            String resp = service.sayHello("Alice");
            System.out.println("收到回應: " + resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

關鍵部署細節與避坑指南

  1. Stub 類分發:客戶端類路徑必須包含遠端介面、Stub 類 (rmic 生成)。現代 JDK 使用動態代理 (Java Dynamic Proxy) 可免生成 Stub,但傳統 rmic 方式仍需了解。
  2. 序列化兼容:參數/返回值類必須實現 Serializable 並顯式宣告 serialVersionUID,否則版本不一致導致 InvalidClassException。
  3. 防火牆/NAT:RMI 預設使用隨機高端口傳輸數據,需配置 java.rmi.server.hostname 指定對外 IP,或使用 RMISocketFactory 固定端口範圍。
  4. 安全性:RMI 無內建認證/加密,生產環境需配合 SSL (RMI over SSL)、IP 白名單、防火牆隔離。
  5. Registry 單點:嵌入式 Registry 隨服務進程消失,生產建議獨立啟動 rmiregistry 並配置高可用 (或遷移至 Zookeeper/Etcd/Consul 服務發現)。

Jini / Apache River:RMI 的動態化演進

Jini 在 RMI 基礎上增加三大能力,解決傳統 RMI「強耦合 IP/端口/名稱」痛點:

特性傳統 RMIJini
服務發現客戶端硬編碼 Registry IP + 服務名稱自適應探索:組播/單播自動發現 Lookup Service,無需知 IP
服務註冊靜態綁定名稱字串註冊 服務介面類型 + 序列化代理對象 (可為 Stub、完整實現、驅動程式)
租約機制無心跳,服務掛掉 Registry 仍保留條目自恢復網絡:Lease 定期續租,失效自動移除,保證目錄準確性
代碼移動客戶端預先部署 Stub服務可動態下載代理類 (Code Mobility),客戶端零部署

Jini 運作流程

  1. Lookup Service (LUS) 啟動,發送組播公告存在。
  2. 服務啟動,組播發現 LUS,註冊介面類型 + 代理對象,獲得 Lease。
  3. 客戶端啟動,組播發現 LUS,查詢介面類型,獲取代理對象。
  4. 客戶端直接使用代理對象調用服務 (可能經 RMI、可能本地執行)。
  5. 服務定期續租;掛掉則 Lease 過期被 LUS 清理。

從 RMI 到微服務架構的演進

RMI (Java 專用、二進制協議 JRMP/IIOP、強耦合介面、單點 Registry)
    │
    ▼
Web Services (SOAP/HTTP、XML、跨語言、WSDL 契約、ESB 集中治理)
    │
    ▼
REST/JSON over HTTP (輕量、無狀態、標準化、API Gateway、服務網格)
    │
    ▼
gRPC / Dubbo / Spring Cloud (高效二進制 Protobuf、服務發現、熔斷限流、可觀測、多語言)

總結

RMI 是 Java 分布式計算的**起點**,展示了「遠程調用透明化」的經典模式:介面定義契約、Stub/Skeleton 代理模式、Registry 服務發現、序列化跨越邊界。雖然生產環境已被 gRPC、Dubbo、Spring Cloud 等現代框架取代,但理解 RMI 核心機制,對掌握**微服務通訊本質、序列化協議選型、服務發現原理、分布式異常處理**具有不可替代的教學價值。Jini 的租約、代碼移動、類型化發現思想,更直接啟發了現代服務網格的 Sidecar 模式與控制平面設計。

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