什么事RPC并实现一个简单的RPC

news2024/9/20 1:12:16

1. 基本的RPC模型

主要介绍RPC是什么,基本的RPC代码,RPC与REST的区别,gRPC的使用

1.1 基本概念

  • RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务
  • 本地过程调用:如果需要将本地student对象的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定。
  • 远程过程调用:上述操作的过程中,如果addAge()这个方法在服务端,执行函数的函数体在远程机器上,如何告诉机器需要调用这个方法呢?
  1. 首先客户端需要告诉服务器,需要调用的函数,这里函数和进程ID存在一个映射,客户端远程调用时,需要查一下函数,找到对应的ID,然后执行函数的代码。
  2. 客户端需要把本地参数传给远程函数,本地调用的过程中,直接压栈即可,但是在远程调用过程中不再同一个内存里,无法直接传递函数的参数,因此需要客户端把参数转换成字节流,传给服务端,然后服务端将字节流转换成自身能读取的格式,是一个序列化和反序列化的过程。
  3. 数据准备好了之后,如何进行传输?网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。
    总结一下上述过程:
// Client端 
//    Student student = Call(ServerAddr, addAge, student)
1. 将这个调用映射为Call ID。
2. 将Call ID,student(params)序列化,以二进制形式打包
3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新

// Server端
 - 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap
 - 等待客户端请求
 - 得到一个请求后,将其数据包反序列化,得到Call ID
 - 通过在callIdMap中查找,得到相应的函数指针
 - 将student(params)反序列化后,在本地调用addAge()函数,得到结果
 - 将student结果序列化后通过网络返回给Client

在这里插入图片描述

  • 在微服务的设计中,一个服务A如果访问另一个Module下的服务B,可以采用HTTP
    REST传输数据,并在两个服务之间进行序列化和反序列化操作,服务B把执行结果返回过来。

在这里插入图片描述

  • 由于HTTP在应用层中完成,整个通信的代价较高,远程过程调用中直接基于TCP进行远程调用,数据传输在传输层TCP层完成,更适合对效率要求比较高的场景,RPC主要依赖于客户端和服务端之间建立Socket链接进行,底层实现比REST更复杂。

1.2 rpc demo

系统类图
系统调用过程

客户端:

public class RPCClient<T> {
    public static <T> T getRemoteProxyObj(final Class<?> serviceInterface, final InetSocketAddress addr) {
        // 1.将本地的接口调用转换成JDK的动态代理,在动态代理中实现接口的远程调用
        return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{serviceInterface},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Socket socket = null;
                        ObjectOutputStream output = null;
                        ObjectInputStream input = null;
                        try{
                            // 2.创建Socket客户端,根据指定地址连接远程服务提供者
                            socket = new Socket();
                            socket.connect(addr);

                            // 3.将远程服务调用所需的接口类、方法名、参数列表等编码后发送给服务提供者
                            output = new ObjectOutputStream(socket.getOutputStream());
                            output.writeUTF(serviceInterface.getName());
                            output.writeUTF(method.getName());
                            output.writeObject(method.getParameterTypes());
                            output.writeObject(args);

                            // 4.同步阻塞等待服务器返回应答,获取应答后返回
                            input = new ObjectInputStream(socket.getInputStream());
                            return input.readObject();
                        }finally {
                            if (socket != null){
                                socket.close();
                            }
                            if (output != null){
                                output.close();
                            }
                            if (input != null){
                                input.close();
                            }
                        }
                    }
                });
    }
}

服务端:

public class ServiceCenter implements Server {

    private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    private static final HashMap<String, Class> serviceRegistry = new HashMap<String, Class>();

    private static boolean isRunning = false;

    private static int port;


    public ServiceCenter(int port){
        ServiceCenter.port = port;
    }


    @Override
    public void start() throws IOException {
        ServerSocket server = new ServerSocket();
        server.bind(new InetSocketAddress(port));
        System.out.println("Server Start .....");
        try{
            while(true){
                executor.execute(new ServiceTask(server.accept()));
            }
        }finally {
            server.close();
        }
    }

    @Override
    public void register(Class serviceInterface, Class impl) {
        serviceRegistry.put(serviceInterface.getName(), impl);
    }

    @Override
    public boolean isRunning() {
        return isRunning;
    }

    @Override
    public int getPort() {
        return port;
    }

    @Override
    public void stop() {
        isRunning = false;
        executor.shutdown();
    }
   private static class ServiceTask implements Runnable {
        Socket client = null;

        public ServiceTask(Socket client) {
            this.client = client;
        }

        @Override
        public void run() {
            ObjectInputStream input = null;
            ObjectOutputStream output = null;
            try{
                input = new ObjectInputStream(client.getInputStream());
                String serviceName = input.readUTF();
                String methodName = input.readUTF();
                Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
                Object[] arguments = (Object[]) input.readObject();
                Class serviceClass = serviceRegistry.get(serviceName);
                if(serviceClass == null){
                    throw new ClassNotFoundException(serviceName + "not found!");
                }
                Method method = serviceClass.getMethod(methodName, parameterTypes);
                Object result = method.invoke(serviceClass.newInstance(), arguments);

                output = new ObjectOutputStream(client.getOutputStream());
                output.writeObject(result);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if(output!=null){
                    try{
                        output.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
                if (input != null) {
                    try {
                        input.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (client != null) {
                    try {
                        client.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
public class ServiceProducerImpl implements ServiceProducer{
    @Override
    public String sendData(String data) {
        return "I am service producer!!!, the data is "+ data;
    }
}
public class RPCTest {
    public static void main(String[] args) throws IOException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Server serviceServer = new ServiceCenter(8088);
                    serviceServer.register(ServiceProducer.class, ServiceProducerImpl.class);
                    serviceServer.start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        ServiceProducer service = RPCClient.getRemoteProxyObj(ServiceProducer.class, new InetSocketAddress("localhost", 8088));
        System.out.println(service.sendData("test"));
    }
}

1.3 分析

这里客户端只需要知道Server端的接口ServiceProducer即可,服务端在执行的时候,会根据具体实例调用实际的方法ServiceProducerImpl,符合面向对象过程中父类引用指向子类对象。

2. gRPC的使用

2.1. gRPC与REST

  • REST通常以业务为导向,将业务对象上执行的操作映射到HTTP动词,格式非常简单,可以使用浏览器进行扩展和传输,通过JSON数据完成客户端和服务端之间的消息通信,直接支持请求/响应方式的通信。不需要中间的代理,简化了系统的架构,不同系统之间只需要对JSON进行解析和序列化即可完成数据的传递。

  • 但是REST也存在一些弊端,比如只支持请求/响应这种单一的通信方式,对象和字符串之间的序列化操作也会影响消息传递速度,客户端需要通过服务发现的方式,知道服务实例的位置,在单个请求获取多个资源时存在着挑战,而且有时候很难将所有的动作都映射到HTTP动词。

  • 正是因为REST面临一些问题,因此可以采用gRPC作为一种替代方案,gRPC 是一种基于二进制流的消息协议,可以采用基于Protocol
    Buffer的IDL定义grpc
    API,这是Google公司用于序列化结构化数据提供的一套语言中立的序列化机制,客户端和服务端使用HTTP/2以Protocol
    Buffer格式交换二进制消息。

  • gRPC的优势是,设计复杂更新操作的API非常简单,具有高效紧凑的进程通信机制,在交换大量消息时效率高,远程过程调用和消息传递时可以采用双向的流式消息方式,同时客户端和服务端支持多种语言编写,互操作性强;不过gRPC的缺点是不方便与JavaScript集成,某些防火墙不支持该协议。

  • 注册中心:当项目中有很多服务时,可以把所有的服务在启动的时候注册到一个注册中心里面,用于维护服务和服务器之间的列表,当注册中心接收到客户端请求时,去找到该服务是否远程可以调用,如果可以调用需要提供服务地址返回给客户端,客户端根据返回的地址和端口,去调用远程服务端的方法,执行完成之后将结果返回给客户端。这样在服务端加新功能的时候,客户端不需要直接感知服务端的方法,服务端将更新之后的结果在注册中心注册即可,而且当修改了服务端某些方法的时候,或者服务降级服务多机部署想实现负载均衡的时候,我们只需要更新注册中心的服务群即可。

RPC调用过程

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

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

相关文章

管理类联考——逻辑——技巧篇——数字编码——公式

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&…

特斯拉12V低压系统存在问题:刹车失灵还能怪司机吗?

特斯拉汽车失控加速事件引发全球关注&#xff0c;美国NHTSA&#xff08;交通运输安全委员会&#xff09;和特斯拉之前将责任归咎于司机误操作。但一位研究人员提出新解释&#xff0c;并指出特斯拉的12V低压系统可能存在问题&#xff0c;特别是在高负载状态下。 此研究认为&…

git上传文件到Gitee报错“error: failed to push some refs to https://gitee.com/xxxx”

文章目录 前言一、创建项目仓库二、创建工作区三、配置 LFS四、上传镜像文件 前言 我要将一个 4.27 GB 的文件上传到 Gitee 上&#xff0c;但是出现了下面这样的报错 error: failed to push some refs to https://gitee.com/xxxx/centos.git 因此记录一下解决报错的方法。 一、…

基于粤嵌gec6818开发板嵌入式开发电子相册,音乐播放,视频播放,2048游戏

一、功能与要求 实现功能&#xff1a;本系统需要使用粤嵌的GEC-6818开发板设计一款娱乐影音系统&#xff0c;其中包括图片显示&#xff08;相册&#xff09;、音乐播放、视频播放&#xff0c;游戏四个部分&#xff0c;在每个部分内部&#xff0c;具有操控各个部分的功能触摸按…

UNIAPP调用讯飞语音评测API

1、历经千辛万苦&#xff0c;UNIAPP调用评测API终于完成&#xff0c;在此做下总结下&#xff1a;首先看效果&#xff01; 2、实现第1步&#xff0c;首先是鉴权&#xff0c;用到的CryptoJS等工具都可以从讯飞和uniapp官方获取 import * as base64 from "base-64" impo…

redis操作问题

使用redisTemplate 往set集合中批量添加值 # 第一种办法就是for循环&#xff0c;每次进行setfor (int i 0; i < 1000000; i) {redisTemplate.opsForSet().add("key",i);} # 第二中办法就是使用HashSet批量添加数据Set set new HashSet<>();for (int i 1…

mybatisplus生成代码

导包&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>m…

CorelDRAW2023免费版平面设计矢量绘图工具

最初CorelDRAW 被开发运行于Windows版&#xff0c;数年后Macintosh版也随之发布。同时&#xff0c;CorelDRAW曾经存在过Linux的版本&#xff0c;但后来被放弃了。CorelDRAW简称cdr&#xff0c;是一款功能强大的矢量图制作软件&#xff0c;一说到矢量图制作&#xff0c;大家都会…

Go实现socks5服务器

SOCKS5 是一个代理协议&#xff0c;它在使用TCP/IP协议通讯的前端机器和服务器机器之间扮演一个中介角色&#xff0c;使得内部网中的前端机器变得能够访问Internet网中的服务器&#xff0c;或者使通讯更加安全。SOCKS5 服务器通过将前端发来的请求转发给真正的目标服务器&#…

应用层:万维网WWW

1.万维网WWW 笔记来源&#xff1a; 湖科大教书匠&#xff1a;应用层概述 湖科大教书匠&#xff1a;万维网WWW 声明&#xff1a;该学习笔记来自湖科大教书匠&#xff0c;笔记仅做学习参考 浏览器最重要的部分是渲染引擎&#xff0c;也就是浏览器内核。负责对网页内容进行解析和…

【触类旁通】编写代码和写小说的相似性

【触类旁通】编写代码和写小说的相似性 1&#xff0c;序2&#xff0c;对比分析2.1 相同点2.1.1 三要素2.1.2 读者-用户&#xff08;受众&#xff09;2.1.3 目录——功能模块2.1.4 故事情节——框架2.1.5 文笔——编程风格2.1.6 多部作品——多个阶段2.1.7 修辞——设计模式2.1.…

Mycat【Mycat分片规则(取模、分片枚举、范围约定)】(六)-全面详解(学习总结---从入门到深化)

目录 Mycat分片规则_取模 Mycat分片规则_分片枚举 Mycat分片规则_范围约定 Mycat分片规则_取模 实现方式 取模分片就是根据数据表的某一个字段&#xff0c;通常是某一个整数型的字 段&#xff0c;对其进行十进制的求模运算&#xff0c;将运算结果作为Mycat的路由结果。 注…

Element-案例-脚本页面布局

案例需求&#xff1a; 根据页面原型完成员工管理页面开发&#xff0c;并通过Axios完成数据异步加载 步骤 创建页面&#xff0c;完成页面的总体布局规划布局中各个组件的实现列表数据的异步加载&#xff0c;并渲染展示 1.创建页面&#xff0c;完成页面的总体布局规划 分析案…

代码随想录算法训练营第十三天 | 二叉树系列4

二叉树系列4 617 合并二叉树重点代码随想录的代码我的代码(晚上理解后自己编写) 700 二叉搜索树中的搜索未看解答自己编写看了解答后&#xff0c;精简了两个判断的写法重点代码随想录的代码我的代码(晚上理解后自己编写) 98 验证二叉搜索树重点看到二叉搜索树&#xff0c;就要想…

rust 自动化测试、迭代器与闭包、智能指针、无畏并发

编写测试可以让我们的代码在后续迭代过程中不出现功能性缺陷问题&#xff1b;理解迭代器、闭包的函数式编程特性&#xff1b;Box<T>智能指针在堆上存储数据&#xff0c;Rc<T>智能指针开启多所有权模式等&#xff1b;理解并发&#xff0c;如何安全的使用线程&#x…

静态路由实验

目录 第一步&#xff1a; 第二步&#xff1a; 端口IP配置方法 环回接口IP配置方法 第三步&#xff1a; 第四步&#xff1a; 第一步&#xff1a; 先进行子网划分 192.168.1.0 24 子网划分 需要13个广播域 5个网段——子网划分 192.168.1.0 24 192.168.1.000 00000 27 192.…

数据结构一:绪论

一、绪论 数据结构是计算机科学中的一门基础课程&#xff0c;研究数据的存储、组织和管理方法&#xff0c;以及在这些数据上进行各种操作的算法和技术。掌握数据结构和算法是编程中非常重要的基础&#xff0c;对于实现高效、可靠的程序至关重要。常见的数据结构包括数组、链表、…

雅思口语考试时长多少分钟?

雅思口语考试是单独考的&#xff0c;对于第一次考雅思的同学来说&#xff0c;了解雅思考试的时长才能更好的备考雅思&#xff0c;那么雅思口语考试时长多久呢&#xff1f; 一、雅思口语考试时长 雅思口语考试时长一般为11-14分钟 第一段约4-5分钟&#xff0c;会面问候一番&am…

java项目之高校信息资源共享平台(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的高校信息资源共享平台。技术交流和部署相关看文末&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框架&…

DynaSLAM 2018论文翻译

Dynaslam:动态场景下的跟踪、建图和图像修复 摘要-场景刚性假设是SLAM算法的典型特征。这种强假设限制了大多数视觉SLAM系统在人口稠密的现实环境中的使用&#xff0c;而这些环境是服务机器人或自动驾驶汽车等几个相关应用的目标。 在本文中&#xff0c;我们提出了一个基于ORB…