谁能用通俗的语言解释一下什么是 RPC 框架?

转载自知乎:https://www.zhihu.com/question/25536695

知乎上很多问题的答案还是很好的,R大就经常在上面回答问题

关于RPC
你的题目是RPC框架,首先了解什么叫RPC,为什么要RPC,RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。

比如说,一个方法可能是这样定义的:
Employee getEmployeeByName(String fullName)
那么:
  • 首先,要解决通讯的问题,主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
  • 第二,要解决寻址的问题,也就是说,A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称是什么,这样才能完成调用。比如基于Web服务协议栈的RPC,就要提供一个endpoint URI,或者是从UDDI服务上查找。如果是RMI调用的话,还需要一个RMI Registry来注册服务的地址。
  • 第三,当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制(0101)的,内存中的参数的值要序列化成二进制的形式,也就是序列化(Serialize)或编组(marshal),通过寻址和传输将序列化的二进制发送给B服务器。
  • 第四,B服务器收到请求后,需要对参数进行反序列化(序列化的逆操作),恢复为内存中的表达方式,然后找到对应的方法(寻址的一部分)进行本地调用,然后得到返回值。
  • 第五,返回值还要发送回服务器A上的应用,也要经过序列化的方式发送,服务器A接到后,再反序列化,恢复为内存中的表达方式,交给A服务器上的应用

为什么RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯。由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用,
RPC的协议有很多,比如最早的CORBA,Java RMI,Web Service的RPC风格,Hessian,Thrift,甚至Rest API。
关于Netty
而Netty框架不局限于RPC,更多的是作为一种网络协议的实现框架,比如HTTP,由于RPC需要高效的网络通信,就可能选择以Netty作为基础。除了网络通信,RPC还需要有比较高效的序列化框架,以及一种寻址方式。如果是带会话(状态)的RPC调用,还需要有会话和状态保持的功能。

大体上来说,Netty就是提供一种事件驱动的,责任链式(也可以说是流水线)的网络协议实现方式。网络协议包含很多层次,很多部分组成,如传输层协议,编码解码,压缩解压,身份认证,加密解密,请求的处理逻辑,怎么能够更好的复用,扩展,业界通用的方法就是责任链,

一个请求应答网络交互通常包含两条链,一条链(Upstream)是从传输层,经过一系列步骤,如身份认证,解密,日志,流控,最后到达业务层,一条链(DownStream)是业务层返回后,又经过一系列步骤,如加密等,又回到传输层。


这样每一层都有一个处理接口,都可以进行不同的操作,比如身份认证,加解密,日志,流控,将不同的处理实现像拼积木那样插接起来就可以实现一个网络协议了(快速开发)。每一层都有自己的实现,上层不需要关注面向网络的操作(可维护)。Netty已经提供了很多实现。


当然Netty还有许多好处,比如对非阻塞IO(NIO)的支持,比如在链上传递时最大程度的减少buffer的copy(高性能)。

-----------------------

Netty只是网络通信框架,把Java Socket的API又封装了一次,使得你可以用最少的代码来完成网络通信这一任务。

根据字面意思来推断,RPC 的确是为了进程间通信而准备的,但构造成函数调用这一形式,是因为这是在抽象上最合理的。

我们可以推断演进一下
====
1. A B 两个进程之间需要进行数据交换。
2.于是我们想出来在某个内存区域划出一个空间,然后向该空间中写入和读取数据。(共享文件也可以)(常见的socket就是这一共享内存的抽象,只是现在大多指网络通路)
3.A B 通信完成。
====
4.A B需要完成更复杂的交互
5.于是我们指定一个协议,A B 根据该协议对数据的进行编码解码,根据协议内容做出决策。
====
6.发现协议过于复杂(比如 编号1代表调用 a函数,编号2代表b函数)
7.试图优化协议,将函数参数和调用的函数名称作为协议的一部分,函数返回值类似
8.RPC达成
=====
9.表现出来的特性就是,object invok(parameter),就代表了,序列化 parameter 对象到中间格式,利用远程服务器的 invok 函数进行处理 ,同时将返回的数据解码生成 object对象。

======总结=====

RPC 在整个过程中,体现了逐层抽象,将复杂的协议编解码和数据传输封装到了一个函数中。

======缺点=====
单一 RPC 无法实现 push,即推送服务。
理由是,RPC 是client 调用 server获取数据,是一个完整的过程,实现不了server调用client。
解决方案:让client 既可以调用server上的RPC服务,反之client本身也成为一个RPC服务让Server来调用。

=====回到题主的问题====
1. Netty只是网络通信框架,目的是让你用最少的代码构建出足够支撑网络通信的功能。

2.完成RPC 需要两个协议: 对象序列化协议 和 调用控制协议

常见例子举例:

1.zeroC ICE,拥有自己的网络通信框架 + ICE 调用控制协议和对象序列化协议,同时也涵盖了服务组件的抽象部署等功能。

2.thrift,有自己的网络通信框架+thrift 对象序列化协议+thrift 调用控制协议

3.probuff,只是 对象序列化协议

4.XMLRPC ,jsonRPC,常见的语境是利用HTTP协议作为调用控制协议,XML 和 JSON 作为对象序列化之后的格式。

5.其他的大概也差不多了。

------------

简单回答下,比方说你的网站想获取天气信息,你数据哪里来?肯定是气象中心啦!那你怎么获取?这就需要远程调用、从气象部门的服务器获取信息到你的网站。netty底层是nio写的。nio是非阻塞的,简单来说是不需要等。cpu在处理完后直接交给硬盘读取。举个栗子,你去饭店吃饭,传统的模式是你点菜,然后点完去炒,直到炒完给你再下一个,这个过程下一位可能等的不耐烦了。而非阻塞则是这样,你去前台点菜,前台叫厨师炒,炒完叫你,然后前台点菜员继续服务下一位客户。这样是不是效率大大提高了。
----
不用想的太复杂,rpc框架做的最重要的一件事就是封装了,调用者和被调用者的通信细节。客户端代理负责将调用方法的方法名,参数返回值包等信息根据通信协议组织成报文发送给服务端,服务端解析报文,根据客户端传递的信息执行对应的方法,然后将返回值按照协议组织成报文发送给客户端,客户端再解析出来。
---

RPC, 远程过程调用直观说法就是A通过网络调用B的过程方法。

通信中的协议是你自己规定的,比如你可以规定说当A向B发送数字1, B就打印hello world, 并返回数字1给A, 如果发送数字2,B就打印hello, guy.并发送数字2给A.

这就是一个简单的RPC示例,实际上natty就是干这个的,只不过它提供一套框架给你,让你可以定义自己的规则,实现B里面的函数。

我们来想一想,当A要把数字1发送给B要怎么办呢,你需要用socket,B是server, A是client, B执行完以后再把1通过socket写回即可。你可以用AUPE书中的例子很容易就构建出来。

问题到这里,应该就结束了,事实上就是这么简单,那为什么需要RPC框架呢?

当你给B发送1或者2就可以区分函数方法,但是你不可能写100个函数打印100个字符,你需要在发送1的时候,再接着发送一个字符串,这样B只要实现一个通用的print函数就够了。

我们继续思考,当你想发送一个数字比如65534给B的时候,我们知道socket是按照字节接受数据的,一个字节最大为255也就是0xff, 如果要发送65534你有两种方法,第一把65534当作5个字符6,5,5,3,4然后让socket接受5个字符。第二,你可以把65534变成二进制0xfffe, 这样你可以先发送一个0xff再发送一个0xfe,也就是先发一个255再发一个254再拼装。

另外在拼装这255和254的时候,我们知道不同体系结构的CPU字节序是不一样的,因此,你需要解决大端小端字节序的问题。否则对于一个32位机器上,65534作为一个int型可能会是0xfffe0000,或者0x0000feff,这取决于你如何发送和打包它。

对于相对复杂的RPC, 我们发送的一个数据包往往既包含字符串又包含数字,这样我们就需要把他们切割开来然后分别解码编码。

这个部分就叫做序列化反序列化,在高级一点的RPC框架中,甚至可以做到把
一个类从A扔给B.用到的还是这个方法,只是把类这个类型也放进去了。

对于脚本语言比如Lua,你甚至可以把编译过后的字节码发给远程,然后在B用Lua虚拟机执行。

所以对于一个完整的RPC框架底层往往是socket搭配序列化反序列化的工作。

问题到这里,一个RPC分析应该也结束了, 实际上要想把一个RPC做稳定了,还有几个问题。

首先,要想让一个RPC能够更高效,往往做到让A可以连续向B发送请求,这就要求在协议解析的时候区分两个包,在TCP分包的时候这里有个问题叫做半包和粘包。

另外B也无需在返回完函数1以后再返回函数2,这就要求在返回的时候,A能够区分开来,这就要求在协议里面加入session.

高效的写法不但在编解码和协议设计理念,还需要在编写socket server时候,尽可能降低阻塞调用,用异步来做,这有一大推开源方案,比如libev.

netty就是一个这样异步的RPC通信框架。

当然,在实际项目中,我们只是需要解决实际问题,完全没必要从头构建,你可以选一个通信协议比如protobuf/JSON/msgpack/thrift等等再找他相关的RPC库使用即可。

当然完全可以自己定制。

一个好的框架不只是解决最基本的问题,它更像是一个生态系统,比如我看到netty还有SSL的部分,DNS部分。
posted @ 2018-01-24 09:32  landonzeng  阅读(778)  评论(0编辑  收藏  举报