动手实现一个简易的 RPC *真的很难吗?no no no, 很简单的,不信你把文章看完(doge)。
RPC(Remote Procedure Call)即 远程过程调用,我简单解释下字面的意思,远程肯定是指要跨机器而不是本机,所以需要用网络编程才能实现,但是不是说只通过网络通信访问到另外的一台主机的应用程序,就可以称为RPC调用了,显然还远远不够。
我的理解的 RPC 是帮助我们屏蔽网络编程的细节,实现调用远程方法就跟调用本地(同一个项目中的方法)是一样的,不需要因为这个方法是远程调用就需要编写很多业务无关的代码。
这就好比在修在小河的桥一样,它连着河的两岸,如果没有桥了,你只能通过划船,绕道等方式才能到达河对面。现在是有桥的,这跟路上走到河对面有啥区别呢?没得区别。所以总结我认为,RPC 的作用就是两个方面:
屏蔽远程调用和本地调用的区别,让我们感觉调项目内的方法一样的
隐藏底层网络的复杂性,让我们更专注于业务逻辑
我们从定义提炼 三个关键词
简单 高效 通用
- 简单:RPC 概念的语义十分清晰和简单,这样建立分布式计算就更容易。
- 高效:过程调用看起来十分简单而且高效。
- 通用:在单机中过程往往是不同算法部分间最重要的通信机制。
但是想想互联网应用的量级越来越大,单台计算机的能力有限,那么需要借助可扩展的集群来完成,具体看下如何 RPC在不同机器之间的完成调用。
看下调用流程
别人游戏开局一只狗,咱们这里就开局简单的画一下 rpc 调用流程,你在心里对这张流程大致有个思路,目的让你很快熟悉它的调用。
咱们就开始了
典型的 RPC 架构可划成三部分:
1)服务提供者(RPC Server):运行在服务端,提供服务接口定义和服务实现类。
2)服务消费者(RPC Client):运行在客户端的,通过远程代理对象调用远程服务。
3)注册中心(Registry):也是运行在服务端,负责把本地服务发布成远程的服务,它也要去管理,提供给服务消费者来使用的。
通过上面的图可以看出,一次简单的 RPC 调用可以分为以下几个步骤:
(1)服务提供者启动后主动向注册中心注册机器ip、端口以及提供的服务列表;
(2)服务消费者在启动时到注册中心获取服务提供方地址列表,在本地缓存一份;
(3)服务消费者通过本地调用的方式调用服务,调用模块收到请求后通过负载均衡策略选取合适的远程服务地址;
(4)协议模块负责把方法、入参等信息进行序列化(编码)成能够进行网络传输的消息体,并将消息通过网络发送给服务端;
(5)服务端收到消息后进行解码(反序列化操作)。
(6)根据解码结果调用本地的服务进行相关处理;
(7)服务端将处理返回的结果进行序列化(编码),并将结果通过网络发送至服务消费者;
(8)服务消费者收到消息后进行解码最终得到结果;
敲黑板:在不同的 RPC 框架实现中步骤 1、2、3的顺序可能有些不同。
RPC 调用分以下两种:
同步调用
客户方等待调用执行完成并返回结果。
异步调用
客户方调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。 若客户方不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。
异步和同步的区分在于是否等待服务端执行完成并返回结果。
RPC 结构拆解
如下图所示。
RPC 组件职责
上面我们进一步拆解了 RPC 实现结构的各个组件组成部分,下面我们详细说明下每个组件的职责划分。
RpcServer
负责导出(export)远程接口
RpcClient
负责导入(import)远程接口的代理实现
RpcProxy
远程接口的代理实现
RpcInvoker
客户方实现:负责编码调用信息和发送调用请求到服务方并等待调用结果返回
服务方实现:负责调用服务端接口的具体实现并返回调用结果
RpcProtocol
负责协议编/解码
RpcConnector
负责维持客户方和服务方的连接通道和发送数据到服务方
RpcAcceptor
负责接收客户方请求并返回请求结果
RpcProcessor
负责在服务方控制调用过程,包括管理调用线程池、超时时间等
RpcChannel
数据传输通道
RPC 服务方通过 RpcServer 去导出(export)远程接口方法,而客户方通过 RpcClient 去引入(import)远程接口方法。 客户方像调用本地方法一样去调用远程接口方法,RPC 框架提供接口的代理实现,实际的调用将委托给代理 RpcProxy 。 代理封装调用信息并将调用转交给 RpcInvoker 去实际执行。 在客户端的 RpcInvoker 通过连接器 RpcConnector 去维持与服务端的通道 RpcChannel, 并使用 RpcProtocol 执行协议编码(encode)并将编码后的请求消息通过通道发送给服务方。
RPC 服务端接收器 RpcAcceptor 接收客户端的调用请求,同样使用 RpcProtocol 执行协议解码(decode)。 解码后的调用信息传递给 RpcProcessor 去控制处理调用过程,最后再委托调用给 RpcInvoker 去实际执行并返回调用结果。
RPC 核心功能
一个完整的商用 RPC 框架有很多功能,最最核心的基本功能就是三个:服务寻址、数据编解码、网络传输。
服务寻址
如果是本地调用,被调用的方法在同一个进程内,操作系统或者是虚拟机可以去地址空间去找;但是在远程调用中,这是行不通的,因为两个进程的地址空间是完全不一样的,肯定也无法知道远端的进程在那。
如果要想实现远程调用,我们需要对服务消费者和服务提供者两者进行约束:
在远程过程调用中所有的函数都必须有一个 ID,这个 ID 在整套系统中是唯一存在确定的。
服务消费者在做远程过程调用时,发送的消息体中必须要携带这个 ID。
服务消费者和服务提供者分别维护一个函数和 ID 的对应表。
当服务消费者需要进行远程调用时,它就查一下这个表,找出对应的 ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码就行。
上面说的可能比较抽象,通俗一点就是服务消费者如何寻找服务提供者,这就是服务寻址。
图片
服务寻址的实现方式有很多种,常见的是:服务注册中心。要调用服务,首先你需要一个服务注册中心去查询对方服务都有哪些实例,然后根据负载均衡策略择优选一。
像 Dubbo 框架的服务注册中心是可以配置的,官方推荐使用 Zookeeper。
我私下用的nacos也行。
数据编解码(序列化和反序列化)
对计算机网络稍微有一点熟悉的同学都知道,数据在网络中传输都是二进制的:01010101010101010,类似这种,只有二进制数据才能在网络间传。
那一个客户端调用远程服务的一个方法,像方法入参这些必然需要转换成二进制才能进行传输,这种将对象转换成二进制流的过程就叫做序列化(编码),学过 JavaIO流 那部分你应该熟悉。
服务端接收到二进制流不能识别,势必要将二进制流转换成对象,这个逆过程就叫做反序列化,也可以叫解码。
常见的RPC序列化协议
XML(Extensible Markup Language)是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点。狭义web service就是基于SOAP消息传递协议(一个基于XML的可扩展消息信封格式)来进行数据交换的。
Hessian是一个动态类型,简洁的,可以移植到各个语言的二进制序列化对象协议。采用简单的结构化标记、采用定长的字节记录值、采用引用取代重复遇到的对象。
JSON(Javascript Object Notation)起源于弱类型语言Javascript, 是采用"Attribute-value"的方式来描述对象协议。与XML相比,其协议比较简单,解析速度比较快。
Protocol Buffers 是google提供的一个开源序列化框架,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。同 XML 相比, Protobuf 的主要优点在于性能高。它以高效的二进制方式存储,比 XML 小 3 到 10 倍,快 20 到 100 倍。
Thrift 既是rpc框架,同时也具有自己内部定义的传输协议规范(TProtocol)和传输数据标准(TTransports),通过IDL脚本对传输数据的数据结构(struct) 和传输数据的业务逻辑(service)根据不同的运行环境快速的构建相应的代码,并且通过自己内部的序列化机制对传输的数据进行简化和压缩提高高并发、 大型系统中数据交互的成本。
网络传输
提起网络传输大家脑海里肯定马上就能想到 TCP/IP四层模型、OSI 七层模型,那通常 RPC 会选择那一层作为传输协议呢?
在回答这个问题前
先来看下 RPC 需要网络传输实现什么样的功能。
客户端的数据经过序列化+编码后,就需要通过网络传输到服务端。网络传输层需要把前面说的函数 ID 和序列化后的参数字节流传给服务端,服务端处理完然后再把序列化后的调用结果传回客户端。
原则上只要能实现上面这个功能的都可以作为传输层来使用,具体协议没有限制。
我们先来看下 TCP 协议,TCP 连接可以是按需连接,需要调用的时候就先建立连接,调用结束后就立马断掉,也可以是长连接,客户端和服务器建立起连接之后保持长期持有,不管此时有无数据包的发送,可以配合心跳检测机制定期检测建立的连接是否存活有效。
由此可见 TCP 的性能确实很好,因此市面上大部分 RPC 框架都使用 TCP 协议,但也有少部分框架使用其他协议,比如 gRPC 用的是 HTTP2 来实现。
敲黑板:
数据编解码和网络传输可以有多种组合方式,比如常见的有:HTTP+JSON, Dubbo 协议+TCP 等。
dubbo
最近刚升为apache*项目的dubbo可以说是java语言中RPC架构最流行的框架。同时Dubbo的文档也是开源软件中写的最详细的文档之一,细看dubbo官方文档。下图是dubbo的整体设计:
duboo分层架构
图例说明:
图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调时链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。
各层说明:
config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory
registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
cluster 路由层(RMI没有这一层,因为直接指定具体服务端或客户端):封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
monitor 监控层(非核心,非必须):RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
protocol 远程调用层(从protocol 远程调用层往下4层可以看成RMI图中的Remote层):封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
上面的dubbo展示了一个完整的带服务治理功能的RPC框架的分层架构。下面我们写一个简易的RPC框架,我把dubbo中的一些非核心层省略掉。具体细节见demo。
省略掉config层,直接通过单例模式获取服务。
保留proxy 服务代理层,客户端采用简单的反射机制实现服务接口的动态代理,demo中对应ServiceProxyClient类;服务端初始化的时候,按一定规则写进Map映射中,这样直接获取服务实例对象即可,类似RMI的skeleton模块,demo中对应ServiceProcessor类。
省略registry 注册中心层,demo服务只有一个实例机器提供,故直接写死ip和端口,在ClientRemoter类中getDataRemote方法中直接写死。
省略cluster 路由层,只有当服务实例有多个时才需要通过算法决定哪个服务实例“接待”请求。
省略monitor 监控层,这个是服务治理需要的,不影响核心流程调用。
保留protocol 远程调用层以下四层 ,统称为remote层。负责请求双方调用协议的约定,序列化、传输。client端的remote层对应demo中ClientRemoter类,将请求服务接口转化成二进制通过socket发送给服务端;服务端的remote层对应demo中的ServerRemoter类,负责客户端的二进制按照协议转化成本地的方法调用,然后又将返回结果通过按照协议翻译成二进制通过socket送给客户端。
解决三个小疑问
通过*你能学到什么?
敲黑板:首先强调一下*的目的不是为了放在生产上面去用,肯定会有很多缺陷是不得行,而是造*以实战经验来促进你高效来学习,让你掌握零散的知识点连成一条线。
这次我会深入研究带大家从零开始撸一个 RPC 框架,如果在编写过程中如果呈现问题,欢迎小伙伴们到 github(id: Datalong)提交你的issue,会及时修正。
[过程] 这个词估计你学C的时候听说过,也不知道最开始谁翻译的,个人觉得程序,服务,方法,这几个都比[过程]要好。
为了后面能够好理解,我们统一一下术语,干脆叫,远程服务调用,服务包括是指程序接口,方法等一类资源。
是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC是一种服务器-客户端(Client/Server)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。
RPC是一种进程间通信的模式,程序分布在不同的地址空间里。如果在同一主机里,RPC可以通过不同的虚拟地址空间(即便使用相同的物理地址)进行通讯,而在不同的主机间,则通过不同的物理地址进行交互。许多技术(通常是不兼容)都是基于这种概念而实现的。
听懂了些,好像又没完全懂,那说人话吧。比如说两台服务器:A 和 B,一个应用部署在 A 服务器上,想要调用 B 服务器上某个应用提供的函数/方法。
由于跨应用服务器,你不能直接调用,需要通过网络来表达调用的语意和传达调用的数据,这种调用的方式就是RPC,如下图所示。
RPC 服务方通过 RpcServer 去导出远程接口方法,而客户端通过 RpcClient 去引入远程接口方法。那客户端像调用本地方法一样去调用远程接口方法,RPC框架提供接口的代理实现,实际的调用将委托给代理 RpcProxy。 代理封装调用信息并将调用转交给 RpcInvoker 去实际执行。 在客户端的 RpcInvoker 通过连接器 RpcConnector 去维持与服务端的通道 RpcChannel, 并使用 RpcProtocol 执行协议编码(encode)并将编码后的请求消息通过通道发送给服务方。
RPC 服务端接收器 RpcAcceptor 接收客户端的调用请求,同样使用 RpcProtocol 执行协议解码(decode)。 解码后的调用信息传递给 RpcProcessor 去控制处理调用过程,最后再委托调用给 RpcInvoker 去实际执行并返回调用结果。
LPC & IPC
既然存在 RPC 这种远程过程调用,必然会有与之对应的本地过程调用了。本地过程调用在不同的操作系统中,叫法不同,使用方式也不太一样。在Windows编程中,称为LPC;在linux编程中,更习惯称之为IPC,即进程间通信,这不就绕回来了。
但是,不管如何,其本质上就是本地机器上的不同进程之间通信协作的调用方式。
还有哪些RPC
RPC家族中,RMI是Java制定的远程通信协议。而后,基本上RPC框架都或多或少有RMI的影子(当然,其实主要是RPC本身的实现方式就是这样子的)。RMI既然是Java的标准RPC组件,那必然其他编程语言就无法使用了但是,作为服务化的组件,如果没有服务治理来完成大规模应用集群中服务调用管理工作,则运维工作则是非常繁重的,因此类似 dubbo 这种包含服务治理的 RPC 组件出现。
也就是RPC本身的实现方式。在JDK 1.2的时候,引入到Java体系的。当应用比较小,性能要求不高的情况下,使用RMI还是挺方便快捷的。下面先看看RMI的调用流程。
RMI服务调用结构图
RMI调用时序图
概念说明:
stub(桩):stub实际上就是远程过程在客户端上面的一个代理proxy。当我们的客户端代码调用API接口提供的方法的时候,RMI生成的stub代码块会将请求数据序列化,交给远程服务端处理,然后将结果反序列化之后返回给客户端的代码。这些处理过程,对于客户端来说,基本是透明无感知的。
remote:这层就是底层网络处理了,RMI对用户来说,屏蔽了这层细节。stub通过remote来和远程服务端进行通信。
skeleton(骨架):和stub相似,skeleton则是服务端生成的一个代理proxy。当客户端通过stub发送请求到服务端,则交给skeleton来处理,其会根据指定的服务方法来反序列化请求,然后调用具体方法执行,最后将结果返回给客户端。
registry(服务发现):借助JNDI发布并调用了rmi服务。实际上,JNDI就是一个注册表,服务端将服务对象放入到注册表中,客户端从注册表中获取服务对象。rmi服务,在服务端实现之后需要注册到rmi server上,然后客户端从指定的rmi地址上lookup服务,调用该服务对应的方法即可完成远程方法调用。registry是个很重要的功能,当服务端开发完服务之后,要对外暴露,如果没有服务注册,则客户端是无从调用的,即使服务端的服务就在那里。
目前大规模使用的 RPC 框架有:
- 阿里 Dubbo
- 谷歌 gRPC
- Apache Thrift
- 微博 Montan
- 蚂蚁金服 SOTARpc
- SpringCloud(类似)
- ......等
RMI
作为Java自带的官方 RPC组件,单独介绍;然后我们来看看通用RPC实现结构。
全称是Remote Method Invocation,也就是远程方法调用。在JDK 1.2的时候,引入到Java体系的。当应用比较小,性能要求不高的情况下,使用RMI还是比较快捷的。
为什么你需要学习造*
从零开始,手写一个RPC, 跟随这篇前提知识做铺垫以及数个迭代版本的代码,由简陋到逐渐完备,目的让所有人都能看懂并且写出一个RPC框架。
本文档与代码都是本人第一次手写RPC的心路历程,会有理解的偏差与代码上的不完善,但更是由于这样,有着与新手对同样问题的疑惑,也许会使新手更容易理解这样做的缘故是啥。
另外期待与你的合作:代码,帮助文档甚至rpc框架功能的完备
学习建议:
一定要实际上手敲代码
每一版本都有着对应独立的代码与文档,结合来看
每一版本前有一个背景知识,建议先掌握其相关概念再上手码
每一个版本都有着要解决的问题与此版本的最大痛点,带着问题去写代码,并且与上个版本的代码进行比较差异
如果你认真去学下去,可以掌握下面的技术:
basic
- 1 JavaSE就不说了
- 2 使用自定义注解,学完可以了解注解的基本运行机制;
- 3 会用到反射机制
- 4 学会如何改配置项,并绑定到 bean;
- 5 知道监听 spring 容器的事件;
hard
- 1 底层网络基于 netty, 学完 netty 入门没有问题;
- 2 会用到动态代理技术;
- 3 服务注册基于 zookeeper, 学完 zk 入门没有问题;
- 4 教你如何定义一个xxx-spring-boot-starter, 了解spring boot自动配置机制;
还有就是底层的网络是基于 netty, 学完后 netty 入门没有问题;
有没有一点心动呢?!
这篇文章就当做一个引入和前言吧,主要是为了增强大家的信心,肯定是能学到东西的。
为什么需要RPC?
上面简单解答了为啥咱们要学 rpc *,那铁子们就问为什么需要 RPC?一项新技术肯定是为了改善技术工具或者重新设计解决具体的业务及架构方面的问题。
不得不说单体架构,这种就是把应用程序的所有功能都打成一个部署包。
从上面的架构图总结一下单体架构的特点:
- 所有的功能集成在一个项目工程里面;
- 通过分层架构,上层调用下层接口,所有的调用都在应用内完成;
- 所有的功能打成一个 war 包放在服务器上run;
- 应用和数据库是分开部署;
如果网站流量很小时,只需一个应用,把所有功能部署在一起,来减少部署结点和运维成本。如果流量稍微大一点可以通过部署应用集群和数据库集群来提高系统的性能。
这种肯定逐渐淘汰,业务逐渐复杂,应用的外部流量压力骤增,团队成员会越来越多,这时的抗压能力会逐渐暴露。
具体聊下不足
缺点一:高耦合
某个模块出现死循环,导致内存溢出,上下应用都会挂掉。
缺点二:扩展性很差
系统的扩容只能针对应用进行扩容,不能做到对某个功能模块进行扩容,扩容后必然带来资源浪费的问题
缺点三:持续交付周期拉长
单体应用变大后构建和部署时间也跟着来延长,对于频繁部署不利,阻碍项目持续交付。即使在仅仅更改了一行
网站应用的规模不断扩大,传统的垂直应用架构早已经没法满足了,分布式服务架构以及流动计算架构也在行业应用很广泛,但是在这背景就需要个治理系统确保架构有条不紊的继续演进。
单一应用框架(也叫ORM)
当网站流量很小,它只需一个应用,将所有功能如下单,支付等都部署在一起,减少了部署节点和成本。
- 缺点就是:单一的系统,使得在开发途中,占用的资源会越来越多,并且是随着流量的增加越来越难以维护
垂直应用框架(MVC)
垂直应用架构解决了单一应用架构所面临的扩容问题,流量能够分散到各个子系统当中,且系统的体积可控,一定程度上降低了开发人员之间协同以及维护的成本,提升了开发效率。
- 缺点:但是在垂直架构中相同逻辑代码需要不断的复制,不能复用。
分布式应用架构(RPC)
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心
流计算架构(SOA)
它也叫服务治理结构,随着服务化的进一步发展,服务越来越多,服务之间的调用和依赖关系也越来越复杂,诞生了面向服务的架构体系(SOA),也因此衍生出了一系列相应的技术,如对服务提供、服务调用、连接处理、通信协议、序列化方式、服务发现、服务路由、日志输出等行为进行封装的服务框架
Duboo基本功能
在RPC框架中主要有三个角色:Provider、Consumer和Registry。如下图所示:
节点角色说明:
- Server: 暴露服务的服务提供方。
- Client: 调用远程服务的服务消费方。
- Registry: 服务注册与发现的注册中心。
- 远程通讯
- 基于接口方法的透明远程过程调用
- 负载均衡
- 服务注册中心
此RPC的最大痛点:
只能调用服务端Service唯一确定的方法,如果有两个方法需要调用呢?(Reuest需要抽象)
返回值只支持User对象,如果需要传一个字符串或者一个Dog,String对象呢(Response需要抽象)
客户端不够通用,host,port, 与调用的方法都特定(需要抽象)
RPC 采用 C/S模式。请求程序就是个客户端,而服务提供程序那他就是个服务器。第一步,客户端调用进程发送一个有进程参数的调用信息给服务进程,然后它就在那里应答信息。在服务端,进程保持睡眠状态直到调用信息全部送达才行的。当一个调用信息到了,服务器获得进程参数,然后计算结果,发送答复信息,等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行会继续的进行,周而复始。
我在成都打电话给在深圳的好基友,问他今天深圳天气怎么样(此时我是客户端(也是服务消费者),我发出请求),朋友听到我的提问后把答案告诉我(此时朋友是服务端(既服务提供者),响应答案给我)。 我没有去过深圳,但是我打一下电话,就晓得了今天深圳的天气,说的就是这意思。
它需要解决那些问题(了解下):
通讯问题:
是通过在客户端和服务器之间建立 TCP 连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接的,调用结束后就断掉了呗,也可以是长连接,多个远程过程调用共享同一个连接。
寻址问题:
A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称名称是什么,这样才能完成调用。比如基于Web服务协议栈的RPC,就要提供一个endpoint URI,或者是从UDDI服务上查找。如果是RMI调用的话,还需要一个RMI Registry来注册服务的地址。
序列化和反序列化:
当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制的形式,也就是序列化(Serialize)或编组(marshal),通过寻址和传输将序列化的二进制发送给B服务器。
同理,B服务器接收参数要将参数反序列化。B服务器应用调用自己的方法处理后返回的结果也要序列化给A服务器,A服务器接收也要经过反序列化的过程。