目录
[2.1 服务端项目结构(IDEA)](#2.1 服务端项目结构(IDEA))
[2.2 客户端项目结构(Eclipse)](#2.2 客户端项目结构(Eclipse))
[3.1 数据库访问层](#3.1 数据库访问层)
[3.2 远程接口定义](#3.2 远程接口定义)
[3.3 远程服务实现](#3.3 远程服务实现)
[3.4 服务端启动类](#3.4 服务端启动类)
[4.1 缓存拦截器](#4.1 缓存拦截器)
[4.2 代理类](#4.2 代理类)
[4.3 RMI客户端](#4.3 RMI客户端)
[4.4 测试类](#4.4 测试类)
[5.1 服务端启动](#5.1 服务端启动)
[5.2 客户端第一次查询](#5.2 客户端第一次查询)
[5.3 客户端第二次查询](#5.3 客户端第二次查询)
在分布式系统中,缓存是提升性能的关键手段。它能减少对远程服务或数据库的重复访问,降低网络开销与服务压力。本文将通过 "RMI 远程数据查询 + 本地文件缓存"的组合,实现一套简单但实用的分布式缓存方案。
一、核心思路
当客户端需要查询数据时,优先从本地缓存文件读取;若缓存不存在,再通过 RMI 调用远程服务查询数据库,并将结果写入本地缓存,供后续使用。整体流程如下:
-
远程服务端:提供数据库查询能力,通过 RMI 暴露服务。
-
客户端代理层:拦截数据查询请求,管理缓存逻辑(读缓存、写缓存)。
-
缓存存储:使用本地文件序列化存储查询结果。
二、项目结构说明
2.1 服务端项目结构(IDEA)

2.2 客户端项目结构(Eclipse)

三、服务端实现(IDEA)
3.1 数据库访问层
数据库操作类(DBData
):负责连接数据库并执行查询
java
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 block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 查询表数据,返回List<Map>格式。查询结果列表(每行数据为Map形式)
public List<Map<String, String>> queryDatas(String tableName) {
// TODO Auto-generated method stub
this.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 block
e.printStackTrace();
} finally {
if (null != conn) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return lists;
}
}
3.2 远程接口定义
定义 RMI 远程调用的接口:
- 继承Remote接口标识为远程服务
- 方法必须声明抛出RemoteException
- 接口需要在客户端和服务端保持一致
java
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
方法,实现数据库查询。
java
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();
}
@Override
public List<Map<String, String>> queryDatas(String tableName) throws RemoteException {
// TODO Auto-generated method stub
System.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
访问。
java
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 block
e.printStackTrace();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (AlreadyBoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
四、客户端实现(Eclipse)
4.1 缓存拦截器
缓存拦截器(InterceptorData
)负责检查本地缓存文件是否存在:
- 检查
./cachermidata/
目录下是否存在目标表(tableName
)的缓存文件。 - 若存在,通过
ObjectInputStream
反序列化缓存数据。
java
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 block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else
{
System.out.println("目标数据缓存文件不存在");
lists = new ArrayList<Map<String, String>>();
}
}
}
return lists;
}
}
4.2 代理类
实现缓存逻辑(先读缓存,缓存没有则调用 RMI 并写缓存):
- 代理模式:封装
DataAop
(RMI 远程服务)和InterceptorData
(缓存拦截)。 - 缓存逻辑:先查本地缓存,缓存存在则直接返回;否则调用 RMI,再将结果写入缓存。
java
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;
}
@Override
public 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 block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
}
return dbList;
}
}
}
4.3 RMI客户端
封装 RMI 服务的查找逻辑:
- 通过
Naming.lookup
从 RMI 注册表获取远程服务引用。 - 对外暴露与
DataAop
一致的接口,隐藏 RMI 调用细节。
java
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 block
e.printStackTrace();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NotBoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public List<Map<String, String>> queryDatas(String tableName) throws RemoteException {
// TODO Auto-generated method stub
if (dataAop == null) {
this.connRMIServer();
}
System.out.println("发起远程查询: " + tableName);
return dataAop.queryDatas(tableName);
}
}
4.4 测试类
用户交互入口,指定查询的表名:
java
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 服务:

5.2 客户端第一次查询

5.3 客户端第二次查询
