填坑系列 - gRPC

gRPC 是谷歌写的一个开源 RPC 框架,官网在这里


RPC

通常我们写代码的时候,要么调用的函数就在同一个文件里,要么在同一个工程里,要么函数在本地的其他某个库里,编译的时候加点选项就 OK 了。

1
2
3
4
5
6
7
void functionA(...)
{...}

int main()
{
functionA(...);
}

然而实际的生产环境中可能遇到的很多都是另一台机器上的 Server 提供服务,然后 Client 要去得到服务的过程。

我们当然可以说这就是本地调用通信函数跟远程的通信进程交互嘛,如果把这个过程再作一点点的抽象,RPC(Remote Procedure Call)提供了一种很有意思的思路:

即把远程的服务调用抽象成为一个类似本地函数调用的过程,我写代码的时候不管这个函数在什么地方,还是直接写:

1
2
3
4
5
6
7
8
9
10
11
12
13
void remoteFunctionA(...)
{
...
... tcp connect ...
... send/recv message ...
... return data ...
...
}

int main()
{
remoteFunctionA(...)
}

对于写这段代码的我来说,我的目标要的只是一个能工作的函数,并不关心这个函数是在什么地方,如果它不在本机上,如果有人能给我一个实现好的框架来实现调用,那么我也并不关心里面的通信部分到底是怎么写的。

RPC 框架就是完成了这个任务,高度封装之后,直接体现在工作代码里面就是一个简单的函数调用,背后的通信等等实现都被隐藏在框架里面了。

大概来说,一般的 RPC 都是这么个结构:

Server 和 Client 的应用中都有一个叫 Stub 的结构,这个结构用于找到需要调用的函数在哪台机器上或者对远程向本地的服务申请作出响应。简单地说就是靠它来完成所有工作细节的封装,向上只要给出一个类似本地函数调用一样的接口(当然实际中肯定还需要一些别的东西),向下它会完成网络通信部分的任务。

中间通常还会有个序列化/反序列化的过程,则完整的 RPC 过程是类似这个样子的:

  • 底层的数据传输用的是二进制序列的形式, Client 需要调用 RPC 服务的时候,首先将方法名、各种参数等等序列化为一串二进制数据,然后将其发送给 Server

  • Server 收到二进制数据之后反序列化得到需要调用的详细内容,交给上层处理

  • 上层处理完成之后,将返回值等等信息重新序列化为二进制数据,发送给 Client

  • Client 收到二进制数据,反序列化得到想要的结果,返回给上层


gRPC

回到这篇日志的主体内容。

谷歌之前一直用的 RPC 服务随着时代发展已经越来越不适合他们自己的使用了,于是人家在一堆原则和诉求下新写了一个 gRPC 用于支持他们的各种服务。

不愧是谷歌的东西,事实上 gRPC 实现得非常好,性能强,上层支持十多种不同的编程语言混合通信,还支持各种平台…满满的都是优点。

怨念时间:
但是这玩意让你拿去用当然都是的优点,需要你去读代码和改代码的时候就成了巨大的坑点。
为了支持多语言、多平台, gRPC 的层次结构非常复杂,而且代码部分缺乏足够的文档,读代码真的是把人折磨得死去活来啊。

坑死爹了!

之前的手上的任务是把这玩意从 TCP 改成 RDMA ,啃代码花了不少时间。

具体关于代码部分,之后看情况要不要再补写到这里吧。


To be continued…

0%