美文网首页
RPC原理与FastRPC实现

RPC原理与FastRPC实现

作者: SunnyZhou1024 | 来源:发表于2020-05-01 14:14 被阅读0次

0. 起因

最近看文档,发现一些组件是通过FastRPC来进行沟通的,并且偶尔看到某些场景下在FastRPC上的时间消耗好像也蛮可观,恰好FastRPC是开源的,因此决定看看FastRPC具体的实现。

1. RPC简介

当初在学Java的时候,初遇RMI(Remote Method Invocation),感觉这是一个非常神奇的东西,竟然可以调用别的机器上的方法!多么好奇这是怎么实现的。后来渐渐明白,其实RMI就是个RPC的另一个名字。那么,RPC的原理是什么呢?
RPC全称Remote Procedure Call(远程过程调用),所谓远程,就是执行的例程(routine)位于另一个地址空间(关于地址空间的介绍可以参阅之前的文章——冰山之下:使用new申请内存的背后
简单说可以认为地址空间就是进程),通常情况下是另一台机器。如图1所示,RPC框架为调用者提供一个代理,这个代理中的方法和远程例程一一对应。当调用者调用了代理的一个方法后,代理通过一种客户端——服务器的方式来,将函数签名、参数等封装成一个网络请求,发送给服务器。服务器接收到请求后通过解析获得函数签名、参数等信息,通过查表获取到目标例程地址,调用该例程并将参数传递给它。例程计算完成后,将结果通过网络返回到代理,代理将服务器返回的结果稍加处理,成为调用者可以理解的数据格式,最终返回到调用者。客户端和服务器的沟通细节是通过RPC框架来实现的,数据的序列化和反序列化、网络请求与答复等细节是隐藏的,对于调用者来说,它并不知道它调用的方法来对数据的处理到底是自己的地址空间进行还是在别的地址空间进行。
举个栗子:你到一家餐厅吃饭,店里有一个服务员为你服务。你点完菜之后,服务员就走进了后厨。但是,服务员并不是直接将你点的菜告诉他们店里的厨师,相反的,他打电话到别的店点了外卖,外卖到了之后他将饭菜重新装盘,然后给你端了上来。但是你是不知道这个细节的,在你看来,这次吃饭和以往的任何一次没有区别,服务员端上的饭菜在哪里做的你是不知道的,你和平常一样点了菜然后菜就上了,你并不需要自己去关心怎么找到外卖电话,怎么将饭菜装盘。你只管点菜和吃,除了可能等的时间比以往略久,没有任何区别。这个栗子中,服务员就扮演着RPC 中代理的角色,你就是调用者,外卖店就是服务器。

Fig 1 RPC internal

情况就是这么个情况,但是由于RPC只是一种实现方法,并没有形成标准,因此有着很多不同的实现,比如gRPC、Dubbo、Thrift和FastRPC等。对于如何进行序列化和反序列化、如何通信,不同的框架有着不同的实现。

2. FastRPC简介

FastRPC是一个XML-RPC协议的实现,它的特点是有多种数据序列化方式可选:二进制、JSON、XML以及Base64,因为它使用HTTP协议作为载体,通过HTTP的头的数据格式协商字段很容易知道数据的格式。

FastRPC相对与其他框架来说非常简单,代码主要就三部分:客户端实现、服务端实现以及数据序列化与反序列化的实现。也是因为它太简单了,它并没有涉及到鉴权等方面的内容,这些需要自己去考虑。但是对于了解一个RPC框架的具体实现已经足够了。

3. FastRPC调用流程

RPC分为两部分:运行在本地址空间的客户端部分以及运行在其他地址空间的服务器部分。下面就来看看FastRPC对于着两部分的实现。

3.1. FastRPC Server调用流程

FastRPC Server的实现如图2所示,其工作流程如下:

  1. 将客户端可以调用的例程依次进行注册,存放到一个注册表,其实就是一个以函数名为键、函数地址为值的一个字典;
  2. 例程注册完成后,开始启动对特定的端口的监听;
  3. 当有请求到达后,通过HTTP的头确定内容的序列化格式,调用对应的反序列化方法对数据进行解析。
  4. 解析完成获得函数名和参数,通过查表或取到函数地址,调用函数并将参数传入;
  5. 函数返回后通过将数据返回请求者。

整个过程非常简单。


Fig 2 FastRPC Server

3.2. FastRPC Client的调用流程

FastRPC Client的实现如图3所示,其工作流程如下:

  1. 首先进行链接的配置,例如服务器地址、端口号、最大等待时间等;
  2. 配置完成后等待客户调用,客户调用特定方法,传入了参数。客户端调用选定的序列化例程对数据进行序列化,并且根据数据填充HTTP头信息;
  3. 客户端发起HTTP请求,将数据发送个服务器,等待服务器应答;
  4. 获取到服务器返回的数据后,对数据进行解析,返回给调用者。
fFig 3 FastRPC Client

整个请求的核心就是下面的几行代码,位于frpcserverproxy.cc中,首先将方法名和参数都序列化到本地的缓存,然后通过flush方法写到HTTP输出流:

// fastrpc\src\frpcserverproxy.cc
    ...
    try {
        marshaller->packMethodCall(methodName);

        // marshall all passed values until null pointer
        while (const Value_t *value = va_arg(args, Value_t*))
            feeder.feedValue(*value);

        marshaller->flush();
    } catch (const ResponseError_t &e) {}
    ...
// fastrpc\src\frpctreefeeder.cc

template <typename Marshaller_t>
void feedValueImpl(Marshaller_t &marshaller, const Value_t &value) {
    switch(value.getType()) {
    case Int_t::TYPE:
        marshaller.packInt(Int(value).getValue());
        break;

    case Bool_t::TYPE:
        marshaller.packBool(Bool(value).getValue());
        break;

    case Null_t::TYPE:
        packNull(marshaller, value);
        break;
    ...
// fastrpc\src\frpcbinmarshaller.cc

void BinMarshaller_t::packInt(Int_t::value_type value) {
    if (protocolVersion.versionMajor > 2) {
        // pack via zigzag encoding.
        uint64_t zig = zigzagEncode(value);
        unsigned int numType = getNumberType(
                static_cast<Int_t::value_type>(zig));
        //pack type
        char type = FRPC_DATA_TYPE(INT, numType);
        //pack number value
        Number_t number(zig);

        //write type
        writer.write(&type, 1);
        writer.write(number.data, getNumberSize(numType));

    } else if (protocolVersion.versionMajor > 1) {
    ...
// fastrpc\src\frpchttpclient.cc

void HTTPClient_t::write(const char* data, unsigned int size) {
    contentLenght += size;

    if (size > (BUFFER_SIZE - queryStorage.back().size())) {
        if (useChunks) {
            sendRequest();
            queryStorage.back().append(data, size);
        } else {
            if (size > BUFFER_SIZE) {
                queryStorage.push_back(std::string(data, size));
            } else {
                queryStorage.back().append(data, size);
            }
        }
    } else {
        queryStorage.back().append(data, size);
    }
}
// fastrpc\src\frpchttpclient.cc

void HTTPClient_t::sendRequest(bool last) {
    SocketCloser_t closer(httpIO.socket());
    std::string headerData;
    if (!headersSent) {
        HTTPHeader_t header;
        StreamHolder_t os;
        //create header
        os.os << POST << ' ' << (url.isUnix() ? "/" : url.path) << ' '
              << (useHTTP10 ? HTTP10 : HTTP11) << "\r\n";
        if (!useHTTP10) {
            os.os << HOST << ": ";
            if (!url.isUnix()) {
                os.os << url.host << ':' << url.port;
            }
            os.os << "\r\n";
        }
      ...

4. 总结

说白了,RPC最终就是一个服务器和客户端。那么RPC和REST(Representational State Transfer,表示层状态转移)有什么区别呢?两者的区别就是在于对服务器请求的目的不同:REST需要服务器给出资源,例如一张图片一段数据,并且通过GET, PUT, UPDATE, DELETE等特定方法表述希望对这个资源进行的操作;而RPC客户端需要RPC服务器提供计算服务,你用我希望的任何方式对我提供的数据进行计算并告诉我结果。

本文首发于个人微信公众号TensorBoy,微信扫描上方二维码或者微信搜索TensorBoy并关注,及时获取最新文章。C++ | Python | Linux | 推理引擎 | AI框架源码,有一起玩耍的么?

5. References

[1] What differentiates a REST web service from a RPC-like one? 【stackoverflow】
[2] https://github.com/seznam/fastrpc

相关文章

  • RPMsg:协议简介

    0. 起因 之前在RPC原理与FastRPC实现一文中介绍过RPC的原理,简而言之,RPC就是实现本地程序调用位于...

  • RPC原理与FastRPC实现

    0. 起因 最近看文档,发现一些组件是通过FastRPC来进行沟通的,并且偶尔看到某些场景下在FastRPC上的时...

  • RPC简述及简单实现

    本文主要介绍RPC的原理并实现一个简单的例子。 RPC主要原理 什么是RPCRPC -- Remote Proce...

  • RPC详解&跨语言RPC实践

    本文将从大的框架层面来聊聊RPC原理和实现,既然叫跨语言RPC,也将以thrift为例讲讲跨语言RPC如何实现。在...

  • 100 行代码搞定了 RPC 原理,面试官随便问!

    引言 本文主要论述的是“RPC 实现原理”,那么首先明确一个问题什么是 RPC 呢?RPC 是 Remote Pr...

  • 100 行代码透彻解析 RPC 原理

    引 言 本文主要论述的是“RPC 实现原理”,那么首先明确一个问题什么是 RPC 呢?RPC 是 Remote P...

  • 【深度知识】RPC原理及以太坊RPC的实现

    1.摘要 本文介绍RPC协议的原理和调用流程,同时介绍以太坊RPC的实现机制。 2. 内容 2.1 RPC协议和调...

  • RPC

    RPC原理及RPC实例分析 Dubbo与Zookeeper、SpringMVC整合和使用(负载均衡、容错)

  • YarnRPC过程

    最近在看《深入解析YARN架构设计与实现原理》,看到第三章YRAN RPC实现的时候对其中如果使用ProtoBuf...

  • RPC框架的实现原理,及RPC架构组件详解

    微服务系列:RPC框架的实现原理,及RPC架构组件详解 RPC的由来 随着互联网的发展,网站应用的规模不断扩大,常...

网友评论

      本文标题:RPC原理与FastRPC实现

      本文链接:https://www.haomeiwen.com/subject/ijqzwhtx.html