隨著電力企業信息化深入,跨網絡、跨平台、跨部門的數據交換與業務協同需求激增。RMI (Remote Method Invocation) 作為 **Java 平台內建的分布式對象調用機制**,提供了透明的遠程方法調用語義,是構建企業級分布式應用的基石技術之一。本文系統闡述 RMI 架構原理、核心組件、實現步驟、與 CORBA/Web Services 對比,並給出生產級部署建議。
| 維度 | Socket (TCP) | RMI | CORBA |
|---|---|---|---|
| 程式模型 | 流式字節收發,需自定義協議解析 | **遠程介面調用,語法與本地方法調用一致** | IDL 定義介面,多語言映射 (C++/Java/COBOL) |
| 開發效率 | 極低 (手寫協議、序列化、連線池、心跳、重試) | **高 (JDK 內建序列化、Stub/Skeleton 自動生成、註冊表查找)** | 中 (需 IDL 編譯器、ORB 部署、複雜配置) |
| 類型安全 | 無 (運行期解析錯誤) | **編譯期類型檢查 (介面契約)** | 編譯期 (IDL → 語言映射) |
| 跨語言 | 可 (協議層協商) | **純 Java** | **核心優勢:異構語言互操作** |
| 防火牆穿透 | 需固定埠口、應用層代理 | 需固定埠口 (RMI Registry + Server Socket)、可配合 SOCKS/HTTP 通道 | IIOP 固定埠口、支援防火牆代理 |
| 適用場景 | 高效能、自定義協議、非 Java 互通 | **Java 內部微服務、舊系統改造、EJB 底層、分布式計算節點** | 異構企業級集成 (主機、ERP、C++ 系統) |
結論:在**純 Java 技術棧**內部服務調用,RMI 開發效率最高、類型最安全、維護成本最低。跨語言場景選 gRPC/Thrift/REST。
+----------------+ 1. 綁定/查找 +------------------+
| Client | <---------------------> | RMI Registry |
| (Stub 代理) | (Naming/Context) | (命名服務) |
+----------------+ +------------------+
| ^
| 2. 遠程調用 (JRMP/RMI-IIOP) | 3. 啟動註冊遠程物件
v |
+----------------+ +------------------+
| Server | ----------------------> | Remote Object |
| (Skeleton/Impl)| 導出/註冊 | (實作類) |
+----------------+ +------------------+
java.rmi.Remote,方法拋 RemoteException,定義服務契約。UnicastRemoteObject (自動導出) 或手動 exportObject()。Naming.bind/rebind/lookup 或 JNDI Context 操作。
package com.power.grid.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
public interface MeterDataService extends Remote {
List getReadings(String meterId, long startTime, long endTime) throws RemoteException;
void submitReading(MeterReading reading) throws RemoteException;
}
package com.power.grid.rmi.server;
import com.power.grid.rmi.MeterDataService;
import com.power.grid.model.MeterReading;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class MeterDataServiceImpl extends UnicastRemoteObject implements MeterDataService {
private final Map> storage = new ConcurrentHashMap<>();
protected MeterDataServiceImpl() throws RemoteException {
super(); // 自動導出,監聽匿名埠口
}
@Override
public List getReadings(String meterId, long startTime, long endTime) throws RemoteException {
return storage.getOrDefault(meterId, Collections.emptyList())
.stream()
.filter(r -> r.getTimestamp() >= startTime && r.getTimestamp() <= endTime)
.toList();
}
@Override
public void submitReading(MeterReading reading) throws RemoteException {
storage.computeIfAbsent(reading.getMeterId(), k -> Collections.synchronizedList(new ArrayList<>()))
.add(reading);
}
}
package com.power.grid.rmi.server;
import com.power.grid.rmi.MeterDataService;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class RmiServer {
public static void main(String[] args) {
try {
// 1. 建立/獲取 RMI Registry (JVM 內建,預設埠 1099)
LocateRegistry.createRegistry(1099); // 或確保外部 registry 已啟動
// 2. 實例化遠程物件
MeterDataService service = new MeterDataServiceImpl();
// 3. 綁定名稱 (rmi://host:port/name)
String url = "rmi://localhost:1099/MeterDataService";
Naming.rebind(url, service); // rebind 覆蓋舊綁定
System.out.println("RMI Server started. Service bound to: " + url);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.power.grid.rmi.client;
import com.power.grid.rmi.MeterDataService;
import com.power.grid.model.MeterReading;
import java.rmi.Naming;
import java.time.Instant;
import java.util.List;
public class RmiClient {
public static void main(String[] args) {
try {
String url = "rmi://localhost:1099/MeterDataService";
MeterDataService service = (MeterDataService) Naming.lookup(url);
// 測試提交
MeterReading reading = new MeterReading("METER-001", Instant.now().toEpochMilli(), 1250.5);
service.submitReading(reading);
System.out.println("Submitted: " + reading);
// 測試查詢
List readings = service.getReadings("METER-001", 0, System.currentTimeMillis());
System.out.println("Readings count: " + readings.size());
readings.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
}
}
}
| 課題 | 解決方案 |
|---|---|
| 固定伺服器埠口 (穿透防火牆) | UnicastRemoteObject.exportObject(obj, port) 指定固定埠口,或設定系統屬性 -Djava.rmi.server.hostname=外部IP、-Dsun.rmi.transport.tcp.localport=固定埠 (JDK 內部屬性,慎用)。 |
| 序列化版本控制 | 所有遠程傳遞物件實作 Serializable 並宣告 private static final long serialVersionUID = 1L;,避免版本不匹配導致 InvalidClassException。 |
| 安全性 | 1. 啟用 SecurityManager + policy 限制權限。2. RMI over SSL (RMI/SSL) 配置 javax.net.ssl.keyStore/trustStore、自定義 RMIClientSocketFactory/RMIServerSocketFactory。3. 註冊表綁定驗證 (自定義 Registry 實作)。 |
| 超時與重試 | 客戶端設定 sun.rmi.transport.tcp.responseTimeout (毫秒);業務層實作重試邏輯 (指數退避)、冪等性設計。 |
| 分散式垃圾回收 (DGC) | RMI 內建 DGC (租約機制),客戶端定期續租 (renew),租約過期伺服器端可回收遠程物件。-Dsun.rmi.dgc.client.gcInterval=3600000 (毫秒) 調整客戶端 GC 間隔。 |
| 叢集與負載均衡 | RMI 無內建叢集。方案: 1. 客戶端維護多 Stub 列表,輪詢/隨機選擇。 2. 前置負載均衡器 (Nginx/HAProxy TCP 模式) 轉發 RMI 流量 (需固定埠口)。 3. 遷移至 gRPC/Dubbo/Spring Cloud 具備服務發現與負載均衡能力。 |
JMXConnectorServer 使用 RMI 連接器暴露 MBean。RMI 以**「透明遠程調用、內建序列化、命名服務、分散式 GC」**解決了 Java 內部服務調用的核心難題。掌握其架構原理、Stub/Skeleton 機制、序列化陷阱、安全與部署細節,是理解 Java 分布式計算演進史 (RMI → EJB → Web Services → REST/gRPC → Service Mesh) 的必修課。雖新專鮮少直接用 RMI,但其設計哲學 — **位置透明、介面契約、序列化邊界** — 永遠值得學習。