【RPC学习之旅】手把手实现一个RPC框架

2023-05-16

手把手实现一个RPC框架

一、RPC前置知识介绍

1.什么是RPC?

RPC是远程过程调用(Remote Procedure Call)的缩写形式。

假设我们有两台服务器A与B,一个在A服务器上部署的应用想要调用B服务器上部署的应用的函数、方法,由于不在同一个内存空间,不能直接调用,因此需要通过网络来表达调用的语义和传达调用的数据。 在Java中,即将被调用的类、方法、参数序列化后通过网络传到目标应用,通过反射的方式调用执行

在这里插入图片描述

2.数据交换方式

利用中间件进行数据交换。

在这里插入图片描述

直接进行数据交换。

在这里插入图片描述

3.现有RPC框架对比

目前主流的RPC框架如下表所示:

在这里插入图片描述

4.核心原理

4.1 调用流程

在这里插入图片描述

  • Server: Provider,服务提供者
  • Client: Consumer,服务消费者
  • Stub: 存根,服务描述

一次函数调用的流程如下:

  • 首先客户端需要告诉服务端,需要调用的函数,这里函数和进程存在一个映射,客户端远程调用时,需要查一下函数,找到对应的标识,然后执行函数的代码。
  • 客户端需要把本地参数传给远程函数,本地调用的过程中,直接压栈即可,但是在远程调用过程中不在同一个内存里,无法直接传递函数的参数,因此需要客户端把参数转换成字节流,传给服务端,然后服务端将字节流转换成自身能读取的格式,是一个序列化和反序列化的过程。
  • 数据准备好了之后,如何进行传输?网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程。

4.2 架构

my-rpc
├── my-rpc-client    -- 客户端
├── my-rpc-codec     -- 序列化与反序列化
├── my-rpc-common    -- 提供一些反射工具
├── my-rpc-protocol  -- 规定数据传输协议
├── my-rpc-server    -- 服务端
├── my-rpc-transport -- 用于client与server的http通信处理
└── my-rpc-example   -- 调用样例

模块依赖关系如下图所示,my-rpc-server和my-rpc-client依赖关系相同。

在这里插入图片描述

4.3 各功能模块功能详细介绍

(1)protocal模块

用于规定数据传输协议和规则;

(2)transport模块

  • 该模块主要用于client与server的http通信处理问题,其client请求内容以request类形式封装传输,server响应内容以response类封装返回;
  • 使用jetty容器完成init,start和stop功能;
  • 最重要的是RequestHandler实例的初始化,该抽象类定义于Transport模块,主要用于server处理来自client的请求。其抽象方法实现将在RpcServer类中详细讲解。

(3)common模块

  • common模块主要为一些反射工具,其具体实现如下:
  • getPublicMethods()方法一个用途是Server注册时存储所有的method的ServcieSescriptor。
  • invoke()方法用于执行指定实例对象的method。

(4)codec模块

  • Encoder 编码器
  • Decoder 解码器

(5)server模块

  • 本项目最核心两个模块之一,主要作用是定义了处理client请求的方法。
  • register()方法主要用于注册该class的所有共有方法,并且获取之前讲述的ServiceDescriptor实例与ServiceInstance作为键值对的形式存储。
  • 其内部主要定义了连个变量,一个是需要执行某个method的目标对象,另一个是需要执行的method。
  • 其onRequest()方法通过Servlet的inputStream与OutputStream参数获取来自Client的数据,并且通过获取到的Request实例参数从ServiceManager中get实例对象与method。
  • 因为Request对象中包含有Client获取到的实际参数,因此将上述参数一起传递到ServiceInvoker对象进行执行。

(6)client模块

  • 该模块主要功能有连个一个时动态代理获取实参,一个是请求Server进行过程调用。
  • 其RpcClient类主要是用于处理Client对Server的连接问题,相当于连接池,由有需求时随机返回连接。
  • RpcClient类的getProxy()方法为动态代理,需要重点关注RemoteInvoker类。
  • invoke()方法中对代理方法的参数进行存储封装到Request对象并且最终序列化传递到Server。

(7)example样例

  • 一个加减法的使用样例

4.4 涉及的技术栈

  • 基础:Java、Maven、反射、JDK动态代理
  • 序列化:FastJson
  • 网络:Jetty、URLConnection

二、实现

1.类依赖图

在这里插入图片描述

2.实现过程

附加问题:dependencies与dependencyManagement区别是什么

(1)dependencies即使在子项目中不写该依赖项,那么子项目仍然会从父项目中继承该依赖项(全部继承);

(2)dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom;另外如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。

附加问题:Java泛型中Class 、T与Class<?>

单独的T 代表一个类型 ,而 Class代表这个类型所对应的类, Class<?>表示类型不确定的类/

如何创建一个Class类型的实例?

就像使用非泛型代码一样,有两种方式:调用方法 Class.forName() 或者使用类常量X.class。 Class.forName() 被定义为返 回 Class<?>。另一方面,类常量 X.class 被定义为具有类型 Class,所 以 String.class 是Class 类型的。

方法中为什么需要 T修饰呢

泛型的声明,必须在方法的修饰符(public,static,final,abstract等)之后,返回值声明之前。

public static <T> T request2Bean(HttpServletRequest request,Class<T> clazz){}

其中第一个是与传入的参数Class相对应的,相当于返回值的一个泛型,后面的T是返回值类型,代表方法必须返回T类型的(由传入的Class决定)

实现过程:

1.建工程
2.实现通用模块
3.实现序列化模块
4.实现网络模块
5.实现server
6.实现client
7.gk-rpc使用案例
rpc-proto : 基础协议封装
    Peer:主机+端口
    ServiceDescriptor:服务描述,将会是注册中心中对应的【服务的key值】包含有【类,方法,返回值类型,参数类型数组】,唯一确定一个方法
    Request:请求体,持有【服务描述+请求参数数组】
    Response:默认的响应体封装
rpc_transport: 网络服务封装
    TransportClient:客户端封装【接口+实现】
        1.创建连接
        2.发送数据等待响应:发送inputstream,等待outputstream
        3.关闭连接
    TransportServer:服务端封装【接口+实现】
        1.启动监听: servlet管理
        2.接收请求:接收请求,反序列化获取对象,处理调用,返回数据
        3.关闭监听
rpc-common: 工具类封装
    ReflectionUtils:
        根据class创建对象
        根据class获取该类所有公共方法
        invoke方法调用 : public static Object invoke(Object object, Method method, Object... args)
rpc-codec: 序列化封装【接口+实现】
    Encoder 转二进制
    Decoder 转对象
rpc-server: 服务端封装
    RpcServerConfig:服务配置类
        HTTPTransportServer:默认服务实例类
        JSONEncoder:序列化实例类
        JSONDecoder:反序列化实例类
        port:监听端口
    ServiceInstance:服务的实例 --> 哪个对象暴露出哪个方法
        target:对象
        method:方法
    ServiceManager:管理rpc的所有服务
        Map<ServiceDescriptor, ServiceInstance> services:要将服务描述,服务实例作为key-value存储,便于客户端传来时,能够找到准确地实例,调用正确的方法
        register:服务注册【register(Class<T> interfaceClass, T bean) 】
            接口类 + 对象bean:将对象中的每一个方法都当做一个ServiceInstance注册进map中
        lookup:服务查找【lookUp(Request request)】
            获取请求中的ServiceDescriptor,去map中取出
    ServiceInvoke:【服务的调用】
        invoke(ServiceInstance serviceInstance, Request request):
            通过request的ServiceDescriptor找到服务的实例
            通过反射调用方法,传入参数
            ReflectionUtils.invoke(serviceInstance.getTarget(), serviceInstance.getMethod(),request.getParameters())
    RPCServer:【服务的封装】
        1.设置RpcServerConfig config
        2.反射获取网络实例     ReflectionUtils.newInstance(config.getTransportClass());
        3.反射获取序列化实例   ReflectionUtils.newInstance(config.getEncoderClass());
        4.反射获取反序列化实例 ReflectionUtils.newInstance(config.getDecoderClass());
        5.创建服务调用对象: this.serviceInvoke = new ServiceInvoke();
        6.创建服务管理对象:this.serviceManager = new ServiceManager();
        7.初始化网络实例:this.net.init(config.getPort(), this.handler); 此时只是准备好服务信息,并未开启监听
        
        8.register:服务注册 register(Class<T> interfaceClass, T bean) {serviceManager.register(interfaceClass, bean); }
        9.start:this.net.start 开启
        10.stop:this.net.stop  关闭
        11.handler请求处理:
            1.接收inputStream
            2.反序列化获得Request
            3.根据ServiceManager.lookup(request)找到实例ServiceInstance
            4.通过Object invoke = serviceInvoke.invoke(sis, request);得到响应结果并封装到 response.setData(invoke);
            5.序列化Response
            6.write
rpc-client: 客户端封装

结果展示图:

在这里插入图片描述
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【RPC学习之旅】手把手实现一个RPC框架 的相关文章

  • 8本推荐游戏开发书籍

    很多刚刚接触游戏开发的朋友经常问我 xff1a 如何开始学习游戏开发 xff1f 我从事游戏开发行业很多年了 xff0c 坦率地讲 xff0c 开发游戏充满挑战性 xff0c 需要开发人员具备大量的技能与积极的创新精神 希望这篇小文能帮助朋
  • Maxwell启动停止脚本

    Maxwell启动停止脚本 进入 maxwell bin nbsp 直接运行下面的内容 生成脚本 bin bash description maxwell File maxwell Description Starts and stops
  • Epoll 的time_out参数引发的cpu占用问题

    转自 xff1a https www cnblogs com Jimmy104 p 5258205 html 针对自己写的一个服务器网络引擎Engine 文章后面附上源码 使用epoll 刚刚开始时候发现占用CPU 特别高 xff0c 但是
  • 【周志华机器学习】集成学习

    第八章 集成学习 个体与集成BoostingBagging 与随机森林Bagging随机森林 结合策略平均法投票法学习法 多样性 个体与集成 集成学习通过构建并结合多个学习器来完成学习任务 xff0c 也被称为多分类器系统 集成学习的一般结
  • 用户句柄表的遍历

    私有句柄表 HANDLE TABLE ENTRY的Object直接指向 OBJECT HEADER不用减 span class token macro property span class token directive hash spa
  • 树莓派安装后--安装必要软件(个人版)

    树莓派安装后 安装必要软件 xff08 个人版 xff09 查看是哪个版本 lsb release a 查看raspbian是哪个版本 getconf LONG BIT 查看系统位数 uname a kernel 版本 opt vc bin
  • 固定翼姿态控制流程

    固定翼控制流程 主文件夹 子文件 fw att control位于src moudle文件夹下 fw att control main c 主文件 fw att control params c 主文件参数 CMakeList attitu
  • 固定翼位置控制_Tecs

    Tecs在位置控制中主要控制纵向的高度 xff0c 因为升降舵可以控制飞机的高度 xff0c 油门可以控制飞机的速度 xff0c 但是单单通过升降舵改变高度会使速度下降或者上升 xff0c 单单通过油门改变速度会使高度改变 xff0c 所以
  • 固定翼位置控制_L1

    L1算法控制航向 飞机从现在位置到设定位置 xff0c 需要进行转弯 转弯需要一个横向的加速度来改变速度的方向 这里横向加速度的计算公式 a 61 V2R 又因为R 61 2 sin L1 可得 a 61 2 V2L1 sin L1是现在位
  • 存储过程懂不懂

    存储过程的官方定义是这么说的 xff1a 存储过程 xff08 Stored Procedure xff09 是一组为了完成特定功能的 SQL 语句集 xff0c 经编译后存储在数据库中 用户通过指定存储过程的名字并给出参数 xff08 如
  • PX4多旋翼位置控制

    多悬翼的位置控制由内外环控制 xff0c 外环P控制作用于位置差 xff0c 产生期望速度 xff0c 内环PID作用于速度差 xff0c 产生期望油门 xff0c 然后将油门解算成期望姿态 外环位置控制的三种控制源 1 Manual 手动
  • PX4混控器定义

    PX4混控 把输入指令 分配给电机以及舵机的执行器 信号传递 Actuators id 61 ORB ID actuator controls 0 0号控制组 Orb publish actuators id actuators 0 pub
  • Linux--Ubuntu18.04交叉编译链;多窗口终端;彻底删除不用的虚拟机镜像

    文章目录 建立共享文件夹交叉编译查看是否安装了交叉编译工具安装交叉编译工具链退出root模式 多窗口终端 Terminator彻底删除不用的虚拟机镜像 建立共享文件夹 实现windows系统与虚拟机中的ubuntu之间的文件共享 PC机新建
  • 准确率,召回率,mAP(mean average precision)解释

    准确率Precision 召回率Recall 其实这个翻译相当蛋疼 recall最合理的翻译应该是 查全率 而Precision的最合理的翻译应该是查准率 这样就很容易理解了 xff0c 假设一个班级有10个学生 xff0c 5男5女 你用
  • Android 开发使用 Java 8 中Lambda 表达式功能

    简介 Android开发支持所有 Java 7 语言功能 xff0c 以及一部分 Java 8 语言功能 xff08 具体因平台版本而异 xff09 注意 xff1a 在开发应用时 xff0c 可以选择使用 Java 8 语言功能 您可以将
  • 浅谈 for循环

    浅谈for循环 xff0c for循环习题讲解 文章多看几遍吧 相信你一定会收获不少哇 什么是循环 xff1f 我认为循环就是把一个步骤一直重复执行的操作 如果没有终止条件 xff0c 那么这一步骤就会一直执行下去 xff0c 直到地老天昏
  • 给Jetson Nano更换eMMC闪存(扩容)

    7月4日更新 xff1a 添加了有关问题的解释与探讨 xff08 文末 xff0c 下一篇文章 xff09 7月5日更新 xff1a 添加了效果图 xff08 文末 xff09 7月8日更新 xff1a 添加了视频 b站 xff1a BV1
  • Linux下安装Anaconda3,这个教程一定要看!

    前言 大家好 xff0c 我是爱写Bug的麦洛 由于工作需要 xff0c 要为客户搭建Python开发环境 作为从来没有接触过Python的小白 xff0c 为了完成任务 xff0c 也是破费周折 xff0c 请教了身边做Python的朋友
  • Stm32F303进入bootloader重新烧录程序

    STM32F3飞控或开发板无法进入bootloader重新进行烧录固件程序的解决方案 F3系列的MCU无法进入bootloader 前提准备开始F4和F3的不同 F3系列的MCU无法进入bootloader 前几日找了一块F3的飞控板 刷入
  • 存储过程进阶(vb.net+SQL Server2008环境)

    写过一篇 存储过程入门 的博客 xff0c 那仅仅是入门 xff0c 下面和大家一起深入学习存储过程 xff08 也许以后还会有更深入 xff09 以经典的注册为例子 xff0c 篇幅有限只写了核心部分 xff0c 其他略过 无参数无返回值

随机推荐