首页 > 编程语言 >分布式计算编程项目二

分布式计算编程项目二

时间:2024-05-24 22:51:56浏览次数:34  
标签:服务 项目 数据库 编程 分布式计算 java RMI 远程 客户端

利用RPC技术实现一个学生信息管理系统

目录

一、具体要求

1.客户端实现用户交互,服务器端实现学生信息存储和管理。客户端与服务器端利用RPC机制进行协作。中间件任选。

2.服务器端至少暴漏如下RPC接口:

  • bool add(Student stu) 添加一条学生信息
  • Student queryByID(int stuID) 查询指定ID号的学生信息
  • Student queryByName(String name) 按姓名查询符合条件的学生信息
  • bool delete (int stuID) 删除指定ID号的学生信息

二、相关理论

理论迭代

1.单机结构

一个系统业务量很小的时候,所有的代码都放在一个项目中,然后这个项目部署在一台服务器上。整个项目所有的服务都由这台服务器提供。这就是单机结构。 它的缺点是显而易见的,因为单机的处理能力毕竟是有限的,当你的业务增长到一定程度的时候,单机的硬件资源将无法满足你的业务需求。此时便出现了集群模式。

2.集群结构

单机处理到达瓶颈的时候,把单机复制几份,这样就构成了一个“集群”。集群中每台服务器就叫做这个集群的一个“节点”,所有节点构成了一个集群。每个节点都提供相同的服务,那么这样系统的处理能力就相当于提升了好几倍(有几个节点就相当于提升了这么多倍)。

但问题是用户的请求究竟由哪个节点来处理呢?最好能够让此时此刻负载较小的节点来处理,这样使得每个节点的压力都比较平均。要实现这个功能,就需要在所有节点之前增加一个“调度者”的角色,用户的所有请求都先交给它,然后它根据当前所有节点的负载情况,决定将这个请求交给哪个节点处理。这个“调度者”有个名字——负载均衡服务器。

集群结构的好处就是系统扩展非常容易。如果随着系统业务的发展,当前的系统又支撑不住了,那么给这个集群再增加节点就行了。但是,当你的业务发展到一定程度的时候,你会发现一个问题——无论怎么增加节点,整个集群性能的提升效果并不明显了。这时候,就需要使用微服务结构了。

3.微服务结构

从单机结构到集群结构,代码基本无需要作任何修改,要做的仅仅是多部署几台服务器,每台服务器上运行相同的代码就行了。但是,当要从集群结构演进到微服务结构的时候,之前的那套代码就需要发生较大的改动了。所以对于新系统建议系统设计之初就采用微服务架构,这样后期运维的成本更低。因为如果一套老系统需要升级成微服务结构的话,那就得对代码大动干戈了。

微服务介绍

微服务就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在微服务结构中,每个子系统就被称为“服务”。这些子系统能够独立运行在web容器中,它们之间通过RPC方式通信。

eg.假设需要开发一个在线商城。按照微服务的思想,我们需要按照功能模块拆分成多个独立的服务,如:用户服务、产品服务、订单服务、后台管理服务、数据分析服务等等。这一个个服务都是一个个独立的项目,可以独立运行。如果服务之间有依赖关系,那么通过RPC方式调用。

相关优点:

  1. 系统之间的耦合度大大降低,可以独立开发、独立部署、独立测试,系统与系统之间的边界非常明确,排错也变得相当容易,开发效率大大提升。
  2. 系统之间的耦合度降低,从而系统更易于扩展。我们可以针对性地扩展某些服务。假设这个商城要搞一次大促,下单量可能会大大提升,因此我们可以针对性地提升订单系统、产品系统的节点数量,而对于后台管理系统、数据分析系统而言,节点数量维持原有水平即可。
  3. 服务的复用性更高。比如,当我们将用户系统作为单独的服务后,该公司所有的产品都可以使用该系统作为用户系统,无需重复开发。

RPC介绍

img
  1. 注册中心 :注册中心负责服务地址的注册与查找,相当于目录服务。服务端启动的时候将服务名称及其对应的地址(ip+port)注册到注册中心,服务消费端根据服务名称找到对应的服务地址。有了服务地址之后,服务消费端就可以通过网络请求服务端了。

    例如:

    • RMI客户端在调用远程方法时先创建Stub。
    • Skeleton处理客户端请求:lookup、rebind、unbind,如果是lookup则查找RMI服务名绑定的接口对象。
  2. 网络传输 :既然要调用远程的方法就要发请求,请求中至少要包含你调用的类名、方法名以及相关参数。

    例如:

    • RMI客户端的远程引用层传输RemoteCall序列化后的请求信息通过Socket连接的方式(传输层)传输到RMI服务端的远程引用层。
  3. 序列化 :既然涉及到网络传输就一定涉及到序列化,这里直接使用 JDK 自带的序列化。这种内置的序列化机制允许开发者将实现了Serializable接口的Java对象转换为一个字节流。

    image-20240412135736823

    例如:

    • RemoteCall序列化RMI服务名称、Remote对象。
    • Skeleton调用RemoteCall反序列化RMI客户端传过来的请求信息。
    • Skeleton处理客户端请求,序列化该对象并通过RemoteCall传输到客户端。
    • RMI客户端反序列化服务端结果,获取远程对象的引用。
    • RMI服务端反射调用RMI服务实现类的对应方法并序列化执行结果返回给客户端。
    • RMI客户端反序列化RMI远程方法调用结果,即为最终结果。
  4. 动态代理 : 另外,动态代理也是需要的。因为 RPC 的主要目的就是让我们调用远程方法像调用本地方法一样简单,使用动态代理可以屏蔽远程方法调用的细节比如网络传输。也就是说当你调用远程方法的时候,实际会通过代理对象来传输网络请求(不然的话,怎么可能直接就调用到远程方法呢?)
    例如:

    • RMI客户端在调用远程方法时先创建Stub。这个步骤通常涉及动态代理,因为Stub作为客户端和服务端之间的代理,隐藏了远程方法调用的复杂性。
  5. 负载均衡 :如果我们的系统中的某个服务的访问量特别大,我们将这个服务部署在了多台服务器上,当客户端发起请求的时候,多台服务器都可以处理这个请求。那么,如何正确选择处理该请求的服务器就很关键。负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题。

    例如:

    • 尽管负载均衡在这个基础的RMI调用流程中没有直接提及,但如果RMI服务部署在多个服务器上,负载均衡可以在客户端调用远程方法时选择合适的服务器实例。

使用到的相关包

RMI相关包

import java.rmi.Naming;//这个类提供了在RMI注册表中查找和绑定远程对象的方法。
import java.rmi.registry.LocateRegistry;// 这个类用于获取和创建RMI注册表的实例,注册表是用于注册和查找远程对象的。
import java.rmi.Remote;// 这是一个标记接口,用于标识可以从非本地虚拟机上调用的接口。
import java.rmi.RemoteException;// 这个类用于声明远程方法调用过程中可能出现的异常。
import java.rmi.server.UnicastRemoteObject;// 这个类用于导出一个对象,使其成为远程对象,并使其能够接收远程方法调用。

其他包

import InterFace.*;// 这是一个自定义的包,包含了项目中定义的所有接口。

import java.util.List;// 这个接口代表一个元素的有序集合。
import java.util.ArrayList;// 这个类提供了List接口的可调整大小的数组实现。
import java.util.HashMap;// 这个类提供了基于哈希表的Map接口的实现。
import java.util.stream.Collectors;// 这个工具类提供了基于流操作的归约和汇总操作的实现。
import java.util.Scanner;// 这个类用于解析基本类型和字符串的简单文本扫描器。

import org.slf4j.Logger;// SLF4J的Logger类,用于记录日志。
import org.slf4j.LoggerFactory;// LoggerFactory类用于创建Logger对象。

import java.sql.Connection;// 这个接口定义了与特定数据库的连接,通过它可以发送命令并接收结果。
import java.sql.PreparedStatement;// 这个接口表示预编译的SQL语句,可以用于执行带或不带输入参数的SQL语句。
import java.sql.ResultSet;// 这个接口表示数据库结果集,它是通过执行查询数据库的语句生成的。
import java.sql.SQLException;// 提供关于数据库访问错误或其他错误信息的异常。
import java.sql.DriverManager;// 这个类管理一组JDBC驱动程序的基本服务。

import java.io.Serializable;// 这是一个标记接口,用于启用其序列化功能的类必须实现此接口。

MySQL启动方法

mysql的启动和环境配置 https://blog.csdn.net/qq_47900752/article/details/130324285

三、代码架构

  1. 客户端 (RMIClient):
    • 提供用户界面,通过控制台与用户交互,执行增加、查询、修改、删除和打印学生信息的操作。
    • 使用Java RMI的Naming.lookup方法从RMI注册表中获取服务端的远程对象引用,以调用远程方法。
  2. 服务端 (RMIServer):
    • 注册远程对象StudentSystemImpl,使其可通过RMI客户端访问。
    • 创建和管理RMI服务注册表,允许在指定的端口(如9527)上接收请求,最后将RMI服务名绑定至远程方法实现类上。
    • 先定义RMI服务器IP地址、监听端口号、RMI服务名称,再创建注册中心。
  3. 远程接口 (StudentSystemInt):
    • 定义了客户端和服务端共用的远程方法接口,如添加、查询、删除、修改学生信息和打印学生列表。
    • 所有方法都声明了抛出RemoteException,以处理远程调用中可能发生的异常。
  4. 远程对象实现 (StudentSystemImpl):
    • 其中实现学生信息管理系统的数据结构定义以及数据操纵函数。
    • 实现了StudentSystemInt接口,处理实际的业务逻辑。
    • 使用HashMap来存储学生数据,同时通过StudentDaoImpl与数据库交互,实现数据的持久化。
  5. 数据访问对象 (StudentDao, StudentDaoImpl):
    • 提供对数据库的访问方法,如保存、查询、删除和更新学生信息。
    • 使用JDBC(是一种Java API,用于在Java应用程序中连接和执行查询与数据库。这个API,由一组用Java语言编写的类和接口组成,提供了一种标准方法,让Java程序能够独立于底层数据库访问与操作数据)进行数据库操作,保证数据的持久存储。

四、功能实现+界面展示

基本功能:

  • 增加学生:客户端收集学生ID和姓名,调用服务端的添加方法。服务端检查缓存(HashMap)中是否已存在该学生,若不存在则添加到缓存和数据库。
  • 查询学生:支持通过ID或姓名查询。通过ID查询会先查缓存,未命中再查询数据库。通过姓名查询使用并行流从缓存中筛选,未命中则查询数据库。
  • 删除学生:从缓存和数据库中删除指定ID的学生。
  • 修改学生信息:更新指定学生的姓名信息,在缓存和数据库中都进行更新。
  • 打印学生列表:从数据库中检索所有学生信息,并在客户端打印。

技术点:

除了Java RMI技术实现网络间的远程方法调用外,还具备以下优势:

1. 查询速度提高

  • 索引:在操作储存在缓存中的数据部分时,考虑使用更高效的数据结构,如HashMap,以学生ID作为键,这样查询的时间复杂度可以从O(n)降低到接近O(1)。

2. 数据模型和封装

  • 封装Student类的属性:最初Student类的studentIDstudentName属性是公开的(public)。后面改进为使用私有属性和公开的getter/setter方法来增强封装性。

3. 异常处理

  • 细化异常处理:最初的实现中,异常被捕获后仅仅打印堆栈跟踪。所以我根据不同的异常类型做更细化的处理,例如,对用户输入错误或网络问题给出更友好的提示信息。

4. queryByName方法的性能

  • 使用并行流:将数据结构已经从ArrayList切换到了HashMap后,考虑到对于queryByName这样的操作,仍然需要遍历整个集合。为了提高处理大量数据的效率,我使用了Java 8的parallelStream()来并行处理这些数据,将数据分片后,同时查询多个片段,从而减少总体查询时间。

5. 增加日志记录

  • 使用日志框架:在关键操作处(如添加、删除学生信息)增加日志记录。这不仅有助于开发和调试,而且对于生产环境中的问题排查也非常有用。我使用了SLF4J的日志框架。

6. 安全性考虑

  • 参数校验:在处理客户端请求时,对输入参数进行校验,比如检查studentIDstudentName是否为null或空字符串,以避免潜在的错误或安全问题。

7. 数据持久化

  • 持久化学生数据:数据一般保存在缓存中,我将学生信息持久化到MySQL数据库中,这样即使程序重启,学生信息也不会丢失。

界面展示:

1.启动成功的服务端:

image-20240412151821033

2.启动成功的客户端:

image-20240412151902479

3.为了演示操作,我先把数据库里的东西都删掉:

比如说我的数据库原来里面是:

image-20240412152101431

我在客户端进行如下操作:

image-20240412152211536

然后我的服务端可见我的删除日志:

image-20240412152324175

然后我刷新数据库:

image-20240412152415199

可见数据已经被全部删除;

4.接下来,我们从第一条开始尝试功能:

image-20240412152816644

5.查看我们的日志:

image-20240412152855409

6.我们可以先看缓存里面添加成功没有,输入6,打印学生列表:

image-20240412153229876

7.接下来,我们刷新数据库:

image-20240412153248837

8.然后,我们试试我们的查询功能:

(1)通过ID查询:

image-20240412153528921

我们查看日志:

image-20240412153554449

显示的是通过缓存查到了对应学生,这是因为我设置了先从缓存查,再从数据库查,从缓存查到后就不再查询数据库。

我们再试个查不到的:

image-20240412154146654

下面是我们的日志:

image-20240412154209396

(2)通过姓名查询:

image-20240412153835450

下面是我们的日志:

image-20240412153857850

我们再试个查不到的:

image-20240412154025634

下面是我们的日志:

image-20240412154101778

9.我们试试删除功能:

image-20240412154346768

下面是我们的日志:

image-20240412154410099

我们来展示一下我们的学生列表:

image-20240412154446966

可以看到ID==3的红太狼被删了,接下来,我们可以看看数据库:

image-20240412154546857

10.最后,我们试一下改名功能:

image-20240412154744770

我们日志也有正确呈现:

image-20240412154813740

我们看一下数据库:

image-20240412154846814

改名成功!!!

11.我们试试加入ID相同的学生:

image-20240412155017781

我们的日志也有回应:

image-20240412155054544

数据库也没有添加成功:

image-20240412155116245

另外,补充一下我们的数据库设置:

image-20240412155249059

12.我们试试把学生都删了再继续删:

image-20240412155455665

删后的数据库:

image-20240412155736265

可见程序可以准确识别错误:

image-20240412155538102

13.我们尝试退出程序:

image-20240412155626431

14.我们试试在数据库直接加一个学生:

image-20240412155959093

可以发现查询ID==1可以查到他!

image-20240412160121364

我们查看日志:

image-20240412160202406

演示完毕!成功!!!

五、系统的展望

1.其他序列化协议:因为JDK 自带的序列化效率低并且有安全漏洞。 所以,还要考虑使用哪种序列化协议,比较常用的有 hession2、kyro、protostuff;

2.多个服务端:现在只有一个服务端,后面拓展到多个后调用服务时,从很多服务地址中根据相应的负载均衡算法选取服务地址;

3.逐步引入服务专用数据库:当条件允许时,为每个服务配置独立的数据库;

4.容器化部署:当使用了微服务架构后,我们将一个原本完整的系统,按照业务逻辑拆分成一个个可独立运行的子系统。为了降低系统间的耦合度,我们希望这些子系统能够运行在独立的环境中,这些环境之间能够相互隔离。若使用虚拟机来实现运行环境的相互隔离的话成本较高,虚拟机会消耗较多的计算机硬件/软件资源。所以这里建议使用Docker,Docker不仅能够实现运行环境的隔离,而且能极大程度的节约计算机资源。

5.自动化构建:当我们使用微服务架构后,随着业务的逐渐发展,系统之间的依赖关系会日益复杂,而且各个模块的构建顺序都有所讲究。这里可以使用Jenkins,我们只需在Jenkins中配置好代码仓库、各个模块的构建顺序和构建命令,在以后的构建中,只需要点击“立即构建”按钮,Jenkins就会自动到你的代码仓库中拉取最新的代码,然后根据你事先配置的构建命令进行构建,最后发布到指定的容器中运行。

6.安全性与数据管理:考虑引入更复杂的错误处理机制、增强的安全性措施(如使用SSL/TLS加密RMI通信),以及更高级的数据管理策略(如引入ORM框架如Hibernate)。

六、运行老师的文件的收获

首先,我的环境是JDK8

我对代码及其结构均进行了调整:

刚好可以在下面这张图里基本都展示出来:

image-20240406221400480

然后,有用的步骤是:

1.分别到server/client所在目录下cmd完成.java文件的编译: javac -encoding utf-8 *.java,然后在targe目录下就会出现一些.class文件(必须的,不然编译时找不到文件;注意修改.java内容后要重新编译);

2.在客户端与服务器端.class端的大目录里面建立一个jar包:

jar cvf shared.jar rmiserver\MyCalc.class
image-20240406221950283

3.先清理占用1099端口的进程:

(1)也是到客户端与服务器端.class端的大目录,cmd一下;

(2)清理进程

netstat   -ano|findstr 1099

image-20240406222234724

tasklist|findstr 99232

(这部可有可无)

taskkill /pid 99232 -t -f

image-20240406222518371

(3)引入注册中心

start /b java -classpath "D:\Grade 3\分布式计算\_0406tri\rmi_teacher\target\classes;D:\Grade 3\分布式计算\_0406tri\rmi_teacher\target\classes\shared.jar" sun.rmi.registry.RegistryImpl 1099

image-20240406222804901

4.在IDEA上先运行服务端,再运行客户端~

大功告成!!!

正确结果:

服务端先:

image-20240406223249966

客户端后:

image-20240406223324926

然后返回服务端:

image-20240406223408934

整体界面展示:

image-20240406223558625

错误回忆

1.这个的造成一方面是因为我现在的JDK版本会限制接口MyCalc必须是唯一的,另一方面是因为我的代码当时没有在客户端把这个接口删掉+申明用服务端编译好的MyCalc,还有一方面是运行目录也不对;

image-20240406223707759

2.修改代码后,原来对应的.class文件最好删掉,重新编译!

另外补充

命令行运行方式(前提:编译好class文件+进入正确目录):

服务器端:

java -classpath "target\classes;target\classes\shared.jar" rmiserver.MyRMIServer

image-20240406224513155

客户端:

java -classpath "target\classes;target\classes\shared.jar" rmiclient.MyRMIClient

image-20240406224849242

(那时代码还没改hhh,所以有一丢丢问题,代码改好就正确啦!)

PS:错误的根本原因是因为客户端尝试将远程对象转换为 rmiclient.MyCalc,而实际上它应该转换为远程接口 rmiserver.MyCalc。即使两个接口的源代码完全相同,如果它们的包路径不同,Java 也会将它们视为完全不同的类型。

七、参考链接

我手写了一个RPC框架。成功帮助读者斩获字节、阿里等大厂offer。 - 知乎 (zhihu.com) 基于 Netty+Kyro+Zookeeper 实现的 RPC 框架(非常优秀!)

分布式计算_grpc_书籍信息管理系统(不配叫系统)_分布式计算实现书籍管理-CSDN博客

【JAVA】基于RMI的书籍信息管理系统_bool add(book b) 添加一个书籍对象。 book querybyid(int book-CSDN博客

CourseDesigned: 课程设计 - Gitee.com

Java的RMI远程方法调用实现和应用-阿里云开发者社区 (aliyun.com)

Distributed-Computing/exp3-rpcsystem at main · silence-tang/Distributed-Computing (github.com)

八、未来的学习方向

1.(微服务讲得特别浅显易懂,我有时间一定实现一下)

如何零基础搭建一套微服务框架(Spring Boot + Dubbo + Docker + Jenkins) - 知乎 (zhihu.com)

2.注册中心:Nacos 网关:Gateway 后端基础框架:ssm 前端:Vue + SPA Axios(request.js)

SpringCloud微服务之学生管理_基于微服务的学生管理系统-CSDN博客

3.基于 MySQL + springcloud 微服务框架的作业管理系统_基于微服务的作业管理系统-CSDN博客

4.文章详细,可视化良好

在idea中搭建微服务项目(22版),详细教程_idea创建微服务项目-CSDN博客

5.作为补充吧

JDK 1.8 IEDA 2019(推荐)Maven 3.5.x(推荐) nacos 1.1.3(推荐)

手把手教你搭建第一个微服务(框架) - 知乎 (zhihu.com)

标签:服务,项目,数据库,编程,分布式计算,java,RMI,远程,客户端
From: https://www.cnblogs.com/HYLOVEYOURSELF/p/18211787

相关文章

  • 网盘项目注册以及登录功能的实验
    注册功能的实现思路:用户输入用户名,手机号,密码作为登录所用的数据,后端检验用户名,密码,手机号的合法性,并且后端可以通过手机号唯一的特性来验证该手机号是否已被注册过,若被注册过,则返回错误信息,否则,使用UUID生成盐值来对密码进行加盐处理,然后再使用md5对密码进行加密(加盐值的好处......
  • WinSock 的多线程编程
    目录概述Winsock为什么需要多线程阻塞模式和非阻塞模式单线程和多线程的优缺点Win32系统下的多进程多线程机制进程和线程线程创建线程同步线程通信  VC++对多线程网络编程的支持MFC中的多线程支持ATL中的多线程支持多线程FTP客户端实例头文件包含线......
  • Golang初学:项目目录结构,project-layout 项目
    goversiongo1.22.1windows/amd64Windows11+amd64x86_64x86_64GNU/Linux--- 序章golang项目的代码要怎么组织?怎么放比较简洁易读?看下面这个项目就晓得了。 project-layouthttps://github.com/golang-standards/project-layout注,有时访问失败。特写文记录。......
  • 使用 Coolify 极简运维你的项目和数据库
    使用Coolify极简运维你的项目和数据库引言本文将介绍一个名为Coolify的开源软件,基于Docker提供了托管网站、数据库等服务的功能,功能丰富强大,操作简单。官方自称是Heroku/Netlify/Vercel的开源替代。我在试用了以后立即决定抛弃之前nginxproxymanager+doc......
  • cmakelist 编译源码生成动态静态库并链接到项目
    当我们使用vscode编译c++代码时,需要加入第三方代码,而它没有库时。这时候我们就需要自己写一个Cmakelist编译成库,然后链接到自己的项目上。下面我以qt的qtpropertybrowser类为例,这个类并不在qt的标准库中,若是在qtcreator中使用,需要在pro引入该文件路径(qt安装目录里-\Qt\5......
  • 实验5 C语言指针应用编程
    1.实验任务1task1_1.c1#include<stdio.h>2#defineN534voidinput(intx[],intn);5voidoutput(intx[],intn);6voidfind_min_max(intx[],intn,int*pmin,int*pmax);78intmain()9{10inta[N];11intmin,max;1213......
  • SpringBoot项目 制作邮箱 验证码发送(163)
     1.邮箱授权码获取邮箱--> 设置-->POP3/SMTP/IMAP-->开启服务(POP3/SMTP服务)-->授权码 开启并获取后将授权码保存下来。2.项目导入依赖<!--邮箱--><dependency><groupId>org.springframework.boot</groupId>......
  • GitHub上新!14个Python项目详细教程(附完整代码)
    Python作为程序员的宠儿,越来越得到人们的关注,使用Python进行应用程序开发的也越来越多。今天给小伙伴们分享的这份项目教程完整代码已上传至GitHub,你可以选择跟着这份教程一段一段的手敲出来这几个项目,也可以直接从GitHub上copy下来。限于文章篇幅原因,只能以截图的形式展示出......
  • 商会招商项目系统功能定制开发介绍
    商会招商项目系统功能定制开发介绍商会招商项目系统,是一个集PC和移动端功能于一体的解决方案,线上线下进行服务,围绕活动报名、在线课程、项目大厅、线下签到、会员系统等。为商会提供了更加便捷高效的管理方式,提升了商会活动和项目的组织、推广和运营能力。功能特性为你介绍......
  • 在代码中集成sentry项目
    创建团队和项目选择项目进入设置选择错误跟踪选择对应的项目类型根据配置和测试信息配置sentry项目到代码中并测试......