手写简易RPC(实践版)

news2025/2/22 1:15:22

首先了解rpc

rpc-远程过程调用,openFeign,Dubbo都可以算作rpc,以微服务来具体说明,就是在本地不需要去发送请求,通过rpc框架,像调用本地方法一样调用其他服务的方法,本质上还是要经过网络,去请求其他服务的资源

一般rpc都会有一个注册中心,使用rpc框架的服务称为消费者,被调用的服务成为生产者,以下是简单的图示

这里的服务可以简单理解为类中的方法或者类

接下来实际去手写一个最简单rpc框架

首先准备一个多模块的项目,结构如下

cosumer为消费者,provider为生产者,rpcFrame是我们要写的核心,也就是rpc框架的内容

这个过程就像工厂车间,从生产出来,到工厂加工,然后到消费者手上,按照这个顺序去编写程序理解也会更轻松一些

先把整体文件目录结构放出来,找不到的可以对应一下

按照车间的生产顺序,首先要 provider 生产产品

public interface HelloService {
    public String sayHello(String name);
}
public class HelloServiceImpl implements HelloService{

    @Override
    public String sayHello(String name) {
        System.out.println("Hello " + name);
        return "Hello " + name;
    }
}

这里的产品就是提供一个方法以及其实现类,一个简单的hello

产品有了,下一步就是加工,加工要有仓库,那么我们在rpcFrame中创建一个本地的“仓库”(注册中心)

public class LocalRegister {
    public  static final Map<String, Class<?>> map = new ConcurrentHashMap<String, Class<?>>();

    /**
     * 注册
     */
    public static void register(String interfaceName, Class<?> implClass) {
        map.put(interfaceName, implClass);
    }

    /**
     * 获取服务
     */
    public static Class<?> get(String interfaceName) {
        return map.get(interfaceName);
    }
}

因为可能不止一个产品,所以需要一个集中的有包容性的仓库,那么在Java中这个仓库就是Map

key为服务的名称,应当是provider服务,不过后续为了方便记录这里的key是类接口的名称,val则是其实现类

然后回到我们的生产车间 provider,生产好的产品需要送入仓库,在provider中添加主函数

public class Main {
    public static void main(String[] args) {
        //注册服务
        LocalRegister.register(HelloService.class.getName(), HelloServiceImpl.class);
        //启动网络服务
        HttpServer httpServer = new HttpServer();
        httpServer.startServer();
    }
}

注册服务就是我们将产品送入仓库的过程,但是在Java中主函数不能作为一个服务,所以我们使用需要一个网络框架,这里选用的是tomcat

把启动服务的工作一并交给仓库加工,所以这里调用的是rpcFrame的方法,如下

public class HttpServer {

    //主要是启动服务
    public void startServer(){
        Tomcat tomcat = new Tomcat();
        //服务器网络参数,可以通过参数传递,我们写的是简单版rpc,直接写死即可
        tomcat.setBaseDir("/");
        tomcat.setPort(8081);
        tomcat.setHostname("127.0.0.1");
        Context context = tomcat.addContext("/", null);// 添加上下文,映射路径为'/'
        // 初始化参数 (可选)
        context.addParameter("param1", "value1");
        context.addParameter("param2", "value2");
        context.addErrorPage(new ErrorPage());
        context.setCookies(true);
        context.setSessionTimeout(30);

        // 为context上下文添加servlet--处理请求
        // 这些是Javaweb的相关知识,如果不熟悉再去了解一下
        Tomcat.addServlet(context,"defaultServlet",new IndexServlet());
        // 为名称defaultServlet的servlet添加一个映射路径为'v1'
        context.addServletMappingDecoded("/*","defaultServlet");//为 servlet 配置 URL 映射

        try {
            tomcat.start();
        } catch (LifecycleException e) {
            throw new RuntimeException(e);
        }
        tomcat.getServer().await();//阻塞

    }
}

以上的大部分内容作者也不太会,因为springboot自带的tomcat,没用过原生的tomcat,网上随便找的启动方式稍微调整一下就可以用了

有服务端之后需要对请求进行处理,那么就涉及servlet的知识了(这里如果看不懂稍后结合消费者一端请求再理解一下)

调用服务,肯定要添加参数,那么这个参数也需要一个类来承载,准备一个RpcRequest类,其中的Serializable 一定要实现,因为在网络中无法传递Java对象,必须要实现序列化

@Data
@AllArgsConstructor
public class RpcRequest implements Serializable {//序列化很重要

    /**
     * 服务名称
     */
    private String serviceName;

    /**
     * 方法名称
     */
    private String methodName;

    /**
     * 参数类型列表
     */
    private Class<?>[] parameterTypes;

    /**
     * 参数列表
     */
    private Object[] args;

}
public class IndexServlet extends HttpServlet {

    // 处理http请求
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置响应的内容类型和字符编码
        resp.setContentType("text/plain");
        resp.setCharacterEncoding("UTF-8");

        try  {
            ServletInputStream inputStream = req.getInputStream();
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            // 读取请求对象
            RpcRequest request = (RpcRequest) objectInputStream.readObject();

            // 获取服务类和方法
            Class<?> serviceClass = LocalRegister.get(request.getServiceName());

            Method method = serviceClass.getMethod(request.getMethodName(), request.getParameterTypes());

            // 创建服务实例
            Object serviceInstance = serviceClass.getDeclaredConstructor().newInstance();

            // 通过反射直接调用方法
            Object result = method.invoke(serviceInstance, request.getArgs());

            // 返回结果
            resp.getOutputStream().write(result.toString().getBytes());
        } catch (Exception e) {
            System.out.println("Error occurred while processing request");
        }
    }
}

下一步就可以先启动服务端,把“生产+送进车间”完成

服务启动成功

消费者就很简单,只需要构造一个参数,然后去向rpc消费就可以了

public class Main {
    public static void main(String[] args) throws Exception {
        //简单构造参数,这里还可以向上封装一层,使其更加简单易用,比较流行的rpc框架也是这么做的
        //不过我们主要是为了学习了解rpc,以完成其基本功能为主,择取最简单路径
        RpcRequest rpcRequest = new RpcRequest(HelloService.class.getName(),"sayHello",new Class[]{String.class},new Object[]{"张三"});
        //调用
        String s = HttpClient.sendHttpRequest("127.0.0.1", 8081, rpcRequest);
        System.out.println(s);
    }
}

rpc归根到底还是服务间的通讯,离不开网络,把这个网络请求放到rpc里,我们只需要调用方法即可,具体的请求在rpcFrame中如下

public class HttpClient {
    public static String sendHttpRequest(String host, Integer port, RpcRequest request) throws IOException {
        URL url = new URL("http", host, port, "/");
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setDoOutput(true);
        connection.setRequestProperty("Content-Type", "application/json");

        try (OutputStream outputStream = connection.getOutputStream();
             ObjectOutputStream oot = new ObjectOutputStream(outputStream)) {
            oot.writeObject(request);
            oot.flush();
        }
        //获取结果
        try (InputStream inputStream = connection.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
            StringBuilder result = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                result.append(line).append("\n");
            }
            return result.toString();
        }
    }
}

这里的内容与rpc关系不大,只需要知道,这里是发送http请求的即可(因为我也是网上搜+AI调整的)

以上就是一个最简单的rpc框架,我来梳理一下具体的流程

首先provider将自己的服务HelloService注册到rpcFrame的注册中心,也就是前面的map中。

然后provider需要启动一个网络服务,成为一个提供服务的服务器;此时的rpc中包含一个provider发起的网络,网络里有一个服务HelloService

cosumer构造好必要的参数,向rpc发起请求,rpc通过http请求,向存在的provider的网络请求资源,执行对应的方法,然后将结果进行处理返回给cosumer

可能会出问题的地方

① 由于使用的是原始的方式启动tomcat,可能会出现版本与具体的类出现冲突,导致服务无法启动,这是我的Tomcat版本

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

②如果没有多模块开发的经验单纯想了解rpc发现有些类爆红无法被发现,记得去导入对应的模块

③网络协议相关的内容,本人也不是很了解,就是遇到问题解决问题,如果说有网络相关的问题,建议百度,有大佬如果不用tomcat也可以用其他网络框架,netty等都可以

④操作请求的时候IO操作较多,注意资源的问题,解决不了直接问AI,这种资源泄露等相关问题对于AI都是小儿科

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

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

相关文章

mysql学习笔记-多版本并发控制

1、什么是ReadView 在 MVCC机制中&#xff0c;多个事务对同一个行记录进行更新会产生多个历史快照&#xff0c;这些历史快照保存在 Undo Log里。如果一个事务想要查询这个行记录&#xff0c;需要读取哪个版本的行记录呢?这时就需要用到 ReadView 了&#xff0c;它帮我们解决了…

算法日记20:SC72最小生成树(prim朴素算法)

一、题目&#xff1a; 二、题解 2.1&#xff1a;朴素prim的步骤解析 O ( n 2 ) O(n^2) O(n2)(n<1e3) 0、假设&#xff0c;我们现在有这样一个有权图 1、我们随便找一个点&#xff0c;作为起点开始构建最小生成树(一般是1号)&#xff0c;并且存入intree[]状态数组中&#xf…

Redis7——基础篇(五)

前言&#xff1a;此篇文章系本人学习过程中记录下来的笔记&#xff0c;里面难免会有不少欠缺的地方&#xff0c;诚心期待大家多多给予指教。 基础篇&#xff1a; Redis&#xff08;一&#xff09;Redis&#xff08;二&#xff09;Redis&#xff08;三&#xff09;Redis&#x…

从零搭建微服务项目Base(第7章——微服务网关模块基础实现)

前言&#xff1a; 在前面6章的学习中已经完成了服务间的调用实现&#xff0c;即各微服务通过nacos或eureka服务器完成服务的注册&#xff0c;并从nacos中拉取配置实现热更新。当某个服务接口需要调用其他服务时&#xff0c;通过feign定义接口&#xff0c;并通过注解配置服务名…

pdf转换成word在线 简单好用 支持批量转换 效率高 100%还原

pdf转换成word在线 简单好用 支持批量转换 效率高 100%还原 在数字化办公的浪潮中&#xff0c;文档格式转换常常让人头疼不已&#xff0c;尤其是 PDF 转 Word 的需求极为常见。PDF 格式虽然方便阅读和传输&#xff0c;但难以编辑&#xff0c;而 Word 格式却能灵活地进行内容修…

嵌入式音视频开发(二)ffmpeg音视频同步

系列文章目录 嵌入式音视频开发&#xff08;零&#xff09;移植ffmpeg及推流测试 嵌入式音视频开发&#xff08;一&#xff09;ffmpeg框架及内核解析 嵌入式音视频开发&#xff08;二&#xff09;ffmpeg音视频同步 嵌入式音视频开发&#xff08;三&#xff09;直播协议及编码器…

SpringBoot速成概括

视频&#xff1a;黑马程序员SpringBoot3Vue3全套视频教程&#xff0c;springbootvue企业级全栈开发从基础、实战到面试一套通关_哔哩哔哩_bilibili 图示&#xff1a;

微信小程序image组件mode属性详解

今天学习微信小程序开发的image组件&#xff0c;mode属性的属性值不少&#xff0c;一开始有点整不明白。后来从网上下载了一张图片&#xff0c;把每个属性都试验了一番&#xff0c;总算明白了。现总结归纳如下&#xff1a; 1.使用scaleToFill。这是mode的默认值&#xff0c;sc…

Matlab写入点云数据到Rosbag

最近有需要读取一个点云并做处理后&#xff0c;重新写回rosbag。网上有很多读取的教程&#xff0c;但没有写入。自己写入时也遇到了很多麻烦&#xff0c;踩了一堆坑进行记录。 1. rosbag中一个lidar的msg有哪些信息&#xff1f; 通过如下代码&#xff0c;先读取一个rosbag的l…

数据分析--数据清洗

一、数据清洗的重要性&#xff1a;数据质量决定分析成败 1.1 真实案例警示 电商平台事故&#xff1a;2019年某电商大促期间&#xff0c;因价格数据未清洗导致错误标价&#xff0c;产生3000万元损失医疗数据分析&#xff1a;未清洗的异常血压值&#xff08;如300mmHg&#xff…

用命令模式设计一个JSBridge用于JavaScript与Android交互通信

用命令模式设计一个JSBridge用于JavaScript与Android交互通信 在开发APP的过程中&#xff0c;通常会遇到Android需要与H5页面互相传递数据的情况&#xff0c;而Android与H5交互的容器就是WebView。 因此要想设计一个高可用的 J S B r i d g e JSBridge JSBridge&#xff0c;不…

Vue 3最新组件解析与实践指南:提升开发效率的利器

目录 引言 一、Vue 3核心组件特性解析 1. Composition API与组件逻辑复用 2. 内置组件与生命周期优化 3. 新一代UI组件库推荐 二、高级组件开发技巧 1. 插件化架构设计 2. 跨层级组件通信 三、性能优化实战 1. 惰性计算与缓存策略 2. 虚拟滚动与列表优化 3. Tree S…

计算机网络(涵盖OSI,TCP/IP,交换机,路由器,局域网)

一、网络通信基础 &#xff08;一&#xff09;网络通信的概念 网络通信是指终端设备之间通过计算机网络进行的信息传递与交流。它类似于现实生活中的物品传递过程&#xff1a;数据&#xff08;物品&#xff09;被封装成报文&#xff08;包裹&#xff09;&#xff0c;通过网络…

JVM-Java程序的运行环境

Java Virtual Machine Java程序的运行环境 JVM组成 程序计数器 线程私有的&#xff0c;内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。 Java堆 线程共享的区域: 主要用来保存对象实例, 数组等, 当堆中没有内存空间可分配给实例也无法再扩展时, 则抛出OutOfMe…

什么是网关,网关的作用是什么?网络安全零基础入门到精通实战教程!

1. 什么是网关 网关又称网间连接器、协议转换器&#xff0c;也就是网段(局域网、广域网)关卡&#xff0c;不同网段中的主机不能直接通信&#xff0c;需要通过关卡才能进行互访&#xff0c;比如IP地址为192.168.31.9(子网掩码&#xff1a;255.255.255.0)和192.168.7.13(子网掩码…

《千恋万花》无广版手游安卓苹果免费下载直装版

自取https://pan.xunlei.com/s/VOJS77k8NDrVawqcOerQln2lA1?pwdn6k8 《千恋万花》&#xff1a;柚子社的和风恋爱杰作 《千恋万花》&#xff08;Senren * Banka&#xff09;是由日本知名美少女游戏品牌柚子社&#xff08;Yuzusoft&#xff09;于2016年推出的一款和风恋爱题材…

javaEE-14.spring MVC练习

目录 1.加法计算器 需求分析: 前端页面代码: 后端代码实现功能: 调整前端页面代码: 进行测试: 2.用户登录 需求分析: 定义接口: 1.登录数据校验接口: 2.查询登录用户接口: 前端代码: 后端代码: 调整前端代码: 测试/查错因 后端: 前端: lombok工具 1.引入依赖…

rabbitmq五种模式的实现——springboot

rabbitmq五种模式的实现——springboot 基础知识和javase的实现形式可以看我之前的博客 代码地址&#xff1a;https://github.com/9lucifer/rabbitmq4j-learning 一、进行集成 &#xff08;一&#xff09;Spring Boot 集成 RabbitMQ 概述 Spring Boot 提供了对 RabbitMQ 的自…

23. AI-大语言模型-DeepSeek赋能开发-Spring AI集成

文章目录 前言一、Spring AI 集成 DeepSeek1. 开发AI程序2. DeepSeek 大模型3. 集成 DeepSeek 大模型1. 接入前准备2. 引入依赖3. 工程配置4. 调用示例5. 小结 4. 集成第三方平台&#xff08;已集成 DeepSeek 大模型&#xff09;1. 接入前准备2. POM依赖3. 工程配置4. 调用示例…

Educational Codeforces Round 174 (Rated for Div. 2)(ABCD)

A. Was there an Array? 翻译&#xff1a; 对于整数数组 ​&#xff0c;我们将其相等特征定义为数组 &#xff0c;其中&#xff0c;如果数组 a 的第 i 个元素等于其两个相邻元素&#xff0c;则 &#xff1b;如果数组 a 的第 i 个元素不等于其至少一个相邻元素&#xff0c;则 …