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

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

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

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

二、项目结构说明

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
  • 调用DBDataqueryDatas方法,实现数据库查询。
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 服务:

5.2 客户端第一次查询

5.3 客户端第二次查询

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/96918.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/96918.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Electron第一个应用

1、安装node nodeJS下载 2、下载完成&#xff0c;需要配置环境。 写道path路径 、 3、安装完成&#xff0c;查看版本 npm -v4、 配置cnpm npm install -g cnpm --registryhttps://registry.npmmirror.com5、参考Electron 写&#xff1a; Electron第一个程序hello 6、安装…

React 原理篇 - React 新架构深度解析

使用过 React v16 之前版本的开发者或许都经历过这样的场景&#xff1a;当页面包含复杂组件或大量列表时&#xff0c;输入框打字会卡顿&#xff0c;滚动会不流畅。这些体验问题的背后&#xff0c;往往与 React 的渲染机制密切相关。2017 年 React v16 推出的 Fiber 架构&#x…

【JavaSE五天速通|第三篇】常用API与日期类篇

适合有其他语言基础想快速入门JavaSE的。用的资料是 Java入门基础视频教程 &#xff0c;从中摘取了笔者认为与其他语言不同或需要重点学习的内容 常用API与日期类只需要有印象即可&#xff0c;用到了再来这查 day04 常用API 一、StringBuilder类 StringBuilder代表可变字符…

K8s学习笔记(二) Pod入门与实战

1 K8s核心资源Pod 1.1 Pod是什么&#xff1f; 官方文档&#xff1a;Pod | Kubernetes Pod 是 Kubernetes&#xff08;k8s&#xff09;中最小的部署与调度单元&#xff0c;并非直接运行容器&#xff0c;而是对一个或多个 “紧密关联” 容器的封装。 核心特点可简单总结为 3 …

用 Python 调用 Bright Data MCP Server:在 VS Code 中实现实时网页数据抓取

用 Python 调用 Bright Data MCP Server&#xff1a;在 VS Code 中实现实时网页数据抓取&#xff0c;本文介绍了Bright Data的Web MCP Server&#xff0c;这是一款能实现实时、结构化网页数据访问的API&#xff0c;适用于AI应用等场景。其支持静态与动态网页&#xff0c;前3个月…

SPSS绘制ROC曲线并计算灵敏度、特异度

SPSS绘制ROC曲线并计算灵敏度、特异度。 &#xff08;1&#xff09;绘制ROC曲线&#xff1a; 输入&#xff1a;预测值、受试者标签。 在SPSS中点击“分析”-“分类”-“ROC曲线” 变量输入&#xff1a;检验变量输入预测值&#xff0c;状态变量输入受试者标签&#xff0c;如果标…

Modbus协议原理与Go语言实现详解

目录 Modbus协议概述协议架构与通信模式Modbus数据模型Modbus协议帧格式功能码详解Go Modbus库完整实现高级应用示例调试与故障排除 Modbus协议概述 Modbus是一种串行通信协议&#xff0c;由Modicon公司&#xff08;现施耐德电气&#xff09;于1979年开发&#xff0c;用于PL…

下载CentOS 7——从阿里云上下载不同版本的 CentOS 7

没有废话&#xff0c;直接上干货。跟着图片教程&#xff0c;一步一步来就行。 想下载其它版本的&#xff0c;自己可以再选择其它的就行。 想省事的朋友可以直接点击: 1、下载页面链接 2、CentOS-7-x86_64-DVD-2207-02(4.4GB).iso

SpringBoot -原理篇

文章目录配置优先级Bean管理获取beanbean作用域第三方beanSpringBoot原理起步依赖自动配置自动配置原理方案源码跟踪原理分析 Conditional案例&#xff08;自定义starter&#xff09;案例&#xff08;自定义starter分析&#xff09;案例&#xff08;自定义starter实现&#xff…

JavaScript与jQuery:从入门到面试的完整指南

JavaScript与jQuery&#xff1a;从入门到面试的完整指南 第一部分&#xff1a;JavaScript基础 1.1 JavaScript简介 JavaScript是一种轻量级的解释型编程语言&#xff0c;主要用于Web开发&#xff0c;可以为网页添加交互功能。它是ECMAScript规范的一种实现。 // 第一个JavaScri…

解决:Ubuntu、Kylin、Rocky系统中root用户忘记密码

解决Linux系统中root用户忘记密码 Ubuntu2204 重启电脑&#xff0c;启动时&#xff0c;长按Shift键&#xff08;对于 BIOS 系统&#xff09;或 Esc 键&#xff08;对于 UEFI 系统&#xff09;进入GRUB菜单 步骤1&#xff1a;重启Ubuntu系统&#xff0c;长按Shift键进入Ubuntu…

ENVI系列教程(二)——自定义坐标系(北京 54、西安 80、2000 坐标系)

目录 1 概述 1.1 地理投影的基本原理 1.2 国内坐标系介绍 1.3 参数的获取 2 详细操作步骤 2.1 添加椭球体 2.2 添加基准面 2.3 定义坐标系 2.4 使用自定义坐标系 1 概述 1.1 地理投影的基本原理 常用到的地图坐标系有 2 种,即地理坐标系和投影坐标系。地理坐标系是…

一种基于因果干预的少样本学习的故障诊断模型

一、研究背景与问题 ​工业背景​:机械故障诊断对工业系统安全至关重要,但实际中故障样本稀少,难以训练传统深度学习模型。 ​现有问题​: 当前少样本学习(FSL)方法大多基于相关性而非因果关系建模,容易学习到伪相关特征,导致模型可解释性差、泛化能力弱。 跨组件故障诊…

机器视觉光源的尺寸该如何选型的方法

机器视觉光源的尺寸该如何选型的方法&#x1f3af;机器视觉光源的尺寸选型的方法&#x1f3af;一、选型案例&#x1f3af;二、照射方式&#x1f3af;三、镜头选择&#x1f3af;四、光源架构光源的工作距离与视野大小&#x1f3af;五、总结&#xff1a;光源选型 —— 机器视觉检…

HTML新属性

HTML5引入了许多新属性&#xff0c;旨在增强语义化、交互性和多媒体支持。以下是一些重要的新属性及其用途分类&#xff1a;语义化与结构属性data-*&#xff1a;自定义数据属性&#xff0c;允许开发者存储额外信息&#xff08;如data-id"123"&#xff09;。hidden&am…

从工地到链上:一个土建人的 Web3 转行经历

Web3 的风&#xff0c;终究还是吹到了土建行业。2017 年&#xff0c;土建专业&#xff08;给排水工程&#xff09;的刘正源偶然看到一则关于比特币的新闻&#xff0c;被它背后的经济模型与技术架构深深震撼。到了 2021 年&#xff0c;他在工地上再次听人提起区块链&#xff0c;…

20250914-03: Langchain概念:提示模板+少样本提示

20250914-03: Langchain概念&#xff1a;提示模板少样本提示 聊天模型 消息 提示 结构化输出 &#x1f3af; 学习目标 掌握如何“喂给模型正确的输入”并“解析出想要的输出”。 &#x1f517; 核心概念 ​聊天模型&#xff08;ChatModel&#xff09;​消息&#xff08;M…

【AI推理部署】Docker篇04—Docker自动构建镜像

Docker 自动构建镜像1. Dockfile 编写2. 镜像使用使用 Dockerfile 构建镜像 Dockerfile 其实就是把我们前面的一系列安装、配置命令写到一个文件中&#xff0c;通过 docker build 命令&#xff0c;一键完成镜像的构建。接下来&#xff0c;我们以 bitnami/pytorch:2.1.1 作为基础…

LeetCode 674.最长连续递增序列

给定一个未经排序的整数数组&#xff0c;找到最长且 连续递增的子序列&#xff0c;并返回该序列的长度。 连续递增的子序列 可以由两个下标 l 和 r&#xff08;l < r&#xff09;确定&#xff0c;如果对于每个 l < i < r&#xff0c;都有 nums[i] < nums[i 1] &am…

贪心算法java

贪心算法简介贪心算法是一种在每一步选择中都采取在当前状态下最优&#xff08;局部最优&#xff09;的选择&#xff0c;从而希望导致结果是全局最优的算法。贪心算法通常用于解决最优化问题&#xff0c;如最短路径、最小生成树、任务调度等。贪心算法的基本步骤问题分析&#…