手写RPC框架(手写dubbo框架)

提示:dubbo底层实现,手写dubbo框架。手写rpc框架、用servlet实现dubbo、用servlet实现rpc框架

文章目录

  • 前言
  • 一、实现步骤描述
    • 1.1、provider的原理
    • 1.2、consumer的原理:
  • 二、代码实现
    • 2.1、api项目
      • 2.1.1
      • 2.1.2
      • 2.1.3
    • 2.2、provider项目
      • 2.2.1、provider项目的调用流程
      • 2.2.2、代码
        • 2.2.2.1、
        • 2.2.2.2、
        • 2.2.2.3、
        • 2.2.2.4、
    • 2.3、consumer
      • 2.3.1、consumer项目的流程
      • 2.3.2、代码
        • 2.3.2.1、
        • 2.3.2.2、
        • 2.3.2.3、
    • 2.4、测试
  • 总结


前言

上次开会,同时们讨论了一个dubbo框架的事务问题。才猛然意识到,自己之前手写过dubbo框架,结果都还给老师了。趁此机会,回忆并记录一下,方便自己日后查阅。本人水平有限,如有误导,欢迎斧正,一起学习,共同进步!


一、实现步骤描述

手写rpc原理时,一共有三个项目,分别是api项目、consumer项目、provider项目。其中consumer和provider都依赖了api。其中的
api项目中:请求的参数定义、接口的定义 等一些公共规范。
provider服务提供方:接口的具体实现(具体的功能)、socket的服务器端
consumer服务消费方:准备了请求参数、socket的客户端。

1.1、provider的原理

项目启动起来,创建一个socket的服务端,不断的监听某一个端口,等待着socket的客户端的链接,然后通过socket.getInputSream来拿到socket传递过来的对象,拿到对象以后,就是具体实现的参数(比如说UserServiceImpl的addUser的方法的请求参数),通过这个参数,去调用这个方法,调用完成以后,拿到一个返回结果。将这个返回结果以socket.getOutputSream的writeObject方法返回给请求端。

1.2、consumer的原理:

项目通过动态代理来生成一个目标(UserService)的代理对象(此次是jdk动态代理)。因为是jdk动态代理,所以是实现了InvocationHandler接口,具体的执行的invoke方法,在invoke方法中,去发起了socket的调用(指定了socket的host、port,参数,然后socket服务器那边就会监听到)。拿到这个代理对象以后,调用这个对象的addUser方法,并拿到响应结果。因为这个动态代理对象有这个目标对象的全部方法,所以可以直接调用。

二、代码实现

2.1、api项目

2.1.1

这个是UserService。也就是定义的规范

/*** @author: ZhengTianLiang* @date: 2021/07/11  17:16* @desc: 服务的标准(对外暴露的api规范)*/
public interface UserService {public UserDTO addUser(UserDTO userDTO);
}

2.1.2

这个是UserDTO 也就是addUser方法的请求参数

/*** @author: ZhengTianLiang* @date: 2021/07/11  17:16* @desc: 是在网络中传输的dto,*/@Data
public class UserDTO implements Serializable {/*** 为了保证系列化和反序列化的安全性,可以加一个id*/private static final long serialVersionUID = -7085411221862236858L;private String name;private String age;private String userId;
}

2.1.3

这个是RPCCommonReqDTO,也就是网络传输中的对象,里面有一个Object类型的参数,这个里面可以放上面的UserDTO

/*** @author: ZhengTianLiang* @date: 2021/07/12  22:34* @desc: 定义的统一的在网络中数据传输的规则*/@Data
public class RPCCommonReqDTO implements Serializable {private static final long serialVersionUID = 666960806401175269L;// 方法名称private String methodName;// 类的权限定路径private String classpath;// 方法的参数private Object[] args;
}

2.2、provider项目

2.2.1、provider项目的调用流程

项目跑起来,调用startup方法,监听一个端口。监听某一个端口时它会等着某个客户端的链接。然后来一个socket对象,就创建一个线程去处理这个socket对象,所以具体的操作这个socket的方法,就需要写在runnable接口的run方法里面。run方法中,通过构造器的方式,将socket注入进来了,然后通过socket获取到输入流对象,然后拿到传输过来的对象,然后将这个对象,交给ServiceDispatch对象进行服务的分发,然后将ServiceDispatch对象分发完以后,返回的对象,通过网络(socket,OutputStream)返回给请求端

2.2.2、代码

2.2.2.1、

这个是serviceImpl,是上面的规范的具体实现

/*** @author: ZhengTianLiang* @date: 2021/07/11  17:28* @desc: 这个是,api项目中的。UserService 接口的实现类*      就是说,rpc-api项目,只是单纯的定义规范,*      rpc-provider项目,是具体实现(提供服务的)*      prc-consumer项目,是服务的消费方*/public class UserServiceImpl implements UserService {/*** @author: ZhengTianLiang* @date: 2021/07/11  17:29* @desc: 服务的具体实现*/public UserDTO addUser(UserDTO userDTO) {System.out.println("接收到的dto:" + userDTO);userDTO.setUserId(new Random().nextInt(1000000) + "");System.out.println("返回的dto:" + userDTO);return userDTO;}
}
2.2.2.2、

dispatch,进行服务的分发,有点类似于nginx的服务转发

package com.csdn.dispatch;import com.csdn.dao.RPCCommonReqDTO;import java.lang.reflect.Method;/*** @author: ZhengTianLiang* @date: 2021/07/11  18:00* @desc: 用来做网络的分发*/
public class ServiceDispatch {/*** @author: ZhengTianLiang* @date: 2021/07/11  18:00* @desc: 做服务的分发*  若是不用反射的话,可能是*      当type=1  --->  调用某个方法(比如说addUser)*      当type=2  --->  调用某个方法(比如说selectUser)*      ...*      你至少要写一个枚举,type=1,2,3.。。  可能还要提供一份文档**      但是若是用了反射,则省下了这些操作,直接提供不同的参数,就调用了不同的方法*/public static Object dispatch(Object reqObj) {// 基于方法的反射的调用,来实现服务的分发。RPCCommonReqDTO rpcCommonReqDTO = (RPCCommonReqDTO) reqObj;Object[] args = rpcCommonReqDTO.getArgs();String classpath = rpcCommonReqDTO.getClasspath();String methodName = rpcCommonReqDTO.getMethodName();// 这个是存放形参的类型的数组的,是因为反射调用方法时,要传递的Class [] types = new Class[args.length];for (int i=0;i<args.length;i++){types[i] = args[i].getClass();}Object respObj = null;try {Class<?> clazz = Class.forName(classpath);Method method = clazz.getDeclaredMethod(methodName, types);// 调用方法  第一个参数是对象,第二个参数是方法的参数值(参数值,不是参数的clazz对象)//  Object obj = clazz.getConstructor().newInstance(); Object result = m2.invoke(obj,10,20);respObj = method.invoke(clazz.newInstance(),args);}catch (Exception e){e.printStackTrace();}return respObj;}
}
2.2.2.3、

netServer,也就是socket的服务端,其中的main,是让socket服务端启动,开始监听socket请求

package com.csdn.net;import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @author: ZhengTianLiang* @date: 2021/07/11  17:34* @desc: 提供网络上的服务的传输  的功能的类*/
public class NetServer {// 总不能说,我这个服务器,只能处理一个线程吧,所以此时需要一个socket对应一个线程,因此我们创建一个线程池对象private static final ExecutorService threadPool = Executors.newFixedThreadPool(100);/*** @author: ZhengTianLiang* @date: 2021/07/11  17:35* @desc: 提供服务的实现。port代表的是端口**  流程是:项目跑起来,调用startup方法,监听一个端口。监听某一个端口时它会等着某个客户端的链接*          然后来一个socket对象,就创建一个线程去处理这个socket对象,*          所以具体的操作这个socket的方法,就需要写在runnable接口的run方法里面。*          run方法中,通过构造器的方式,将socket注入进来了,然后通过socket获取到输入流对象,*          然后拿到传输过来的对象,然后将这个对象,交给ServiceDispatch对象进行服务的分发,*          然后将ServiceDispatch对象分发完以后,返回的对象,通过网络(socket,OutputStream)返回给请求端*/public static void startup(int port) throws IOException {ServerSocket serverSocket = new ServerSocket(port);while (true){// 阻塞的等待着,socket客户端的链接Socket socket = serverSocket.accept();// 让线程池去提交一个任务。submit中可以是  实现了runnable接口的类threadPool.submit(new RPCThreadProcessor(socket));// socket是通过输入、输出流的操作来实现通讯。这样是一种典型的nio(同步阻塞io),nio是会导致线程等待的。
//            socket.getInputStream();
//            socket.getOutputStream();}}/*** @author: ZhengTianLiang* @date: 2021/07/11  17:34* @desc: 写一个启动类,去启动这个socket的服务端,去不断的监听 是否有socket请求进来*/public static void main(String[] args) throws IOException {startup(9999);}}
2.2.2.4、

RPCThreadProcessor,每一个线程的具体的操作

package com.csdn.net;import com.csdn.dispatch.ServiceDispatch;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;/*** @author: ZhengTianLiang* @date: 2021/07/11  17:44* @desc: 线程的处理类(线程池中的线程对象 , 都这样处理)*/public class RPCThreadProcessor implements Runnable {/*** 因为我们这个线程是要操作socket对象的,所以我们为了方便,自己通过构造器方式注入一个socker对象* spring中的注入方式有三种,getter方法、构造器方式、注解方式(@Component/@Repository/@Controller/@Service)*/private Socket socket;public RPCThreadProcessor(Socket socket) {this.socket = socket;}/*** @author: ZhengTianLiang* @date: 2021/07/11  17:54* @desc: 具体的操作应该写在这里,流程的步骤我写在了:NetServer的解释中*/public void run() {// 因为在网络中传输的是java对象(dto对象),所以可以用ObjectInputStream而不是InputStreamObjectInputStream ois = null;ObjectOutputStream oos = null;try {ois = new ObjectInputStream(socket.getInputStream());// 在网络传输的过程中,传输过来的一个对象Object reqObj = ois.readObject();// 将网络中传输过来的对象,交给这个分发器去分发。拿到的返回对象,是要往请求端返回的Object respObj = ServiceDispatch.dispatch(reqObj);oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(respObj);oos.flush();}catch (Exception e){e.printStackTrace();}finally {try {ois.close();oos.close();} catch (IOException e) {e.printStackTrace();}}}
}

2.3、consumer

2.3.1、consumer项目的流程

首先 通过动态工厂的方式,拿到一个目标对象的动态代理对象。然后封装请求参数,发起调用。调用的过程中,最终会跑到实现了InvocationHandler接口的 invoke 方法中,所以我们在invoke方法中调用了 socket的工具类,去发起socket请求。并接收到服务提供方的返回结果,然后拿到这个返回结果。完成一次rpc远程调用。

2.3.2、代码

2.3.2.1、

NetClient,也就是socket客户端的工具类

package com.csdn.net;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;/*** @author: ZhengTianLiang* @date: 2021/07/25  10:24* @desc: 是socket的工具类*/
public class NetClient {/*** @author: ZhengTianLiang* @date: 2021/07/25  10:24* @desc: 是socket的实现,发出具体的网络请求。 host代表目标主机、port代表目标端口,obj是请求入参*/public static Object callRemoteService(String host,int port,Object obj){ObjectInputStream ois = null;ObjectOutputStream oos = null;Object o = null;try {Socket socket = new Socket(host,port);oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(obj);oos.flush();ois = new ObjectInputStream(socket.getInputStream());// 网络传过来的响应对象o = ois.readObject();}catch (Exception e){e.printStackTrace();}finally {try {ois.close();oos.close();} catch (IOException e) {e.printStackTrace();}}return o;}
}
2.3.2.2、

ProxyFactory,也就是生成目标对象(UserServiceImpl)的动态工厂

package com.csdn.proxy;import java.lang.reflect.Proxy;/*** @author: ZhengTianLiang* @date: 2021/07/25  9:56* @desc: 代理工厂对象,用来动态的生成  各种 serviceImpl 对象*/
public class ProxyFactory {/*** @author: ZhengTianLiang* @date: 2021/07/25  9:57* @param: 想创建什么类型的对象,就传入什么类型的clazz对象* @desc: 使用jdk动态代理来生成 传入参数的   动态代理对象*/public static <T> T getProxyInstance(Class<T> interfaceClazz){/*这个里面有三个参数,分别是:classLoader、clazz数组、InvocationHandler当前类的clazzLoader: 类名.class().getClassLoaderclass数组:接口列表(要创建的对象),是一个数组InvocationHandler:实现了InvocationHandler 接口的实现类*/return (T) Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(),new Class[]{interfaceClazz},new RPCInvocationHandler());}
}
2.3.2.3、

InvocationHandler,其中的invoke方法,也就是具体的执行(里面调用了socket工具类去发请求)。

package com.csdn.proxy;import com.csdn.dao.RPCCommonReqDTO;
import com.csdn.net.NetClient;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;/*** @author: ZhengTianLiang* @date: 2021/07/25  10:04* @desc: 是实现了InvocationHandler接口的实现类,jdk动态代理需要实现这个接口。*  其中的 invoke 方法是具体的实现*/
public class RPCInvocationHandler implements InvocationHandler {/*** @author: ZhengTianLiang* @date: 2021/07/25  10:04* @desc: 动态代理可以对 保护目标对象、实例对象进行强化,*      这个invoke 方法内就是强化的具体*      这个invoke方法,进行网络请求的封装(因为你调用目标对象serviceImpl的addUser方法,*      最终是执行的这个invoke方法,所以我们可以把网络请求封装到这个invoke方法中去)*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 封装网络请求RPCCommonReqDTO rpcCommonReqDTO = new RPCCommonReqDTO();rpcCommonReqDTO.setMethodName(method.getName());rpcCommonReqDTO.setArgs(args);// 这个目前是写死的,二期优化会是动态的rpcCommonReqDTO.setClasspath("com.csdn.api.impl.UserServiceImpl");// 调用socket的工具类来发起请求Object responseObj = NetClient.callRemoteService("localhost", 9999, rpcCommonReqDTO);return responseObj;}
}

2.4、测试

测试如下:

package com.csdn;import com.csdn.api.UserService;
import com.csdn.dao.UserDTO;
import com.csdn.proxy.ProxyFactory;/*** @author: ZhengTianLiang* @date: 2021/07/25  9:55* @desc: 测试发起实例(这个项目是一个rpc Consumer项目 , 是测试消费方的代码)*/
public class Test {/*** @author: ZhengTianLiang* @date: 2021/07/25  9:55* @desc: 测试发起消费。通过自己动态代理工厂生成的,服务提供方的serviceImpl(我本身是服务消费方),* 通过这个动态代理生成的 服务提供方的动态代理对象,来具体的执行serviceImpl中的方法,来实现rpc远程调用***  首先 通过动态工厂的方式,拿到一个目标对象的动态代理对象。然后封装请求参数,发起调用。*  调用的过程中,最终会跑到实现了InvocationHandler接口的  invoke  方法中,所以我们在invoke方法*  中调用了 socket的工具类,去发起socket请求。并接收到服务提供方的返回结果,然后拿到这个返回结果。*  完成一次rpc远程调用。*/public static void main(String[] args) {UserService proxyInstance = ProxyFactory.getProxyInstance(UserService.class);// 封装请求参数UserDTO userDTO = new UserDTO();userDTO.setName("userName");userDTO.setAge("18");UserDTO responsePojo = proxyInstance.addUser(userDTO);System.out.println("传入的参数是:" + userDTO);System.out.println("传入的参数是:" + responsePojo);}
}

总结

这只是dubbo框架的一个简单的实现,内部东西还很多。此次只是分享一下他最核心的rpc调用的流程。以后有机会了,会继续分享一些更细致的东西。

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

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

相关文章

椋鸟C语言笔记#33:文件的顺序读写

萌新的学习笔记&#xff0c;写错了恳请斧正。 目录 光标&#xff08;文件位置指示器&#xff09; 文件的顺序读写 fgetc 使用实例 fputc 使用实例 fgets fputs 使用实例 fscanf fprintf fread fwrite 使用实例 光标&#xff08;文件位置指示器&#xff09; 我们…

在程序中链接静态库 和 动态库

9. 链接库 在编写程序的过程中&#xff0c;可能会用到一些系统提供的动态库或者自己制作出的动态库 或者静态库文件&#xff0c;cmake中也为我们提供了相关的加载动态库的命令hehedalinux:~/Linux/loveDBTeacher-v3$ tree . ├── CMakeLists.txt ├── include │ └── …

鸿蒙开发-UI-组件-状态管理

鸿蒙开发-序言 鸿蒙开发-工具 鸿蒙开发-初体验 鸿蒙开发-运行机制 鸿蒙开发-运行机制-Stage模型 鸿蒙开发-UI 鸿蒙开发-UI-组件 文章目录 前言 一、什么是状态管理 二、管理组件拥有的状态 1.组件内状态 State装饰器 2.父子组价单向同步 Prop装饰器 3.父子双向同步 Link装…

Linux下动态库和静态库编译实践

Linux下动态库和静态库编译实践 背景动态库&#xff08;.so文件&#xff09;静态库(.a文件)关于GLIBC 背景 之前写过JNI的文章&#xff0c;在JNI实践过程中&#xff0c;也涉及到对动态库/静态库的一些编译实践&#xff0c;这里统一记录一下。 动态库&#xff08;.so文件&…

RWKV入门

主要参考资料 B站视频《【项目原作解读】RWKV Foundation侯皓文&#xff1a;新型RNN模型RWKV&#xff0c;结合Transformer的并行化训练优势和RNN的高效推理》 RWKV官网: https://www.rwkv.com/ 目录 前言RWKV由来模型架构关键结果劣势未来展望 前言 RNN无法并行化&#xff0c;…

CPU告警不用愁,用C语言编写CPU使用率限制程序

现在云服务已经深入千家万户了&#xff0c;不仅商用&#xff0c;私用也很多。很多云服务厂商也都有配套的服务器安全模块&#xff0c;可以检测网络流量异常、内存占用量和CPU占用率&#xff0c;并且允许人工设置告警阈值。例如&#xff0c;CPU持续大于90%10分钟&#xff0c;那么…

华为机试真题实战应用【赛题代码篇】-选修课(附Java、C++和python代码)

目录 题目描述 思路解析 代码实现 Java JS C++ 代码2 python

ESU毅速丨制造企业需不需要建设增材制造中心?

随着科技的不断发展&#xff0c;增材制造技术已经成为制造行业的新宠。越来越多的企业开始考虑建设增材制造中心&#xff0c;以提高生产效率、降低成本、加速产品创新。但是&#xff0c;对于制造企业来说&#xff0c;是否需要建设增材制造中心呢&#xff1f; 首先&#xff0c;我…

SpringCloud全链路灰度发布

日升时奋斗&#xff0c;日落时自省 目录 1、实现框架 2、负载均衡模块 3、封装负载均衡器 4、网关模块 5、服务模块 5.1、注册为灰度服务实例 5.2、设置负载均衡器 5.3、传递灰度标签 1、实现框架 Spring Cloud全链路灰色发布实现构架&#xff1a; 灰度发布的具体实现…

GEE机器学习——利用最短距离方法进行土地分类和精度评定

最短距离方法 最短距离方法(Minimum Distance)是一种常用的模式识别算法,用于计算样本之间的相似度或距离。该方法通过计算样本之间的欧氏距离或其他距离度量,来确定样本之间的相似程度或差异程度。 最短距离方法的具体步骤如下: 1. 数据准备:收集并准备用于训练的数据…

golang学习-函数

1、匿名函数 没有函数名的函数&#xff0c;格式如下&#xff1a; func(参数)返回值{ 函数体 } func main() {//将匿名函数保存到变量中sum : func(x, y int) int {return x y}fmt.Println(sum(10, 20)) //通过变量调用匿名函数//自执行函数:匿名函数定义完加()直接执行fu…

Java中的继承、方法覆盖和多态

一、继承 关于java语言当中的继承&#xff1a; 1.继承是面向对象三大特征之一&#xff0c;三大特征分别是&#xff1a;封装、继承、多态 2.继承最”基本“的作用是&#xff1a;代码复用。但是继承最”重要“的作用是&#xff1a;有了 继承才有了以后”方法的覆盖“和”多态机制…

利用Monte Carlo进行数值积分(二)

进步空间很大的算法版本 话说去年6月的一个周六&#xff0c;我很无聊地发了一个帖子&#xff0c;写了一个自己感觉有点无聊的帖子。 Matlab多重积分的两种实现【从六重积分到一百重积分】https://withstand.blog.csdn.net/article/details/127564478 这个帖子居然成了我这种懒…

springboot整合 hibernate详解

springboot整合 hibernate详解 大家好&#xff0c;我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天&#xff0c;我们将深入研究Spring Boot与Hibernate整合的技术&#xff0c;解析其…

实现数字的千分位,表示,

重点如下 区分是否是负数区分是否有小数点使用正则表达式\B 是指非单词边界? 是正向查找?! 是负向查找 代表有一个或者多个() 是分组g 代表全局匹配 function splitStr(num) {// 转化成字符串let numStr ${num}let isNegative falseif (numStr.startsWith(-)) {isNegativ…

求解建公路问题

课程设计题目 求解建公路问题 课程设计目的 深入掌握 Prim 和 Kruskal算法在求解实际问题中的应用 问题描述 假设有 n 个村庄,编号从到,现在修建一些道路使任意两个村庄之间可以互相连通。所谓两个村庄 A 和B是连通的,指当且仅当A 和 B之间有一条道路或者存在一个村庄 C 使得…

QT通过QPdfWriter类实现pdf文件生成与输出

一.QPdfWriter类介绍 本文代码工程下载地址&#xff1a; https://download.csdn.net/download/xieliru/88736664?spm1001.2014.3001.5503 QPdfWrite是一个用于创建PDF文件的类&#xff0c;它是Qt库的一部分。它提供了一些方法和功能&#xff0c;使您能够创建和写入PDF文件。…

#Prompt##提示词工程##AIGC##LLM#使用大型预训练语言模型的关键考量

如果有不清楚的地方可以评论区留言&#xff0c;我会给大家补上的&#xff01; 本文包括&#xff1a; Prompt 的一些行业术语介绍 Prompt 写好提示词的方法经验介绍&#xff08;附示例教程&#xff09; LLM自身存在的问题&#xff08;可以用Prompt解决的以及无法用Prompt解决的&…

u盘监控系统—公司电脑如何监控U盘使用?【详解】

在当今的办公环境中&#xff0c;U盘等移动存储设备已成为数据传输和存储的重要工具。 然而&#xff0c;随着U盘的广泛使用&#xff0c;也带来了潜在的安全风险&#xff0c;如数据泄露、病毒传播等。 因此&#xff0c;对于随时会有数据泄露风险的企业而言&#xff0c;U盘的使用…

动态规划篇-00:解题思想与框架

引言 这篇文章是我的第一个专栏的第一篇文章。 在这篇文章中&#xff0c;我将总结我对于动态规划的认识与解题框架。这些思想和框架将会在后面的习题中反复出现。 解题思想 对于所有的算法题来说&#xff0c;本质上都是“穷举”——列出所有的结果&#xff0c;然后选择最优…