目錄
一、核心思路
二、項目結構說明
2.1?服務端項目結構(IDEA)
2.2?客戶端項目結構(Eclipse)
三、服務端實現(IDEA)
3.1 數據庫訪問層
3.2 遠程接口定義
3.3 遠程服務實現
3.4 服務端啟動類
四、客戶端實現(Eclipse)
4.1 緩存攔截器
4.2 代理類
4.3 RMI客戶端
4.4 測試類
五、運行流程與效果
5.1 服務端啟動
5.2 客戶端第一次查詢
5.3 客戶端第二次查詢
????在分布式系統中,緩存是提升性能的關鍵手段。它能減少對遠程服務或數據庫的重復訪問,降低網絡開銷與服務壓力。本文將通過 “RMI 遠程數據查詢 + 本地文件緩存”的組合,實現一套簡單但實用的分布式緩存方案。
一、核心思路
????????當客戶端需要查詢數據時,優先從本地緩存文件讀取;若緩存不存在,再通過 RMI 調用遠程服務查詢數據庫,并將結果寫入本地緩存,供后續使用。整體流程如下:
-
遠程服務端:提供數據庫查詢能力,通過 RMI 暴露服務。
-
客戶端代理層:攔截數據查詢請求,管理緩存邏輯(讀緩存、寫緩存)。
-
緩存存儲:使用本地文件序列化存儲查詢結果。
二、項目結構說明
2.1?服務端項目結構(IDEA)
2.2?客戶端項目結構(Eclipse)
三、服務端實現(IDEA)
3.1 數據庫訪問層
????????數據庫操作類(DBData
):負責連接數據庫并執行查詢
package com.demo14.dao;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class DBData {Connection conn;public void connDB() {try {Class.forName("com.mysql.cj.jdbc.Driver");conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/jk202508", "root", "152602");} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}// 查詢表數據,返回List<Map>格式。查詢結果列表(每行數據為Map形式)public List<Map<String, String>> queryDatas(String tableName) {// TODO Auto-generated method stubthis.connDB();String sql = "select * from " + tableName;List<Map<String, String>> lists = new ArrayList<Map<String, String>>();try {PreparedStatement pstmt = this.conn.prepareStatement(sql);ResultSet rs = pstmt.executeQuery();ResultSetMetaData rsmd = rs.getMetaData();int columns = rsmd.getColumnCount();while (rs.next()) {Map<String, String> LineMap = new HashMap<String, String>();for (int i = 0; i < columns; i++) {LineMap.put(rsmd.getColumnName(i + 1), rs.getString(i + 1));}lists.add(LineMap);}} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {if (null != conn) {try {conn.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}return lists;}}
3.2 遠程接口定義
????????定義 RMI 遠程調用的接口:
- 繼承Remote接口標識為遠程服務
- 方法必須聲明拋出RemoteException
- 接口需要在客戶端和服務端保持一致
package com.demo14.interfaces;import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Map;public interface DataAop extends Remote {public List<Map<String,String>> queryDatas(String name) throws RemoteException;}
3.3 遠程服務實現
????????實現遠程接口,調用DBData
查詢數據庫:
- 繼承
UnicastRemoteObject
,自動將對象導出為 RMI 遠程對象。 - 構造函數必須拋出RemoteException
- 調用
DBData
的queryDatas
方法,實現數據庫查詢。
package com.demo14.impl;import com.demo14.dao.DBData;
import com.demo14.interfaces.DataAop;import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.List;
import java.util.Map;/*** 遠程服務實現類* 繼承UnicastRemoteObject并提供遠程方法實現*/
public class DataAopImpl extends UnicastRemoteObject implements DataAop {public DataAopImpl() throws RemoteException {super();}@Overridepublic List<Map<String, String>> queryDatas(String tableName) throws RemoteException {// TODO Auto-generated method stubSystem.out.println("RMI服務器:查詢數據庫表 " + tableName);DBData db = new DBData();List<Map<String, String>> result = db.queryDatas(tableName);System.out.println("查詢完成,返回" + result.size() + "條記錄");return result;}}
3.4 服務端啟動類
????????啟動 RMI 服務并注冊遠程對象:
LocateRegistry.createRegistry(9200)
:在端口 9200 啟動 RMI 注冊表。Naming.bind(...)
:將DataAopImpl
實例綁定到 RMI 注冊表,客戶端可通過rmi://127.0.0.1:9200/queryDatas
訪問。
package com.demo14.rmiserver;import com.demo14.impl.DataAopImpl;
import com.demo14.interfaces.DataAop;import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;// RMI服務端啟動類
public class Main {public static void main( String[] args ){try {// 1. 創建遠程服務實例DataAop dataAop = new DataAopImpl();// 2. 啟動RMI注冊表(端口9200)LocateRegistry.createRegistry(9200);System.out.println("RMI注冊表啟動成功,端口: 9200");// 3. 綁定遠程服務到注冊表(Java命名目錄服務)Naming.bind("rmi://127.0.0.1:9200/queryDatas", dataAop);System.out.println("數據服務已就緒,等待客戶端連接...");} catch (RemoteException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (MalformedURLException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (AlreadyBoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
四、客戶端實現(Eclipse)
4.1 緩存攔截器
????????緩存攔截器(InterceptorData
)負責檢查本地緩存文件是否存在:
- 檢查
./cachermidata/
目錄下是否存在目標表(tableName
)的緩存文件。 - 若存在,通過
ObjectInputStream
反序列化緩存數據。
package com.demo14;import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** 緩存攔截器 - 負責檢查本地緩存文件* 實現緩存檢查和加載功能*/
public class InterceptorData {public List checkFile(String tableName) {List<Map<String, String>> lists = null;// 假設有一個緩沖的目錄的存在File file = new File("./cachermidata");File[] fs = file.listFiles();if (fs.length == 0) {System.out.println("該目錄下沒有緩存的目錄數據文件");lists = new ArrayList<Map<String, String>>();} else {System.out.println("該目錄下有緩存的目錄數據文件");for (File f : fs) {if (f.getName().contains(tableName)) {System.out.println("目標數據緩存文件存在");ObjectInputStream objIn = null;try {objIn = new ObjectInputStream(new FileInputStream("./cachermidata/" + tableName + ".datas"));lists = (ArrayList<Map<String, String>>) objIn.readObject();System.out.println("從緩存加載" + lists.size() + "條記錄");break;} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}}else{System.out.println("目標數據緩存文件不存在");lists = new ArrayList<Map<String, String>>();}}}return lists;}}
4.2 代理類
????????實現緩存邏輯(先讀緩存,緩存沒有則調用 RMI 并寫緩存):
- 代理模式:封裝
DataAop
(RMI 遠程服務)和InterceptorData
(緩存攔截)。 - 緩存邏輯:先查本地緩存,緩存存在則直接返回;否則調用 RMI,再將結果寫入緩存。
package com.demo14;import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Map;
import java.rmi.Remote;/*** 代理類 - 實現緩存策略的核心* 采用代理模式,在遠程調用前先檢查緩存*/
public class ProxyData implements DataAop {private DataAop dataAop; // RMI遠程服務private InterceptorData interceptor; // 緩存攔截public ProxyData(DataAop dataAop, InterceptorData interceptor) {this.dataAop = dataAop;this.interceptor = interceptor;}@Overridepublic List<Map<String, String>> queryDatas(String tableName) {// 檢查本地緩存List lists = this.interceptor.checkFile(tableName);if (lists.size() > 0) {System.out.println("直接從緩存中獲取數據....");return lists;} else {// 緩存未命中,調用RMI查詢遠程服務List<Map<String, String>> dbList = null;try {System.out.println("要去分布式RMI服務器查詢數據....");dbList = this.dataAop.queryDatas(tableName);// 將查詢結果寫入本地緩存ObjectOutputStream objOut = null;objOut = new ObjectOutputStream(new FileOutputStream("./cachermidata/" + tableName + ".datas"));objOut.writeObject(dbList);} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (Exception e) {}return dbList;}}}
4.3 RMI客戶端
????????封裝 RMI 服務的查找邏輯:
- 通過
Naming.lookup
從 RMI 注冊表獲取遠程服務引用。 - 對外暴露與
DataAop
一致的接口,隱藏 RMI 調用細節。
package com.demo14;import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Map;/*** RMI客戶端 - 負責與遠程服務通信* 實現DataAop接口,提供遠程調用能力*/
public class RMIData implements DataAop{DataAop dataAop;// 連接RMI服務器public void connRMIServer() {try {dataAop = (DataAop) Naming.lookup("rmi://127.0.0.1:9200/queryDatas");System.out.println("RMI服務器連接成功");} catch (MalformedURLException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (RemoteException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (NotBoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}}@Overridepublic List<Map<String, String>> queryDatas(String tableName) throws RemoteException {// TODO Auto-generated method stubif (dataAop == null) {this.connRMIServer();}System.out.println("發起遠程查詢: " + tableName);return dataAop.queryDatas(tableName);}}
4.4 測試類
????????用戶交互入口,指定查詢的表名:
package com.demo14;import java.util.List;
import java.util.Map;
import java.util.Scanner;public class Test {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.println("=== 分布式緩存數據查詢系統 ===");System.out.print("請輸入要查詢的表名: ");String tableName = scanner.nextLine();System.out.print("請輸入要顯示的字段名: ");String keyName = scanner.nextLine();// 創建代理實例(組合了遠程調用和緩存功能)ProxyData proxy = new ProxyData(new RMIData(), new InterceptorData());List<Map<String, String>> result = proxy.queryDatas(tableName);if (result.isEmpty()) {System.out.println("未找到數據或表不存在");} else {// 顯示指定字段的數據for (Map<String, String> row : result) {String value = row.get(keyName);if (value != null) {System.out.println(keyName + ": " + value);}}System.out.println("共 " + result.size() + " 條記錄");}}}
五、運行流程與效果
5.1 服務端啟動
?運行Main
類,啟動 RMI 服務: