RPC基本原理

是什么

RPC 的全称是 Remote Procedure Call 是一种进程间通信方式。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的,本质上编写的调用代码基本相同。

起源

RPC 这个概念术语在上世纪 80 年代由 Bruce Jay Nelson 提出。这里我们追溯下当初开发 RPC 的原动机是什么?在 Nelson 的论文 "Implementing Remote Procedure Calls" 中他提到了几点:

  1. 简单:RPC 概念的语义十分清晰和简单,这样建立分布式计算就更容易。
  2. 高效:过程调用看起来十分简单而且高效。
  3. 通用:在单机计算中过程往往是不同算法部分间最重要的通信机制。

应用背景

今天分布式应用、云计算、微服务大行其道,作为其技术基石之一的 RPC。

目标

RPC 的主要目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明调用机制让使用者不必显式的区分本地调用和远程调用。

分类

  • 同步调用:客户端等待调用执行完成并获取到执行结果。
  • 异步调用:客户端调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。若客户端不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。

模型

最早在 Nelson 的论文中指出实现 RPC 的程序包括 5 个理论模型部分:

  • User
  • User-stub
  • RPCRuntime
  • Server-stub
  • Server

组件构成

每个组件的职责划分。

  • RpcServer
    负责导出(export)远程接口
  • RpcClient
    负责导入(import)远程接口的代理实现
  • RpcProxy
    远程接口的代理实现
  • RpcInvoker
    客户端:负责编码调用信息和发送调用请求到服务端并等待调用结果返回
    服务端:负责调用服务端接口的具体实现并返回调用结果
  • RpcProtocol
    负责协议编/解码
  • RpcConnector
    负责维持客户端和服务端的连接通道和发送数据到服务端
  • RpcAcceptor
    负责接收客户端请求并返回请求结果
  • RpcProcessor
    负责在服务端控制调用过程,包括管理调用线程池、超时时间等
  • RpcChannel
    数据传输通道

过程实现

导出

导出是指暴露远程接口的意思,只有导出的接口可以供远程调用,而未导出的接口则不能。 在 Java 中导出接口的代码片段可能如下:

DemoService demo   = new ...;
RpcServer   server = new ...;
server.export(DemoService.class, demo, options);

我们可以导出整个接口,也可以更细粒度一点只导出接口中的某些方法,如下:

// 只导出 DemoService 中签名为 hi(String s) 的方法
server.export(DemoService.class, demo, "hi", new Class<?>[] { String.class }, options);

Java 中还有一种比较特殊的调用就是多态,也就是一个接口可能有多个实现,那么远程调用时到底调用哪个?这个本地调用的语义是通过 JVM 提供的引用多态性隐式实现的,那么对于 RPC 来说跨进程的调用就没法隐式实现了。如果前面 DemoService 接口有 2 个实现,那么在导出接口时就需要特殊标记不同的实现,如下:

DemoService demo   = new ...;
DemoService demo2  = new ...;
RpcServer   server = new ...;
server.export(DemoService.class, demo, options);
server.export("demo2", DemoService.class, demo2, options);

上面 demo2 是另一个实现,我们标记为 demo2 来导出, 那么远程调用时也需要传递该标记才能调用到正确的实现类,这样就解决了多态调用的语义。

导入

导入相对于导出而言,客户端代码为了能够发起调用必须要获得远程接口的方法或过程定义。目前,大部分跨语言平台 RPC 框架采用根据 IDL 定义通过 code generator 去生成 User-stub 代码,这种方式下实际导入的过程就是通过代码生成器在编译期完成的。我所使用过的一些跨语言平台 RPC 框架如 CORBAR、WebService、ICE、Thrift 均是此类方式。

代码生成的方式对跨语言平台 RPC 框架而言是必然的选择,而对于同一语言平台的 RPC 则可以通过共享接口定义来实现。
在 Java 中导入接口的代码片段可能如下:

RpcClient client = new ...;
DemoService demo = client.refer(DemoService.class);
demo.hi("how are you?");

在 Java 中 import 是关键字,所以代码片段中我们用 refer 来表达导入接口的意思。 这里的导入方式本质也是一种代码生成技术,只不过是在运行时生成,比静态编译期的代码生成看起来更简洁些。Java 里至少提供了两种技术来提供动态代码生成,一种是 JDK 动态代理,另外一种是字节码生成。 动态代理相比字节码生成使用起来更方便,但动态代理方式在性能上是要逊色于直接的字节码生成的,而字节码生成在代码可读性上要差很多。两者权衡起来,作为一种底层通用框架,个人更倾向于选择性能优先。

协议

协议指 RPC 调用在网络传输中约定的数据封装方式,包括三个部分:编解码、消息头 和 消息体。

编解码

客户端代理在发起调用前需要对调用信息进行编码,这就要考虑需要编码些什么信息并以什么格式传输到服务端才能让服务端完成调用。 出于效率考虑,编码的信息越少越好(传输数据少),编码的规则越简单越好(执行效率高)。

我们先看下需要编码些什么信息:

调用编码

  1. 接口方法
    包括接口名、方法名
  2. 方法参数
    包括参数类型、参数值
  3. 调用属性
    包括调用属性信息,例如调用附加的隐式参数、调用超时时间等

返回编码

  1. 返回结果
    接口方法中定义的返回值
  2. 返回码
    异常返回码
  3. 返回异常信息
    调用异常信息

消息头

除了以上这些必须的调用信息,我们可能还需要一些元信息以方便程序编解码以及未来可能的扩展。这样我们的编码消息里面就分成了两部分,一部分是元信息、另一部分是调用的必要信息。如果设计一种 RPC 协议消息的话,元信息我们把它放在协议消息头中,而必要信息放在协议消息体中。下面给出一种概念上的 RPC 协议消息头设计格式:

magic 
协议魔数,为解码设计
header size 
协议头长度,为扩展设计
version 
协议版本,为兼容设计
st 
消息体序列化类型
hb 
心跳消息标记,为长连接传输层心跳设计
ow 
单向消息标记,
rp 
响应消息标记,不置位默认是请求消息
status code 
响应消息状态码
reserved 
为字节对齐保留
message id 
消息 id
body size 
消息体长度

消息体

消息体常采用序列化编码,常见有以下序列化方式:

  • xml :如 webservie SOAP
  • json :如 JSON-RPC
  • binary:如 thrift; hession; kryo;protobuff 等
    格式确定后编解码就简单了,由于头长度一定所以我们比较关心的就是消息体的序列化方式。 序列化我们关心三个方面:

效率:序列化和反序列化的效率,越快越好。
长度:序列化后的字节长度,越小越好。
兼容:序列化和反序列化的兼容性,接口参数对象若增加了字段,是否兼容。
上面这三点有时是鱼与熊掌不可兼得,这里面涉及到具体的序列化库实现细节,就不在本文进一步展开分析了。

传输

协议编码之后,自然就是需要将编码后的 RPC 请求消息传输到服务端,服务方执行后返回结果消息或确认消息给客户端。RPC 的应用场景实质是一种可靠的请求应答消息流,这点和 HTTP 类似。因此选择长连接方式的 TCP 协议会更高效,与 HTTP 不同的是在协议层面我们定义了每个消息的唯一 id,因此可以更容易的复用连接。

既然使用长连接,那么第一个问题是到底客户端和服务端之间需要多少根连接?实际上单连接和多连接在使用上没有区别,对于数据传输量较小的应用类型,单连接基本足够。单连接和多连接最大的区别在于,每根连接都有自己私有的发送和接收缓冲区,因此大数据量传输时分散在不同的连接缓冲区会得到更好的吞吐效率。

所以,如果你的数据传输量不足以让单连接的缓冲区一直处于饱和状态的话,那么使用多连接并不会产生任何明显的提升,反而会增加连接管理的开销。

连接是由客户端发起建立并维持的,如果客户端和服务端之间是直连的,那么连接一般不会中断(当然物理链路故障除外)。如果客户端和服务端连接经过一些负载中转设备,有可能连接一段时间不活跃时会被这些中间设备中断。为了保持连接有必要定时为每个连接发送心跳数据以维持连接不中断。心跳消息是 RPC 框架库使用的内部消息,在前文协议头结构中也有一个专门的心跳位,就是用来标记心跳消息的,它对业务应用透明。

执行

客户端 stub 所做的事情仅仅是编码消息并传输给服务方,而真正调用过程发生在服务端。服务端 stub 从前文的结构拆解中我们细分了 RpcProcessor 和 RpcInvoker 两个组件,一个负责控制调用过程,一个负责真正调用。 这里我们还是以 Java 中实现这两个组件为例来分析下它们到底需要做什么?

Java 中实现代码的动态接口调用目前一般通过反射调用。除了原生 JDK 自带的反射,一些第三方库也提供了性能更优的反射调用,因此 RpcInvoker 就是封装了反射调用的实现细节。

调用过程的控制需要考虑哪些因素,RpcProcessor 需要提供什么样地调用控制服务呢?下面提出几点以启发思考:

  • 效率提升
    每个请求应该尽快被执行,因此我们不能每请求来再创建线程去执行,需要提供线程池服务
  • 资源隔离
    当我们导出多个远程接口时,如何避免单一接口调用占据所有线程资源,而引发其他接口执行阻塞。
  • 超时控制
    当某个接口执行缓慢,而客户端已经超时放弃等待后,服务端的线程继续执行此时显得毫无意义。

异常

无论 RPC 怎样努力把远程调用伪装的像本地调用,但它们依然有很大的不同点,而且有一些异常情况是在本地调用时绝对不会碰到的。在说异常处理之前,我们先比较下本地调用和 RPC 调用的一些差异:

  • 本地调用一定会执行,而远程调用则不一定,调用消息可能因为网络原因并未发送到服务方。
  • 本地调用只会抛出接口声明的异常,而远程调用还会跑出 RPC 框架运行时的其他异常。
  • 本地调用和远程调用的性能可能差距很大,这取决于 RPC 固有消耗所占的比重。
    正是这些区别决定了使用 RPC 时需要更多考量。 当调用远程接口抛出异常时,异常可能是一个业务异常,也可能是 RPC 框架抛出的运行时异常(如:网络中断等)。业务异常表明服务方已经执行了调用,可能因为某些原因导致未能正常执行,而 RPC 运行时异常则有可能服务方根本没有执行,对调用方而言的异常处理策略自然需要区分。

由于 RPC 固有的消耗相对本地调用高出几个数量级,本地调用的固有消耗是纳秒级,而 RPC 的固有消耗是在毫秒级。那么对于过于轻量的计算任务就并不适合导出远程接口由独立的进程提供服务,只有花在计算任务上的时间远远高于 RPC 的固有消耗才值得导出为远程接口提供服务。

Ref:
http://blog.csdn.net/mindfloating/article/details/51477557

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • 今天分布式应用、云计算、微服务大行其道,作为其技术基石之一的 RPC 你了解多少?一篇 RPC 的技术总结文章,数...
    零一间阅读 1,875评论 1 46
  • 转自http://mp.weixin.qq.com/s?__biz=MzAxMTEyOTQ5OQ==&mid=26...
    文刂德光军阅读 1,121评论 0 11
  • 今天分布式应用、云计算、微服务大行其道,作为其技术基石之一的 RPC 你了解多少?一篇 RPC 的技术总结文章,数...
    mindwind阅读 1,870评论 1 38
  • 繁星点点,朗月行,灯火阑珊,醉人眼,半世美好莲心
    乐闻悦马阅读 252评论 0 0