Java 分布式缓存实现:结合 RMI 与本地文件缓存
目录
一、核心思路
二、项目结构说明
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 服务: