关于rpc:RPC框架的IDL与IDLless

90次阅读

共计 3169 个字符,预计需要花费 8 分钟才能阅读完成。

IDL,Interface description language,即接口描述语言。

IDL 是一种很有用的工具,它提供了对接口的形容,约定了接口协议。使得通信单方通信时,无需再发送 scheme,无效进步了通信数据的荷载比。

但对于 RPC 框架而言,IDL 又不仅仅是一个接口描述语言。对于市面上绝大多数的 RPC 框架而言,IDL 还是一个工具和一种应用过程,专指依据 IDL 形容文件,用指定的开发语言,生成对应的服务端接口模块,和客户端程序。这样的益处是,便于开发者疾速开发。

初衷是好的,然而,问题随同而来。

首先,根据 IDL 生成对应的客户端和接口模块,这个实质是编译。

但对于编译和语言的惯例了解和意识,却导致了问题的复杂化:IDL 成了一门“编译型”的语言,倒退出了一套简单的规定和语法。而且不同 RPC 框架的 IDL 语言不齐全一样。每用一个新的框架,就有一套新的 IDL 语法和规定,就得重新学习一遍。

其次,市面上绝大多数的 IDL 语法,对于可选参数和可变类型参数,以及不定长参数存在着不同水平的反对问题。

晚期的 IDL,所有的接口字段必须存在。即便是无用的,也须要赋予一个诸如 ‘nil’ 一类的值。否则就是违反 IDL 的标准。随着技术的倒退,之后的 IDL,呈现了 optional 关键字,但仅仅是不得已的状况下,才被举荐应用。而此时,被强烈推荐应用的却是关键字 require 以及 required。比方 Thrift,ProtoBuf 1.0 都是典型的代表。直到技术一直倒退,RPC 畛域的教训积攒越来越多,关键字 require 以及 required 才不再被持续举荐应用,而关键字 optional 成为 IDL 的新宠。可选参数,到目前为止还没有遇到什么问题。直到 IDL 开始编译,生成对应的客户端和接口代码时,新的问题呈现。

接口代码的生成,确实不便了网络服务和接口调用的开发。但适度简单的接口代码,间接导致了接口的强耦合:所有的业务都依赖于 IDL 生成的客户端和服务端的接口,如果一个变动,其余的须要全副追随变动。如果一个接口被改变,与之关联的所有服务和客户端,必须全副从新编译,否则极有可能在 IDL 生成的代码中,呈现不兼容的问题。

最典型的就是,加了一个新的参数,但不在协定数据的开端,那绝大多数 RPC 框架原有的接口在解决新版接口数据时,便会呈现兼容性谬误。

当然,这在很长一段时间内被视为天经地义。

但视之“天经地义”,却不障碍开发者们对其导致的问题熟视无睹,于是不少 IDL 为了绕过这个问题,创造了“序号 / 字段编号”这种本不应该存在的语言标识。

此外,传统的 IDL 一旦遇到参数的缩小,或者参数类型的更改,IDL 所生成的 RPC 框架的接口代码,则往往须要两个独立的接口处理函数,能力同时解决新旧两个版本的数据。所以,为了更加“优雅”的解决参数的删除和类型的扭转,局部 IDL 加强了对“序号 / 字段编号”这种本不应该存在的语言标识的依赖。字段编号不可反复,一旦确定了,便不能批改。于是字段编号的保护,便成了开发者历史包袱的一部分。

而后对于不定类型的参数,反对该个性的 IDL 会选用 Oneof 或 Union 等来实现,而剩下的 IDL 则间接弃疗。

至于不定长参数,相似于 C 语言 printf 那样的参数,这对简直所有的 IDL 而言,均是噩梦般的存在。因而对于不定长参数的反对,简直所有的 IDL 都是以弃疗完结。

在通过漫长的摸索和泛滥我的项目的历练后,咱们发现,对 IDL 的应用,其实不用如此苦楚。

咱们抉择了 IDL-less 的计划,让 IDL 回归到纯正的接口形容。此时,IDL-less 的计划,不仅能轻易反对可选参数、可变类型参数,也能很好的反对参数的增减,和不定长参数。

在这个前提下,咱们应用 IDL-less 设计了新的 RPC 框架。在这个新的框架下,咱们发现,其实咱们尽管也可能依据新框架的 IDL 生成客户端和服务器的框架代码,但在更换思路后,须要生成的代码其实异样的简略,甚至对于手写来说,也齐全不是累赘。此时,如果对于传统的 RPC 框架,IDL 曾经默化为依据 IDL 文件,生成接口和框架代码的话,咱们不仅做到了 IDL-less,甚至咱们能够称为:无 IDL。

咱们的新的 IDL-less 的 RPC 框架:FPNN:Fast Programmable Nexus Network

与动则生成上万行框架和接口代码的传统 RPC 框架和 IDL 而言,因为 FPNN 框架设计之初,便将竭力简化应用复杂度作为外围设计指标之一,所以 FPNN 框架简直没有什么代码须要依据 IDL 形容生成。

首先,是框架的代码。

参考 FPNN 服务端根底应用向导“1. 服务代码框架”将发现,无论开发 TCP 服务还是 UDP 服务,算上括号与空行,规范框架一共也才 3 个文件总共 41 行代码。其中“DemoServer.cpp”24 行,“DemoServer.cpp”15 行,“DemoQuestProcessor.cpp”2 行。而参考 FPNN 客户端根底应用向导 就会发现,算上括号与空行,客户端残缺框架一共就 8 行代码,而以下 17 行代码(算上括号、空行、正文)曾经能间接运行,并向服务器发送申请,并接管应答:

#include <iostream>
#include "TCPClient.h"
 
using namespace std;
using namespace fpnn;
 
int main()
{std::shared_ptr<TCPClient> client = TCPClient::createClient("demo.example.com", 6789);
     
    //-- 生成申请数据
    FPQWriter qw(1, "echo");
    qw.param("feedback", "Example string.");
    FPAnswerPtr answer = client->sendQuest(qw.take());
    
    return 0;
}

所以,对于 FPNN 框架,简直没有什么代码是须要根据 IDL 形容文件生成的。

而后,是接口和对象的解决。

假如 IDL 中规定了一个接口 demoInterface 和一个构造体 DemoData,编译成 C++ 代码可能是以下模式:

struct DemoData
{
    int demoInt;
    double demoDouble;
    std::string demoString;
};
void demoInterface(int firstValue, const std::string& secondValue, const struct DemoData& demoData);

而在 FPNN 中,无论任何接口,接口均对立为以下模式:

FPAnswerPtr method_name(const FPReaderPtr args, const FPQuestPtr quest, const ConnectionInfo& ci);

那对于根本类型参数的获取,FPNN 仅只是多了一步:

int firstValue = args->getInt("firstValue");

std::string secondValue = args->getString("secondValue");

而对于构造体,也是仅多一步 (但从 FPNN 的理念上,构造体间接做为接口参数的这种设计,因为对构造扩大的兼容性极差,十分不举荐):

DemoData demoData = args->get("demoData", Demodata());

而对于构造体成员的获取, 应用 IDL 生成的框架代码:

double value = demoData.demoValue();

间接应用 FPNN 框架:

demoData.demoValue;

没有本质性差别。

所以,对于 FPNN 这一 IDL-less 的 RPC 框架而言,尽管能够依据 IDL 文件生成对应的框架和接口代码,但简直毫无意义。因而,FPNN 框架也不会提供依据 IDL 文件生成对应的框架和接口代码的工具。

正文完
 0