手写一个简单的RPC框架

文章目录

    • 1、初识Dubbo
    • 2、RPC是什么
    • 3、多模块设计
      • 3.1、服务提供者
      • 3.2、注册中心实现
      • 3.3、HTTP协议
        • 内嵌tomcat启动
      • 3.4、服务消费者
        • 测试
        • 优化

1、初识Dubbo

Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和Spring框架无缝集成。现在已成为Apache的开源项目。

2、RPC是什么

RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外的为这个交互作用编程,如果涉及的软件采用面向对象编程(java),那么远程过程调用亦可称作远程调用远程方法调用。只要支持网络传输的协议就是RPC协议,RPC是一种框架。

3、多模块设计

按照官网架构图,模块内容设计如下

  • 服务提供者:提供API,启动的时候要注册服务

  • 服务消费者:从注册中心获取服务,调用子服务

  • 注册中心:保存服务配置

  • RPC协议:

    • 基于Tomcat的HttpProtocol
    • 基于Netty的DubboProtocol

    由于模块之间还要引用jar包,于是在手写实现时以包的形式代表各个模块

3.1、服务提供者

定义一个服务接口

public interface HelloService {public void sayHello(String username);
}
-------------------------------------------------------------
public class HelloServiceImpl implements HelloService {public void sayHello(String username) {System.out.println("Hello:"+username);}
}

注册服务,启动tomcat

这里用到了策略模式和简单工厂模式,提供了两种注册服务的策略

    public static void main(String[] args) {// 注册服务// 远程注册URL url = new URL("localhost", 8080);RemoteMapRegister.regist(HelloService.class.getName(), url);// 服务:实现类// 本地注册LocalRegister.regist(HelloService.class.getName(), HelloServiceImpl.class);// 协议工厂Protocol protocol = ProtocolFactory.getProtocol();protocol.start(url);}

3.2、注册中心实现

服务注册形式

Map<interfaceName, List<URL>>

两个数据bean

  • Invocation

    要实现Serializable,在服务消费端设值后序列化成对象流传输,然后在服务提供端转为对象,获取接口名,从注册中心获取实现类,从而调用方法。

    @Data
    @AllArgsConstructor
    public class Invocation implements Serializable {private String interfaceName;private String methodName;private Object[] params;private Class[] paramType;
    }
    
  • URL

    @Data
    @AllArgsConstructor
    public class URL implements Serializable {private String hostname;private Integer port;
    }
    

具体实现

  • 本地注册

    public class LocalRegister {private static Map<String, Class> map = new HashMap<>();/*** 注册服务(暴露接口)* @param interfaceName* @param implClass*/public static void regist(String interfaceName, Class implClass) {map.put(interfaceName, implClass);}/*** 从注册中心获取实现类(发现服务)* @param interfaceName* @return*/public static Class get(String interfaceName) {return map.get(interfaceName);}
    }
    
  • 远程注册

    public class RemoteMapRegister {private static Map<String, List<URL>> REGISTER = new HashMap<>();public static void regist(String interfaceName, URL url){List<URL> list = REGISTER.get(interfaceName);if (list == null) {list = new ArrayList<>();}list.add(url);REGISTER.put(interfaceName, list);saveFile();}public static List<URL> get(String interfaceName) {REGISTER = getFile();List<URL> list = REGISTER.get(interfaceName);return list;}private static void saveFile() {try {FileOutputStream fileOutputStream = new FileOutputStream("./temp.txt");ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);objectOutputStream.writeObject(REGISTER);} catch (IOException e) {e.printStackTrace();}}private static Map<String, List<URL>> getFile() {try {FileInputStream fileInputStream = new FileInputStream("./temp.txt");ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);return (Map<String, List<URL>>) objectInputStream.readObject();} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}return null;}
    }

3.3、HTTP协议

内嵌tomcat启动

引入内嵌tomcat依赖

<dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-core</artifactId><version>9.0.12</version>
</dependency>

tomcat结构 server.xml

<Server port="8005" shutdown="SHUTDOWN"><Service name="Catalina"><Connector port="8080" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443" URIEncoding="UTF-8"/><Engine name="Catalina" defaultHost="localhost"><Host name="localhost"  appBase="webapps"unpackWARs="true" autoDeploy="true"><Context path="" doBase="WORKDIR" reloadable="true"/></Host></Engine></Service>
</Server>

是不是很熟悉,根据这个xml结构构建一个tomcat启动类

public class HttpServer {public void start(String hostname,Integer port){// 实例一个tomcatTomcat tomcat = new Tomcat();// 构建serverServer server = tomcat.getServer();/*** 在getServer的时候,就在方法内部执行了* Service service = new StandardService();* service.setName("Tomcat");* server.addService(service);*/// 获取serviceService service = server.findService("Tomcat");// 构建ConnectorConnector connector = new Connector();connector.setPort(port);connector.setURIEncoding("UTF-8");// 构建EngineEngine engine = new StandardEngine();engine.setDefaultHost(hostname);// 构建HostHost host = new StandardHost();host.setName(hostname);// 构建ContextString contextPath = "";Context context = new StandardContext();context.setPath(contextPath);context.addLifecycleListener(new Tomcat.FixContextListener());// 生命周期监听器// 然后按照server.xml,一层层把子节点添加到父节点host.addChild(context);engine.addChild(host);service.setContainer(engine);service.addConnector(connector);// service在getServer时就被添加到server节点了// tomcat是一个servlet,设置路径与映射tomcat.addServlet(contextPath,"dispatcher",new DispatcherServlet());context.addServletMappingDecoded("/*","dispatcher");try {tomcat.start();// 启动tomcattomcat.getServer().await();// 接受请求}catch (LifecycleException e){e.printStackTrace();}}}

HttpServerHandler 所有http请求交给HttpServerHandler处理,即服务消费端的远程调用

public class DispatcherServlet extends HttpServlet{@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 方便后期在此拓展服务new HttpServerHandler().handler(req, resp);}
}public class HttpServerHandler {public void handler(HttpServletRequest req, HttpServletResponse resp){try{// Http请求流转为对象Invocation invocation = (Invocation) new ObjectInputStream(req.getInputStream()).readObject();String interfaceName = invocation.getInterfaceName();// 寻找注册中心的实现类,通过反射执行方法Class implClass = LocalRegister.get(interfaceName);Method method = implClass.getMethod(invocation.getMethodName(), invocation.getParamType());String result = (String) method.invoke(implClass.newInstance(), invocation.getParams());// 将结果返回System.out.println("tomcat:" + result);IOUtils.write(result, resp.getOutputStream());}catch (Exception e){e.printStackTrace();}}
}

注意: URL一定要重写equals与hashCode方法,否则Register.get(new URL("localhost",8080),invocation.getInterfaceName());时为null。

ProtocolFactory 协议工厂

public class ProtocolFactory {public static Protocol getProtocol() {// 简单工厂模式String name = System.getProperty("protocolName");if (name == null || name.equals("")) name = "http";switch (name) {case "http":return new HttpProtocol();case "dubbo":return new DubboProtocol();default:break;}return new HttpProtocol();}
}

HttpProtocol协议实现类

public class HttpProtocol implements Protocol {@Overridepublic void start(URL url) {HttpServer httpServer = new HttpServer();httpServer.start(url.getHostname(), url.getPort());}@Overridepublic String send(URL url, Invocation invocation) {HttpClient httpClient = new HttpClient();return httpClient.send(url.getHostname(), url.getPort(),invocation);}
}

3.4、服务消费者

public class consumer {public static void main(String[] args) {HelloService helloService = ProxyFactory.getProxy(HelloService.class);String result = helloService.sayHello("国王");System.out.println(result);}
}

HttpClient

public class HttpClient {public String send(String hostname, Integer port, Invocation invocation) {try {// 进行http连接URL url = new URL("http", hostname, port, "/");HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();httpURLConnection.setRequestMethod("POST");httpURLConnection.setDoOutput(true);OutputStream outputStream = httpURLConnection.getOutputStream();ObjectOutputStream oos = new ObjectOutputStream(outputStream);oos.writeObject(invocation);oos.flush();oos.close();InputStream inputStream = httpURLConnection.getInputStream();String result = IOUtils.toString(inputStream);return result;} catch (MalformedURLException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}
}

HttpProtocol

public class HttpProtocol implements Protocol {@Overridepublic void start(URL url) {HttpServer httpServer = new HttpServer();httpServer.start(url.getHostname(), url.getPort());}@Overridepublic String send(URL url, Invocation invocation) {HttpClient httpClient = new HttpClient();return httpClient.send(url.getHostname(), url.getPort(),invocation);}
}
测试

先启动服务提供者

再启动服务消费者

优化
  • 动态代理ProxyFactory

dubbo是直接引入接口jar包,调用接口方法就可以获取结果,于是使用到了动态代理返回一个代理对象。

public class ProxyFactory<T> {@SuppressWarnings("unchecked")public static <T> T getProxy(final Class interfaceClass) {return (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String mock = System.getProperty("mock");if (mock != null && mock.startsWith("return:")) {String result = mock.replace("return:", "");return result;}Invocation invocation = new Invocation(interfaceClass.getName(), method.getName(), args, method.getParameterTypes());
//                List<URL> urlList = ZookeeperRegister.get(interfaceClass.getName());List<URL> urlList = RemoteMapRegister.get(interfaceClass.getName());URL url = LoadBalance.random(urlList);Protocol protocol = ProtocolFactory.getProtocol();String result = protocol.send(url, invocation);return result;}});}
}
  • 以文本形式实现注册中心

因为消费端与服务端是两个进程,消费端是获取不到服务端的REGISTER的,所以需要在服务端注册时将URL写入文本,然后在消费端根据interfaceName随机调度已发布服务的服务器地址。

public class RemoteMapRegister {private static Map<String, List<URL>> REGISTER = new HashMap<>();public static void regist(String interfaceName, URL url){List<URL> list = REGISTER.get(interfaceName);if (list == null) {list = new ArrayList<>();}list.add(url);REGISTER.put(interfaceName, list);saveFile();}public static List<URL> get(String interfaceName) {REGISTER = getFile();List<URL> list = REGISTER.get(interfaceName);return list;}private static void saveFile() {try {FileOutputStream fileOutputStream = new FileOutputStream("./temp.txt");ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);objectOutputStream.writeObject(REGISTER);} catch (IOException e) {e.printStackTrace();}}private static Map<String, List<URL>> getFile() {try {FileInputStream fileInputStream = new FileInputStream("./temp.txt");ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);return (Map<String, List<URL>>) objectInputStream.readObject();} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}return null;}
}

最后贴一个项目地址:

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

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

相关文章

MySQL经典50题

目录 一、数据表介绍 二、练习题 1. 查询" 01 "课程比" 02 "课程成绩高的学生的信息及课程分数 2. 查询同时存在" 01 "课程和" 02 "课程的情况 3. 查询存在" 01 "课程但可能不存在" 02 "课程的情况…

大寒---每年的最后一个节气

# 大寒节气 # 大寒&#xff0c;是二十四节气中的最后一个节气。斗指丑&#xff1b;太阳黄经达300&#xff1b;于每年公历1月20日左右交节。大寒同小寒一样&#xff0c;都是表示天气寒冷程度的节气&#xff0c;大寒是天气寒冷到极致的意思。大寒节气处在三九、四九时段&#xf…

书生·浦语大模型实战营第四次课堂笔记

先来看看参考作业 哈哈到这才想起来写笔记 倒回去看发现要求将不要葱姜蒜换成自己的名字和昵称&#xff01; 好好好我就是不配玩&#xff08;换成管理员也不行&#xff01;&#xff09; 诶怎么能进这个环境&#xff1f;要进双系统ubuntu&#xff1f; 现在看视频发现原来是…

【 使用路由建立多视图单页应用详细介绍】

使用路由建立多视图单页应用详细介绍 1. 多视图1.1 引入依赖库1.2 创建自定义组件 2. React&#xff08;使用React Router&#xff09;3. Angular&#xff08;使用Angular Router&#xff09;4. Vue&#xff08;使用Vue Router&#xff09; 1. 多视图 构建多视图的单页应用程序…

数据结构学习之顺序表(各种操作合集)

顺序表&#xff08;各种操作合集&#xff09; 顺序表的两种创建方式&#xff1a; 方式1&#xff1a;根据函数的返回值创建 通过返回值返回所申请的内存空间的首地址&#xff1b;示例代码&#xff1a; list_t *create_seq_list_1(){list_t *p (list_t *)malloc(sizeof(list…

009 Linux_文件系统 | 软硬链接

前言 本文将会向你介绍文件系统与软硬链接 文章重点 本文将会先向你介绍文件是如何在磁盘上进行管理的&#xff0c;关于文件的管理将会从管理属性和管理内容两方面来谈&#xff0c;最后会向你介绍软硬链接的概念 文件在磁盘中的管理 首先&#xff0c;假设一个磁盘200GB&#…

大健康中医领域的客户画像标签数据(一)

大健康中医领域的客户画像标签数据 目前国内市场上针对大健康领域的客户数据如何收集&#xff0c;如何系统分析客户在大健康中医领域的一体化链路数据。 分步骤 1&#xff0c;健康领域的客户标签 基础标签: - 年龄:老年、中年、青年 - 性别:男性、女性 - 客户类型:个人、医…

Linux中的共享内存

定义&#xff1a; 共享内存允许两个或者多个进程共享物理内存的同一块区域&#xff08;通常被称为段&#xff09;。由于一个共享内存段会称为一个进程用户空间的一部分&#xff0c;因此这种 IPC 机制无需内核介入。所有需要做的就是让一个进程将数 据复制进共享内存中&#xff…

acwing讲解篇之94. 递归实现排列型枚举

文章目录 题目描述题解思路题解代码 题目描述 题解思路 定义递归深度deep&#xff0c;数字使用情况used&#xff0c;选择的数字顺序path 进行递归 终止条件为递归深度达到n层时&#xff0c;打印path&#xff0c;然后返回 深度加一 遍历未使用的数字&#xff0c;选择数字&am…

【rust/bevy】使用points构造ConvexMesh

目录 说在前面问题提出Rapier具体实现参考 说在前面 操作系统&#xff1a;win11rust版本&#xff1a;rustc 1.77.0-nightlybevy版本&#xff1a;0.12 问题提出 在three.js中&#xff0c;可以通过使用ConvexGeometry从给定的三维点集合生成凸包(Convex Hull) import { ConvexGeo…

【51单片机Keil+Proteus8.9】温室盆栽灌溉系统

实验五 实验名称 温室盆栽灌溉系统 软件设计&#xff1a; 1. 定义对应的引脚和端口的别名。 2. 编写延时函数&#xff0c;用于控制程序的执行速度。 3. 编写LCD控制函数&#xff0c;包括发送命令和发送数据两种操作。 4. 编写显示函数&#xff0c;用于在LCD上显示字符串…

无人机打击激光器

激光器的应用非常广泛&#xff0c;涵盖了多个领域。以下是一些主要的激光器应用&#xff1a; 医疗领域&#xff1a;激光器在医疗行业中有着重要应用&#xff0c;比如用于激光手术&#xff08;如眼科手术&#xff09;、皮肤治疗、牙科治疗、肿瘤治疗等。 工业制造&#xff1a;在…

html form中的input有哪些类型?各是做什么处理使用的

在HTML表单中&#xff0c;input元素有多种类型&#xff0c;主要包括以下几种&#xff1a; button&#xff1a;用于定义可点击的按钮。 checkbox&#xff1a;用于定义复选框&#xff0c;用户可以选择多个选项。 file&#xff1a;用于定义文件输入字段&#xff0c;用户可以从本地…

无忧秘书智脑:轻松驾驭“看图说话”功能,职场沟通更高效

在现代职场中&#xff0c;有效的沟通是提升工作效率的关键。然而&#xff0c;有时候我们面对一张图片或图表&#xff0c;却难以用言语准确表达其中的信息。这时&#xff0c;无忧秘书智脑的“看图说话”功能就派上了用场。这篇文章将手把手教你如何使用这一功能&#xff0c;以及…

在使用go语言开发的时候,程序启动后如何获取程序pid

在Go语言中&#xff0c;标准库并没有直接提供获取进程ID&#xff08;PID&#xff09;的函数。通常&#xff0c;你可以使用os包和syscall包来调用底层的操作系统函数来获取PID。 以下是一个获取程序PID的示例代码&#xff1a; package mainimport ("fmt""os&qu…

【MATLAB源码-第119期】基于matlab的GMSK系统1bit差分解调误码率曲线仿真,输出各个节点的波形以及功率谱。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 GMSK&#xff08;高斯最小频移键控&#xff09;是一种数字调制技术&#xff0c;广泛应用于移动通信&#xff0c;例如GSM网络。它是一种连续相位调频制式&#xff0c;通过改变载波的相位来传输数据。GMSK的关键特点是其频谱的…

springboot(ssm仓库管理系统 wms出入库管理系统Java系统

springboot(ssm仓库管理系统 wms出入库管理系统Java系统 开发语言&#xff1a;Java 框架&#xff1a;ssm/springboot vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysql 5.7&#xff08;或8.0&#xff09; 数…

Nginx安装配置

目录 准备工作 安装Nginx及相关组件 a、yum安装: b、tar安装: c、也可以yum安装pcre、zlib、openssl&#xff0c;tar安装nginx 启动Nginx 简单配置Nginx Nginx配置静态web服务器 Nginx实现负载均衡(不要使用刷新按钮,在地址栏回车) 1.负载均衡策略 2.配置Nginx的负载均衡与分发…

使用 Zabbix + Grafana 搭建服务器监控系统

搭建 Linux 服务器监控的目的是自己有一台阿里云服务器内存是 2g 的 , 多开一些软件就会把内存和 CPU 使用率弄的很高&#xff0c;最终导致服务器卡死。 所以基于这个痛点&#xff0c;想知道当前的 CPU 和内存是多少。阿里云 ECS 控制台中也提供对服务器的监控 , 但是为了学习…

TypeScript 函数教程 - 深入理解和使用 TypeScript

🚀 欢迎来到我的专栏!专注于Vue3的实战总结和开发实践分享,让你轻松驾驭Vue3的奇妙世界! 🌈✨在这里,我将为你呈现最新的Vue3技术趋势,分享独家实用教程,并为你解析开发中的难题。让我们一起深入Vue3的魅力,助力你成为Vue大师! 👨‍💻💡不再徘徊,快来关注…