• RPC通信基本原理 -- 浅析RPC远程过程调用基本原理


    一、RPC基本概念

    1.1、RPC简介

    • RPC 的全称是 Remote Procedure Call是一种进程间通信方式。
    • RPC只是一个概念 而不是具体的协议或框架。
    • 它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。
    • 即程序员无论是调用本地的还是远程的,本质上编写的调用代码基本相同。
    • 它可以有不同的实现方式。如RMI(远程方法调用)、Hessian、Http invoker等。另外,RPC是与语言无关的。

    在这里插入图片描述
    如上图所示,假设Computer1在调用sayHi()方法,对于Computer1而言调用sayHi()方法就像调用本地方法一样,调用 –>返回。但从后续调用可以看出Computer1调用的是Computer2中的sayHi()方法,RPC屏蔽了底层的实现细节,让调用者无需关注网络通信,数据传输等细节。

    1.2、RPC架构

    一个完整的RPC架构里面包含了四个核心的组件,分别是Client,Client Stub,Server以及Server Stub,这个Stub可以理解为存根。

    • 客户端(Client),服务的调用方。
    • 客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
    • 服务端(Server),真正的服务提供者。
    • 服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。

    1.3 RPC调用过程

    在这里插入图片描述

    • (1) 客户端(client)以本地调用方式(即以接口的方式)调用服务;

    • (2) 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);

    • (3) 客户端通过sockets将消息发送到服务端;

    • (4) 服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);

    • (5) 服务端存根( server stub)根据解码结果调用本地的服务;

    • (6) 本地服务执行并将结果返回给服务端存根( server stub);

    • (7) 服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);

    • (8) 服务端(server)通过sockets将消息发送到客户端;

    • (9) 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息发序列化);

    • (10) 客户端(client)得到最终结果。

    RPC的目标是要把2、3、4、7、8、9这些步骤都封装起来。

    注意:无论是何种类型的数据,最终都需要转换成二进制流在网络上进行传输,数据的发送方需要将对象转换为二进制流,而数据的接收方则需要把二进制流再恢复为对象。

    1.4、作用及优势

    在这里插入图片描述

    1、作用:
    • 1、使服务解耦
    • 2、分布式设计
    • 3、部署灵活
    • 4、容易扩展
    2、优点:
    • 1、一般使用长链接,不必每次通信都要3次握手,减少网络开销
    • 2、一般都有注册中心,有丰富的监控管理
    • 3、发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作
    • 4、协议私密,安全性较高
    • 5、rpc 能做到协议更简单内容更小,效率更高
    • 6、rpc是面向服务的更高级的抽象,支持服务注册发现,负载均衡,超时重试,熔断降级等高级特性

    二、RPC架构解析

    2.1、RPC架构分析

    RPC的定义:远程过程调用,调用远程就像调用本地某个过程或函数。通过这一句我们是不是感受不到IP等地址信息的存在了,请求头是不是也应该存在了,编码什么的也不复存在了吧,网络传输不应该让我们开发者感受到了吧…中间的所有跟调用过程相关的是不是都是RPC内部应该帮我们做的,那么接下来我们看一下RPC中具体应该有哪些模块,每个模块又有什么存在的意义

    在这里插入图片描述

    - 
    - 1、调用模块:通过该模块对数据进行封装、对请求进行负载、超时判断、熔断和限流等等
    - 2、序列化	: 通过该模块对数据进行序列化,转成可通过网络传输的格式,并在没收到数据后反序列化成可读的格式
    - 3、协议编码:这里的编码是对数据的编码和解码
    - 4、网络传输:两个服务之间信息的传输
    - 5、服务发现:从注册中心订阅服务
    - 6、服务注册:将服务提供者提供的服务注册到注册中心供消费者使用
    - 7、注册中心:在高可用的生产环境中,服务一般都以集群方式提供服务,集群里面的IP等重要参数信息可能随时会发生变化,节点也可能会动态扩缩容,客户端需要能够及时感知服务端的变化,获取集群最新服务节点的连接信息,而这些变化要求是要对调用方应用无感知的
    - 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.2、RPC框架的实现

    上面介绍了RPC的核心原理:RPC能够让本地应用简单、高效地调用服务器中的过程(服务)。它主要应用在分布式系统。如Hadoop中的IPC组件。但怎样实现一个RPC框架呢?

    2.1.1、从下面几个方面思考,仅供参考:
    • 1.通信模型:假设通信的为A机器与B机器,A与B之间有通信模型,在Java中一般基于BIONIO;。

    • 2.过程(服务)定位:使用给定的通信方式,与确定IP与端口及方法名称确定具体的过程或方法;

    • 3.远程代理对象:本地调用的方法(服务)其实是远程方法的本地代理,因此可能需要一个远程代理对象,对于Java而言,远程代理对象可以使用Java的动态对象实现,封装了调用远程方法调用;

    • 4.序列化,将对象名称、方法名称、参数等对象信息进行网络传输需要转换成二进制传输,这里可能需要不同的序列化技术方案。如:protobuf,Arvo

    2.1.2、hdfs的RPC基本流程:

    在这里插入图片描述
    hdfs的rpc流程基本如上,其中的关键就是获得NameNode代理对象。

    2.3、JAVA中代理方式

    2.2.1、什么是代理模式
    • 1.定义:为其他对象提供一种代理以控制对这个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

    • 2.例子:最开始接触JDBC操作数据库的时候,业务层每一个方法都需要做以下几件事

      (1)打开数据库连接

      (2)执行我们想要的操作

      (3)关闭数据库连接

    此时,我的核心业务是第(2)步,其余两步为辅助业务。当核心业务与辅助业务写在了一个方法中时。会出现以下问题:

     ① 代码业务冗余
     ② 开关数据库连接大量的重复
    
    • 1
    • 2
    • 3.通过代理模式,我们可以抽取出核心业务与辅助业务
    2.2.2、静态代理与动态代理

    对 JAVA 来说就是使用代理!java代理有两种方式

    • (1)静态代理

    先定义一个公共接口,里面包括了可以通过RPC调用的方法列表。而且被代理对象以及对象本身都需要实现该接口
    尽管字节码生成方式实现的代理更为强大和高效,但代码不易维护,大部分公司实现RPC框架时还是选择动态代理方式

    • (2)动态代理

    先定义一个公共接口,里面包括了可以通过RPC调用的方法列表。被代理对象以及对象本身都不需要实现该接口。而是通过匿名内部类+反射的机制实现。hadoop就是使用这种方式。

    • 1.实现InvocationHandle接口,该接口所在位置为:java.lang.reflect.InvocationHandler

    • 2.该接口中有一个方法Object invoke(Object proxy, Method method, Object[] args)

      (1)Ojbect proxy:表示需要代理的对象

      (2)Method method:表示要操作的方法

      (3)Object[] args:method方法所需要传入的参数(可能没有为,null.也可能有多个)

    • 3.代理类 java.lang.reflect.ProxyProxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类)

    • 4.调用代理类中的方法

    
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) 
    	throws IllegalArgumentException
    
    参数:
    
    	(1)loader - 定义代理类的类加载器
    
    	(2)interfaces - 代理类要实现的接口列表
    
    	(3)h - 指派方法调用的调用处理程序
    
    返回:一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.4、hadoop RPC框架的例子

    Server:

    
    /*
    MyInterface.java	
    */
    package Server;
    
    import org.apache.hadoop.ipc.VersionedProtocol;
    
    public interface MyInterface extends VersionedProtocol {
        public static long versionID = 1001; //这个是标记RPC的client和server对应的标记
        public String helloWorld(String name);
    }
    
    
    /*
    MyImpl.java
    */
    package Server;
    
    import org.apache.hadoop.ipc.ProtocolSignature;
    
    import java.io.IOException;
    
    public class MyImpl implements MyInterface{
        /*这是实际目标*/
    
    	//重写我们在上面接口自定义的方法
        @Override
        public String helloWorld(String name) {
            return "hello," + name;
        }
    
        //返回版本号
        @Override
        public long getProtocolVersion(String s, long l) throws IOException {
            return MyInterface.versionID;
        }
    
        //返回签名信息
        @Override
        public ProtocolSignature getProtocolSignature(String s, long l, int i) throws IOException {
            return new ProtocolSignature(MyInterface.versionID, null);
        }
    }
    
    
    /*
    MyRpcServer.java
    */
    package Server;
    
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.ipc.RPC;
    
    import java.io.IOException;
    
    public class MyRpcServer {
        public static void main(String[] args) {
            //建立rpc通道对象
            RPC.Builder builder = new RPC.Builder(new Configuration());
    
            //设置RPC server参数
            builder.setBindAddress("localhost");
            builder.setPort(7788);
    
            //部署程序,传入实现server业务代码的接口定义,这里面包括了该rpcserver 可以提供的方法,也就是给client调用的方法列表,通过反射的方式引入类对象
            builder.setProtocol(MyInterface.class);
    
            //部署接口的实现类对象
            builder.setInstance(new MyImpl());
    
            //开启server
            try {
                RPC.Server server = builder.build();
                server.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    client:

    
    /*
    MyRpcClient.java
    */
    package Client;
    
    import Server.MyInterface;
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.ipc.RPC;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    
    public class MyRpcClient {
        public static void main(String[] args) {
            try {
                //获取代理对象,设置接口类对象、RPC通信的versionID,rpcserver地址、configuration对象
                MyInterface proxy = RPC.getProxy(
                        MyInterface.class,
                        MyInterface.versionID,
                        new InetSocketAddress("localhost", 7788),
                        new Configuration());
                
                //获得代理对象之后,就可以通过proxy调用接口类中的方法,这里就调用上面定义的 helloWorld对象
                System.out.println(proxy.helloWorld("king"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    下面启动server端和client端,执行结果为:

    //server:可以看到显示监听端口 7788
    [main] INFO org.apache.hadoop.ipc.CallQueueManager - Using callQueue: class java.util.concurrent.LinkedBlockingQueue queueCapacity: 100 scheduler: class org.apache.hadoop.ipc.DefaultRpcScheduler
    [Socket Reader #1 for port 7788] INFO org.apache.hadoop.ipc.Server - Starting Socket Reader #1 for port 7788
    [IPC Server Responder] INFO org.apache.hadoop.ipc.Server - IPC Server Responder: starting
    [IPC Server listener on 7788] INFO org.apache.hadoop.ipc.Server - IPC Server listener on 7788: starting
    
    //client:  我们传入“King”作为参数,能够争取执行
    hello,king
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    如何提高网站权重
    穿透、无底洞、雪崩、击穿 解决方案?
    利用SOP完成新客户培育
    得物技术复杂 C 端项目的重构实践
    亚马逊云科技 Lambda 运行selenium
    亚信笔试题卷以及答案.docx
    数据库中的存储过程、游标、触发器与常用的内置函数
    第五课 算术运算
    字字珠玑!GitHub爆赞的网络协议手册,被华为大佬指定内部必学?
    如何安装React的第一个脚手架
  • 原文地址:https://blog.csdn.net/GoodburghCottage/article/details/126762029