Java 分布式缓存实现:结合 RMI 与本地文件缓存

目录

一、核心思路

二、项目结构说明

[2.1 服务端项目结构(IDEA)](#2.1 服务端项目结构(IDEA))

[2.2 客户端项目结构(Eclipse)](#2.2 客户端项目结构(Eclipse))

三、服务端实现(IDEA)

[3.1 数据库访问层](#3.1 数据库访问层)

[3.2 远程接口定义](#3.2 远程接口定义)

[3.3 远程服务实现](#3.3 远程服务实现)

[3.4 服务端启动类](#3.4 服务端启动类)

四、客户端实现(Eclipse)

[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 调用远程服务查询数据库,并将结果写入本地缓存,供后续使用。整体流程如下:

  1. 远程服务端:提供数据库查询能力,通过 RMI 暴露服务。

  2. 客户端代理层:拦截数据查询请求,管理缓存逻辑(读缓存、写缓存)。

  3. 缓存存储:使用本地文件序列化存储查询结果。

二、项目结构说明

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
  • 调用DBDataqueryDatas方法,实现数据库查询。
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 客户端第二次查询

相关推荐
2401_845417452 小时前
set和map
java·开发语言
Chasing__Dreams2 小时前
kafka--基础知识点--5.3--producer事务
分布式·kafka
所愿ღ2 小时前
JavaWeb-Servlet总结及JSP
java·笔记·servlet
杨杨杨大侠2 小时前
Atlas Mapper 教程系列 (4/10):高级映射技巧与类型转换
java·开源·github
杨杨杨大侠2 小时前
Atlas Mapper 教程系列 (3/10):核心注解详解与基础映射
java·开源·github
小枫编程2 小时前
Spring Boot 调度任务在分布式环境下的坑:任务重复执行与一致性保证
spring boot·分布式·后端
tqs_123452 小时前
redis zset 处理大规模数据分页
java·算法·哈希算法
尚学教辅学习资料2 小时前
基于Spring Boot的家政服务管理系统+论文示例参考
java·spring boot·后端·java毕设
杨杨杨大侠2 小时前
Atlas Log 0.2.0 版本
java·github·apache log4j