什么是 RPC?

初步印象

  RPC的语义是远程过程调用,在一般的印象中,就是将一个服务调用封装在一个本地方法中,让调用者像使用本地方法一样调用服务。而具体的实现是通过调用方和服务方各自的stub基于TCP长连接进行数据交互达成。

  上面的解释似云里雾里,仅仅了解到这种程度是远远不够的,还需要更进一步,以相对底层抽象的视角来理解RPC。

三个特点

  广义上来讲,所有本应用程序外的调用都可以归类为RPC,不管是分布式服务,第三方服务的HTTP接口,还是读写Redis的一次请求。从抽象的角度来讲,它们都一样是RPC,由于不在本地执行,都有三个特点:

    •   需要事先约定调用的语义(接口语法)
    •   需要网络传输
    •   需要约定网络传输中的内容格式

  以一次Redis调用为例,执行redis.set("rpc", 1)这个调用,其中:

    •   set及其参数("rpc", 1),就是对调用语义的约定,由redis的API给出
    •   RedisServer会监听一个服务端口,通过TCP传输内容,用异步事件驱动实现高并发
    •   底层库会约定数据如何进行编解码,如何标识命令和参数,如何表示结果,如何表示数据的结尾等等

  这三个特点都是因为调用不在本地而不得不衍生出来的问题,也因此决定了RPC的形态。所有的RPC解决方案都是在解决这三个问题,不断地在提出更加优良的解决方案,试图达到更好的性能,更低的使用成本。 本文也将围绕这三个特点来展开内容。

  常规的RPC一般都是基于一个大的内部服务,进行分布式拆分,由于其语义上以本地方法的作为入口,那么天然的就更倾向于具备高性能、支持复杂参数和返回值、跨语言等特性。下图是RPC调用的过程示意图:

协议约定

  Stub会负责封装命令和参数,并以特定的数据格式进行打包。其中命令、参数和返回值的需要客户端和服务端的Stub事先进行协商,双方都需要维护一份完全一样的方法及参数列表。

  更进一步需要知道对方如何进行压缩打包,如何压缩结构体,如何压缩Class等等,并严格按照标准进行解压缩,中途有任何一丝的差错都会的导致调用失败。

  所以一般情况下可能会对数据进行一定的校验,同时要协商方法、参数等错误时如何返回。 这是一个比较繁杂的过程,混合了调用语法内容解压缩两部分内容,可被理解为协议约定问题。

网络传输

  搞定了协议约定问题后,接下来就是要通过Runtime进行内容传输了,这又是一大难题,一般是需要通过Socket编程来实现,一般使用TCP或UDP来进行传输。

  如果是UDP可以用数据报来区分每一次请求和回复;但如果是字节流的TCP,就需要用特殊的方式来标示请求或回复的末尾,用来区分不同的请求。

  同时当对调用性能有要求时,可能会使用Socket的异步编程模型,消除等待中的消耗,这会引入事件机制,通过状态机来解析处理或回复请求。当出现超时、丢包等情况时还进行做重试、重传、报错等等。

  拆解到协议约定和网络传输时,就会发现实现RPC调用是一件非常复杂的事情,自己实现千难万难,接下来就了解一番已有的,针对协议约定和网络传输的解决方案。

ONC RPC

  ONC RPC是相对早期的RPC解决方案,通过外部数据表示法来约定数据的压缩方式:

  被传输的所有内容都需要按上面的约定进行压缩,这样接收方就能顺利地按照同样的协议进行解压缩。虽然解决了压缩问题,但无法识别哪些是函数、哪些是类、哪些是参数等,因此需要自定义一份文件,规定什么样的数据是函数、类等。然后客户端和服务器按按此规则划分参数等

  对于命令和参数列表的约定,会创建一份公共的协议文件,里面会定义被调用的方法名,参数列表,对象的列表等等。然后用特定工具将文件进行解析生成Stub程序,客户端和服务端都同时将Stub程序放在代码中。比如对方法名进行编号,将GetUserName(userId)这个方法编号为1,在调用时就将1传输给服务端,服务端通过协议文件就知道调用的是方法,这样节省了大量的空间。

 

  传输则通过对应的类库实现,通过Socket编程实现的非常复杂的解决方案,包含了超时、失败、异常处理、状态转换等等功能。

  这种早期的方案,在每一次代码更新时都需要重新生成Stub程序,调用方和服务方都需要及时更新对应的文件。给某一个方法增加一个默认参数,都需要全部使用者同步升级,从迭代或多版本的场景看来,这是一场噩梦。

RESTfull HTTP JSON

  RESTfull是一种资源状态转换的架构风格,也可以用来实现RPC, 互联网对HTTP超广泛的支持,使得这相当简单,也是大多数情况下的首选。

  通过HTTP协议来进行内容传输,Header用来约定编码、body大小等,彼此以\r\n来分割,Header和body之间通过两个连续的\r\n来间隔,能很容易地区分不同的请求。

  通过Url和对应参数来标示要调用的方法和参数。在body中用JSON对内容进行编码,极易跨语言,不需要约定特定的复杂编码格式和Stub文件。在版本兼容性上非常友好,扩展也很容易。

  众多的优点使得这种方案广受欢迎。不过也有其无法避开的弱点:

    •   HTTP的header和Json的数据冗余和低压缩率使得传输性能差
    •   JSON难以表达复杂的参数类型,如结构体等

gRPC HTTP2.0 Protobuf

  gRPC是一款RPC框架,在性能和版本兼容上做了提升和让步:

    •   Protobuf进行数据编码,提高数据压缩率
    •   使用HTTP2.0弥补了HTTP1.1的不足
    •   同样在调用方和服务方使用协议约定文件,提供参数可选,为版本兼容留下缓冲空间

  protobuf是一款用C++开发的跨语言、二进制编码的数据序列化协议,以超高的压缩率著称。它和早期的RPC方案一样,需要双方维护一个协议约束文件,以.proto结尾,使用proto命令对文件进行解析,会生成对应的Stub程序,客户端和服务端都需要保存这份Stub程序用来进行编解码。对于这种协议文件导致的升级困难问题,protobuf 3 中定义的字段默认都是可选的(可以不传),在接口升级时,部分客户端不需要升级自己的Stub程序。

// ***.proto文件
syntax = "proto3";
package id_rpc;
message BusinessType {// 定义参数
string name = ; //参数字段
} message UniqueId {// 定义返回值
uint64 id = ;
string business_type = ;
} service UniqueIdService {// 定义服务,可以调用 MakeUniqueId 方法
rpc MakeUniqueId(BusinessType) returns (UniqueId){}
}

  对于JSON等文本形式的序列化协议来说,protobuf能有几十倍空间和性能提升, 比如传输123,文本类的需要3个字节(ascii 31 32 33)来传输,而二进制类只需要一个字节(01111011)就可以表示。

  同时protobuf会维护.proto文件,这样在解析文件生成Stub程序时,可以对方法名等进行编号,传输时只传编号,而不用传方法的名字,这又可以节省大量字节,还有其他更多的精巧压缩方法,比如TLV,详情可以参考proto encoding

  解决了数据体积的问题后,gRPC使用HTTP2来改善传输性能。 HTTP2是在HTTP1.1的基础上做了大量的改进,HTTP1.1虽然引入了KeepAlive复用TCP连接,但仍然有很多问题:

    •   使用KeepAlive的请求是串行执行(非pipeline时),pipeline时有队首阻塞问题
    •   每次都需要发送不必要的Header
    •   不能双向通信

  简单补充一下pipeline,HTTP1.1中允许多个请求复用连接,同时可以一口气将请求全部发出去,不用一个返回后再发送第二个,提升并发性。而服务端需要将请求的结果,按照pipeline中发送的顺序进行顺序返回,如果靠前的请求阻塞了,那么靠后请求返回就会被动等待。

  HTTP2解决了这些问题,引入了新的机制:

    •   在两端建立Header索引表,每次只发送索引,减小header体积
    •   建立虚拟通道,将数据拆分成多个流,每个流有自己的ID和优先级,并且流可以双向传输,每个流可以进一步拆成多个帧。可以将多个请求切成不同的流发送,每个流可以独立返回,避开1.1的串行或队首阻塞问题。

  同时,基于HTTP2的数据流机制,gRPC客户端和服务端可以实现批量操作优化,客户端可以攒一些请求,一口气发给服务端,服务端也可以批量返回结果,借此实现流式rpc。

RabbitMQ

r  pc作为一种极常见的服务形态,以异步和解耦著称的mq也自然不会放过这个场景,rabbitmq就为rpc调用提供了很好的支持。

  一般和rabbitmq的交互场景是发布或消费消息,是一个单向的过程,而rpc却是一种同步的双向交互过程,在使用上有些差异。要理解rabbitmq如何实现rpc,还是可以从上面三个抽象的特点出发,万变不离其宗。

如何协商调用语义

  mq中的消息是从exchange分发到queue中,消费端在特定的queue中获取消息,rpc的请求依然要走这条路径: 方法调用->exchange->queue->方法执行

  创建一个direct类型的exchange,让每个rpc方法对应一个queue,这个exchange通过routing_key分发到对应的queue中, 让特定的消费者来实际执行rpc方法。这样rpc方法的语义就通过queue来约定,而方法的参数,可以放入消息中。

如何将结果传递回客户端

  方法调用->exchange->queue->方法执行, 这条路是单行道,方法执行端执行完rpc方法后不能按照原路将结果返回给客户端。要实现结果回传,就得再开辟一条结果回传端->exchange->queue->结果等待端路径,一条用来发送rpc请求,另一条用来回传rpc结果,方法调用者和方法执行者都会扮演生产者和消费者。

  rabbitmq中有回调队列(Callback queue)来实现调用结果回传,同时有关联ID(Correlation Id)来唯一标识每一份调用结果

  rpc调用方在发送请求时,会在数据中带上回调队列信息(routing_key)和关联ID,rpc执行方在执行完方法后,就将关联ID掺入执行结果中,并将结果通过exchange发往回调队列(通过routing_key)。 rpc调用方在发送请求后,紧接着在设置的回调队列中等结果就行。 整个过程(两条路径)共用同一个exchange。

  调用参数和调用结果的打包可以用JSON,protobuf等等,协商一致即可。完整示例代码

  使用mq实现rpc,有其独有的优势,rpc执行端可以轻松地横向扩展,rpc调用方也不用考虑负载均衡,沿袭了mq解耦的优点。不过对于调用超时,执行端崩溃等等情况得做额外处理。调用方在等待结果时需要设置超时间,高性能的rpc调用还需要调用方能异步高效地通过关联ID将请求结果储存起来,等待调用者获取。Spring框架的实现方案就是用一个HashMap将结果保存起来,等待调用者以关联ID作为key来取结果。

服务发现

  上面介绍了常见的rpc实现方案,而RPC作为分布式服务的一种形态,本身还有服务发现和负载均衡的问题需要解决(mq除外)。服务注册和发现作为分布式系统的基础设施之一,有很多的解决方案,例如: zookeeper、ETCD、envoy等等。服务启动后向公开的注册中心进行服务注册,服务调用方在调用前,向注册中心查询对应服务的地址,这又是一项工程,不过一般的请求规模也不需要这套东西,更高端的方案,得期待ServiceMesh的进一步发展了。

总结

  RPC从抽象的角度来看,具有需要约定调用语法需要约定编码格式需要网络传输这三大特点,进一步可以归纳为协议约定问题网络传输问题,本文的主要内容都围绕这两大问题,并介绍常见的解决方案,借此对建立RPC更深的理解。

 

RPC协议的更多相关文章

  1. 游戏编程系列[1]--游戏编程中RPC协议的使用

    RPC(Remote Procedure Call Protocol)--远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.RPC协议假定某些传输协议的存在 ...

  2. 传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确。此 RPC 请求中提供了过多的参数。最多应为 2100

    出现这个问题的背景是,判断一批激活码在系统中是否已经存在,很傻的一个作法是,把这一批激活码,以in(in (‘ddd‘,‘aaa‘))的形式来处理,导致问题的出现. 后来,查找资料,http://bb ...

  3. Hessian 二进制RPC协议框架

    Hessian是一个由Caucho Technology开发的轻量级二进制RPC协议. 和其他Web服务的实现框架不同的是,Hessian是一个使用二进制轻量级的Web服务协议的框架,免除了许多附加的 ...

  4. 初识RPC协议

    什么是rpc框架 先回答第一个问题:什么是RPC框架? 如果用一句话概括RPC就是:远程调用框架(Remote Procedure Call) 那什么是远程调用? 通常我们调用一个php中的方法,比如 ...

  5. 转:传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确 .

    近期在做淘宝客的项目,大家都知道,淘宝的商品详细描述字符长度很大,所以就导致了今天出现了一个问题 VS的报错是这样子的  ” 传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确“ 还说某 ...

  6. SQLServer 2000 Driver for JDBC][SQLServer]传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确解决方法

    问题:[SQLServer 2000 Driver for JDBC][SQLServer]传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确.参数 1 (""): ...

  7. 网络协议 22 - RPC 协议(下)- 二进制类 RPC 协议

        前面我们认识了两个常用文本类的 RPC 协议,对于陌生人之间的沟通,用 NBA.CBA 这样的缩略语,会使得协议约定非常不方便.     在讲 CDN 和 DNS 的时候,我们讲过接入层的设计 ...

  8. 网络协议 20 - RPC 协议(上)- 基于XML的SOAP协议

    [前五篇]系列文章传送门: 网络协议 15 - P2P 协议:小种子大学问 网络协议 16 - DNS 协议:网络世界的地址簿 网络协议 17 - HTTPDNS:私人定制的 DNS 服务 网络协议 ...

  9. 网络协议 19 - RPC协议综述:远在天边近在眼前

    [前五篇]系列文章传送门: 网络协议 14 - 流媒体协议:要说爱你不容易 网络协议 15 - P2P 协议:小种子大学问 网络协议 16 - DNS 协议:网络世界的地址簿 网络协议 17 - HT ...

  10. SQL :“传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确” 错误

    其中在DAL层调用存储过程来插入数据的参数 SqlParameter[] parameters = {                                            new S ...

随机推荐

  1. 让您的WinForm控件快速支持拖拽文件

    实现原理:使用扩展方法. /// <summary> /// 控件扩展 /// </summary> public static class ControlExt { /// ...

  2. Python - 记录我开始学习Python的时间节点

    记录我开始学习Python的时间节点 2019-09-22 从明天开始我要开始学习Python了,坚持学习.坚持写博客,慢慢积累. 结合实例项目,最好能把目前在做的项目用Python实现. 加油!

  3. IIS配置——常见问题

    1.控制面板->程序和功能->打开或关闭Windows功能->Internet信息服务 勾选如下这些选项 2.新建一个网站 3.HTTP 错误 403.14 选择目录浏览然后启用即可 ...

  4. Lambda表达式---Day27

    函数式编程思想概述 在数学中,函数就是有输入量.输出量的一套计算方案,也就是“拿什么东西做什么事情”.相对而言,面向对象过 分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语 ...

  5. English--辅音

    English|辅音 英语中的辅音,按照发音的松紧,唇形舌位,划分为七大类.需要好好地体会具体的发音部位与口型. 前言 目前所有的文章思想格式都是:知识+情感. 知识:对于所有的知识点的描述.力求不含 ...

  6. 记录下vue 中引用echarts 出现 "TypeError: Cannot read property 'getAttribute' of undefined"问题

    今天做项目,用echarts展示数据 ,自己测试 先测试 了下.写的代码html: <div ref="myChart" style="height:300px;w ...

  7. android studio学习----The project encoding (windows-1252) does not match the encoding specified in the Gradle build files (UTF-8)

    Warning:The project encoding (windows-1252) does not match the encoding specified in the Gradle buil ...

  8. 【转载】Gradle学习 第十章:网络应用快速入门

    转载地址:http://ask.android-studio.org/?/article/8 This chapter is a work in progress.这一章是一项正在进行中的工作. Th ...

  9. ASP.NET----内置对象----Resuest

    Request对象可以获取包含用户的计算机.页面及浏览器的请求等相关信息. Request对象的属性: ①Form----------返回有关表单变量的集合 ②QueryString--------- ...

  10. Linux下BLAST+的本地化(BLAST 2.2.29+)

    链接:http://blog.sciencenet.cn/home.php?mod=space&uid=830496&do=blog&quickforward=1&id ...