【.com原稿】RPC(RemoteProcedureCall):远程过程调用,是一种远程计算机程序通过网络请求服务,无需了解底层的思想网络技术。RPC是一种技术思想,而不是一种规范或协议。常见的RPC技术和框架包括:应用层服务框架:阿里的Dubbo/Dubbox、谷歌的gRPC、SpringBoot/SpringCloud。远程通信协议:RMI、Socket、SOAP(HTTPXML)、REST(HTTPJSON)。通信框架:MINA和Netty。目前流行的开源RPC框架还是比较多的,包括阿里巴巴的Dubbo、Facebook的Thrift、谷歌的gRPC、推特的Finagle等。下面重点介绍三种:gRPC:是谷歌发布的一款开源软件,基于最新的HTTP2.0协议,支持多种常用编程语言。RPC框架基于HTTP协议实现,底层使用Netty框架的支持。Thrift:Facebook开源的RPC框架,主要是跨语言服务开发框架。用户只需对其进行二次开发,应用对底层RPC通信透明。但是,用户学习领域特定语言的特性仍然需要一定的成本。Dubbo:是阿里集团开源的一个非常著名的RPC框架,广泛应用于很多互联网公司和企业应用中。协议和序列化框架都可以插件,这是一个非常有特色的特性。一个完整的RPC框架包括典型RPC使用场景下的服务发现、负载、容错、网络传输、序列化等组件。“RPC协议”规定了程序如何进行网络传输和序列化。图1:完整的RPC架构图下面是Dubbo的设计架构图,层次清晰,功能复杂:图2:Dubbo架构图RPC核心功能RPC的核心功能是指实现一个RPC最重要的功能模块,即上图“RPC协议”部分:图3:RPC核心功能一个RPC核心功能主要由五部分组成,分别是:客户端、客户端Stub、网络传输模块、服务端Stub、服务端等。图4:RPC核心功能图下面介绍核心RPC框架的重要组成部分:Client:服务调用者。客户端存根(ClientStub):存储服务器的地址信息,将客户端的请求参数数据信息打包成网络报文,然后通过网络传输发送给服务器。ServerStub:接收并解包客户端发送的请求报文,然后调用本地服务进行处理。服务器(Server):真正的服务提供者。网络服务:底层传输,可以是TCP或HTTP。Python自带RPCDemoServer.py:fromSimpleXMLRPCServerimportSimpleXMLRPCServerdeffun_add(a,b):totle=a+breturntotleif__name__=='__main__':s=SimpleXMLRPCServer(('0.0.0.0',8080))#Openxmlrpcservers.register_function(fun_add)#registerFunctionfun_addprint"serverisonline..."s.serve_forever()#开循环等待Client.py:fromxmlrpclibimportServerProxy#Importxmlrpclibpackages=ServerProxy("http://172.171.5.205:8080")#定义xmlrpc客户端打印。fun_add(2,3)打开服务器:打开客户端:Wireshark抓包分析流程客户端到服务器:客户端IP:172.171.4.176服务器IP:172.171.5.95通讯采用HTTP协议,XML文件传输格式。传输的字段包括:方法名methodName,两个参数2、3。图5:Request抓包服务器返回的结果,字段返回Value,结果为5:图6:Response抓包使用HTTP协议在这两次网络传输中,HTTP协议之间有一次TCP三次握手,断开连接时HTTP协议有TCP四次握手。图7:基于HTTP协议的RPC连接过程的详细调用过程。带有Python自带RPC的Demo小程序的实现过程,过程和角色划分可以用下图来表示:图8:RPC调用的详细流程图。RPC调用的过程如下:服务消费者(Clientclients)通过本地调用调用服务。客户端存根接收到调用请求后,负责将方法、输入参数等信息序列化(组装)成可以在网络上传输的消息体。客户端存根(ClientStub)找到远程服务地址,通过网络将消息发送给服务器。服务器存根(ServerStub)收到消息后进行解码(反序列化)。服务器存根(ServerStub)根据解码结果调用本地服务进行相关处理。服务器(Server)本地服务业务处理。处理结果返回给服务器存根(ServerStub)。服务器存根(ServerStub)将结果序列化。服务器存根(ServerStub)通过网络将结果发送给消费者。客户端存根(ClientStub)接收消息并对其进行解码(反序列化)。服务消费者获得最终结果。RPC核心功能实现RPC的核心功能主要由五个模块组成。如果要自己实现一个RPC,最简单的方法就是实现三个技术点,即:服务寻址数据流的序列化和反序列化NetworkTransportServiceAddressing服务寻址可以使用CallID映射。在本地调用中,函数体是直接通过函数指针指定的,但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不同的。所以在RPC中,所有的函数都必须有自己的ID。此ID在所有进程中是唯一确定的。当客户端进行远程过程调用时,它必须附加此ID。那么我们还需要在客户端和服务端分别维护一个对应的函数和CallID表。当客户端需要进行远程调用时,通过查表找到对应的CallID,然后发送给服务器端。服务器端也会查表,确定客户端需要调用的函数,然后执行对应的调用ID。函数的代码。实现方式:服务注册中心。要调用一个服务,首先你需要一个服务注册中心来查询对方的服务有哪些实例。Dubbo的服务注册中心是可配置的,官方推荐使用Zookeeper。实现案例:RMI(RemoteMethodInvocation,远程方法调用)是RPC本身的实现。图9:RMI架构图Registry(服务发现):借助JNDI发布和调用RMI服务。JNDI其实就是一个注册中心,服务端将服务对象放入注册中心,客户端从注册中心获取服务对象。服务器端实现RMI服务后,需要向RMIServer注册,然后客户端会从指定的RMI地址中查找该服务,并调用该服务对应的方法来完成远程方法调用。注册表是一个非常重要的功能。服务端开发服务后,必须要对外暴露。如果没有服务注册,即使服务端有服务,客户端也无法调用。序列化和反序列化客户端如何将参数值传递给远程函数?在本地调用中,我们只需要将参数压入栈中,然后让函数自己去栈上读取即可。但是在远程过程调用中,客户端和服务端是不同的进程,不能通过内存传递参数。这时,客户端需要先将参数转换成字节流,传给服务端后,再将字节流转换成自己可以读取的格式。只有二??进制数据可以在网络上传输。序列化和反序列化的定义是:将一个对象转换为二进制流的过程称为序列化将二进制流转换为对象的过程称为反序列化这个过程称为序列化和反序列化变化。同样,从服务器返回的值也需要序列化和反序列化。网络传输网络传输:远程调用常在网络上使用,客户端和服务器通过网络连接。所有数据都需要通过网络传输,因此需要网络传输层。网络传输层需要将CallID和序列化的参数字节流式传输到服务器,然后将序列化的调用结果传回给客户端。只要能做到这两点,就可以作为传输层使用。所以它使用的协议其实是没有限制的,只要能完成传输即可。虽然大多数RPC框架使用TCP协议,但也可以使用UDP,而gRPC只是使用HTTP2。TCP连接是最常见的。基于TCP的连接简单分析:通常,TCP连接可以是按需连接(需要调用时先建立连接,调用结束后立即断开),也可以是一个长连接(client与server建立连接后,会保持很长时间,不管此时是否有数据包发送,可以配合心跳检测机制,周期性检测是否有数据包被发送)已建立的连接是否存在),多个远程过程调用共享同一个连接。因此,要实现一个RPC框架,只需要实现以下三点:调用ID映射:可以直接使用函数字符串,也可以使用整型ID。映射表一般是哈希表。序列化和反序列化:可以自己写,也可以使用Protobuf或FlatBuffers。网络传输库:可以自己写Socket,也可以用Asio、ZeroMQ、Netty之类的。RPC核心的网络传输协议在第三节中说明了要实现RPC,需要选择一种网络传输方式。图10:网络传输RPC中有多种可选的网络传输方式,可以选择TCP协议、UDP协议、HTTP协议。每个协议对整体性能和效率都有不同的影响。如何选择正确的网络传输协议?首先,我们必须了解各种传输协议在RPC中是如何工作的。基于TCP协议的RPC调用在服务调用者和服务提供者之间建立Socket连接,服务调用者通过Socket将要调用的接口名、方法名和参数序列化后传递给服务提供者。提供者反序列化,然后使用反射调用相关方法。***将结果返回给服务的调用者,整个基于TCP协议的RPC调用大致如此。但是在示例应用中,会进行一系列的封装,比如RMI就是在TCP协议上传输可序列化的Java对象。调用这个基于HTTP协议RPC的方法更像是访问一个网页,只是它的返回结果更简单更简单。大致过程是:服务的调用者向服务的提供者发送请求。这个请求的方法可以是GET、POST、PUT、DELETE等中的一种,服务的提供者可以根据不同的请求方法来做。不同的处理,或者一个方法只允许某个请求方法。具体调用方法是根据URL调用方法,方法需要的参数可能是服务调用方传输的XML数据或JSON数据的解析结果,最终返回JSON或XML的数据结果.由于有很多开源的web服务器,比如Tomcat,所以比较容易实现,就像做web项目一样。将这两种方式与基于TCP协议实现的RPC调用进行比较,由于TCP协议处于协议栈的下层,可以更加灵活的自定义协议字段,减少网络开销,提高性能,获得更大的吞吐量和并发。但需要更多关注底层的复杂细节,实现成本较高。同时,针对不同的平台,如Android、iOS等,需要重新开发不同的工具包来发送请求并进行相应的分析。工作量大,难以快速响应和满足用户需求。基于HTTP协议的RPC可以使用JSON和XML格式的请求或响应数据。而JSON和XML是常见的格式标准(使用HTTP协议也需要序列化和反序列化,但这不是协议的内容,成熟的web程序已经对内容进行了序列化),开源的解析工具已经相当成熟,它对其进行二次开发将非常方便简单。但是由于HTTP协议是上层协议,发送同样内容的信息,HTTP协议传输占用的字节数会比TCP协议传输占用的字节数高。因此,在相同的网络下,通过HTTP协议传输相同内容的效率会低于基于TCP协议的数据效率,信息传输的时间也会更长。当然,压缩数据可以缩小这个差距。利用RabbitMQ的RPC架构,在OpenStack中服务之间使用RESTfulAPI调用,通过RPC调用服务内部的各个功能模块。正是因为使用RPC来解耦服务内部的功能模块,OpenStack服务才具有扩展性强、耦合度低的优势。在OpenStack的RPC架构中,加入了消息队列RabbitMQ来保证RPC在消息传递过程中的安全性和稳定性。下面我们来分析一下如何使用OpenStack中的RabbitMQ来实现RPC调用。RabbitMQ简介以下摘自知乎:初学者,我们以饭店为例,解释一下这三者是什么。不完美,但应该足以说明三者的区别。RPC:假设你是一家餐厅的服务员,顾客向你点菜,但你不会做饭,所以你收集顾客要的东西,告诉后厨做顾客点的菜。这叫做RPC(remoteprocedurecall),因为厨房里的厨师相对于服务员来说是另外一个人(在计算机世界里,是Remote机器上的进程)。厨师做的菜就是RPC的返回值。任务队列和消息队列:本质上都是队列,这里只举任务队列的例子。假设餐厅在高峰期客人很多,而厨师却很少,所以服务员只好将点单按顺序摆在厨房的桌子上,让厨师一一道来。这堆订单就是任务队列厨师们每做完一道菜,就从桌上的订单中取出另一道菜,继续做菜。角色分享如下图所示:图11:RPC中的RabbitMQ角色使用RabbitMQ的好处:同步变异步:可以使用线程池将同步变异步,缺点是需要实现线程汇集自己,并有很强的耦合性。使用消息队列可以轻松地将同步请求转换为异步请求。低内聚高耦合:解耦,减少强依赖。流量削峰:设置通过消息队列请求的最大值,超过阈值的丢弃或转到错误接口。网络通信性能提升:TCP的创建和销毁代价高昂,创建3次握手,销毁4次握手,数万条链路在高峰期会造成巨大的资源浪费,运营商处理的TCP数量系统每秒也是有限的。如果数量有限,势必造成性能瓶颈。RabbitMQ使用通道通信而不是TCP直接通信。一个线程有一个通道,多个线程有多个通道,共享一个TCP连接。一个TCP连接可以容纳第一个通道(如果硬盘容量足够),不会造成性能瓶颈。RabbitMQ的三种开关RabbitMQ使用Exchange(开关)和Queue(队列)来实现消息队列。RabbitMQ中一共有三种类型的开关,每一种都有着截然不同的特点。基于这三种开关类型,OpenStack实现了两种RPC调用方式。首先简单介绍一下这三个开关。图12:RabbitMQ架构图①广播交换机类型(Fanout)这种类型的交换机不分析接收到的消息中的RoutingKey,默认将消息转发给交换机绑定的所有队列。图13:Broadcastswitch②Directswitch类型(Direct)这种类型的switch需要精确匹配RoutingKey和BindingKey,比如消息的RoutingKey=Cloud,那么消息只能转发给BindingKey=Cloud到消息队列。图14:直接交换③主题交换(TopicExchange)这种类型的交换通过将消息的RoutingKey与BindingKey的模式进行匹配,将消息转发给所有满足绑定规则的队列。BindingKey支持通配符,“*”匹配一个词组,“#”匹配多个词组(包括零个)。图15:专题切换注:以上四张图片来自博客园,如有侵权请联系作者:https://www.cnblogs.com/dwlsxj/p/RabbitMQ.html。当producer发送消息RoutingKey=F.C.E时,此时只有Queue1满足,所以会路由到Queue。如果RoutingKey=A.C.E,则同时路由到Queue1和Queue2。如果RoutingKey=A.F.B,则只会向Queue2发送一条消息。Nova基于RabbitMQ实现了两种RPC调用:RPC.CALL(调用)RPC.CAST(通知)其中RPC.CALL是基于请求和响应的方式,RPC.CAST只提供单向请求。这两种RPC调用方式在NovaTypical应用场景中都可用。RPC.CALLRPC.CALL是一个双向通信过程,即RabbitMQ接收消息生产者产生的系统请求消息,消息消费者处理后将系统相应的结果反馈给调用程序。图16:RPC.CALL示意图用户通过Dashboard创建虚拟机,接口消息封装后发送给NOVA-API。NOVA-API作为消息生产者,以RPC.CALL方式通过topicexchange将消息转发到消息队列。此时,Nova-Compute作为消息消费者,接收到信息,通过底层虚拟化软件执行相应虚拟机的启动过程。用户虚拟机启动成功后,Nova-Compute作为消息生产者,将虚拟机启动成功响应消息通过Directswitch和对应的消息队列反馈给Nova-API。此时,Nova-API作为消息消费者接收消息,通知用户虚拟机启动成功。RPC.CALL的工作原理如下:图17:RPC.CALL具体实现图工作流程:客户端创建Message时,指定reply_to队列名和correlation_id来标记调用者。服务器通过队列接收消息。调用函数处理,然后返回。返回的队列是reply_to指定的队列,带有correlation_id。返回消息到达客户端,客户端根据correlation_id判断哪个函数调用返回。如果有多个线程同时进行远程方法调用,那么在Client和Server之间建立的Socket连接上,双方发送的消息就会很多,而且顺序可能是随机的。服务器处理完结果后,将结果消息发送给客户端。客户端收到很多消息。它怎么知道哪个消息结果被哪个线程调用了呢?客户端线程每次通过Socket调用远程接口之前,都会生成一个唯一的ID,即RequestID(在一个Socket连接中必须保证RequestID是唯一的),AtomicLong常用于累加从0生成唯一ID。RPC.CASTRPC.CAST的远程调用过程与RPC.CALL类似,只是少了系统消息响应过程。Topic消息生产者向Topic交换器发送系统请求消息,Topic交换器根据消息的RoutingKey将消息转发到共享消息队列。所有连接到共享消息队列的Topic消费者接收到系统请求消息,并传递给响应服务器进行处理。调用流程如图:图18:RPC.CAST示意图连接设计RabbitMQ实现的RPC网络的总体设计思路:消费者是长连接,发送方是短连接。但是你可以自由控制长连接和短连接。一般消费者连接很长,随时准备接收和处理消息;并且没有特别需要涉及RabbitMQQueues、Exchange的自动删除等短连接,发送方可以使用短连接,不会长时间占用端口号,节省端口资源。Nova中的RPC代码设计:RPC和RestfulAPIRESTfulAPI架构简单对比REST***特性:资源、统一接口、URI和无状态。①资源所谓“资源”就是网络上的一个实体,或者说是网络上的一条特定信息。它可以是一段文字,可以是一张图片,可以是一首歌,也可以是一项服务,是具体的现实。②统一接口的RESTful架构风格规定了数据的元操作,即CRUD(Create、Read、Update、Delete,即数据的增删改查)操作,分别对应于HTTP方法:GET用于获取资源,POST用于创建新资源。(也可以用来更新资源),PUT用来更新资源,DELETE用来删除资源,统一数据操作的接口,完成对数据的所有增删改查只能通过HTTP方法。③URL可以使用一个URI(UniformResourceLocator)来指向资源,即每个URI对应一个特定的资源。要获取这个资源,只要访问它的URI,这样URI就成了每个资源的地址或者标识符。④无状态所谓无状态就是所有的资源都可以通过URI来定位,这个定位与其他资源无关,不会因为其他资源的变化而改变。有状态和无状态的区别用一个简单的例子来解释。比如查询一个员工的工资,如果需要登录系统,进入工资查询页面,进行相关操作获取工资金额,那么这种情况就是有状态的。因为查询工资的每一步都依赖于上一步,所以只要前期操作不成功,后面的操作就无法进行。如果可以通过输入URI检索给定员工的薪水,则情况是无状态的,因为获取薪水不依赖于其他资源或状态。而在本例中,员工工资是一个资源,它对应一个URI,可以通过HTTP中的GET方法获取资源,这是典型的RESTful风格。RPC和RestfulAPI与面向对象不同:RPC更侧重于操作。REST的主体是资源。RESTful是一种面向资源的设计架构,但是系统中有很多对象是不能抽象为资源的,比如登录、修改密码等,RPC可以通过action来操作资源。所以RPC在操作的全面性上要大于RESTful。传输效率:RPC效率更高。RPC,使用自定义的TCP协议,可以使请求报文的大小变小,或者使用HTTP2协议,同样可以减少报文的大小,提高传输效率。复杂性:RPC实现复杂,流程繁琐。REST调用和测试非常方便。RPC实现(见***部分)需要实现编码、序列化、网络传输等,而RESTful不用关注这些,RESTful实现更简单。灵活性:无论哪种语言都支持HTTP协议,HTTP相对来说更规范、更规范、更通用。RPC可以实现跨语言调用,但整体灵活性不如RESTful。总结RPC主要用于公司内部的服务调用,性能消耗低,传输效率高,实现复杂。HTTP主要用于外部异构环境,浏览器接口调用,App接口调用,第三方接口调用等RPC使用场景(大型网站,内部子系统多,接口多适合使用RPC):长链接。不需要像HTTP那样每次通信都要经过3次握手,减少了网络开销。注册发布机制。RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对于调用者来说都是无感知的统一操作。安全,不暴露资源操作。微服务支持。就是最近流行的面向服务的架构和面向服务的治理。RPC框架是一个强有力的支持。【原创稿件,合作网站转载请注明原作者和出处为.com】
