gRPC-go源码(1):连接管理

在这个系列的文章中,我们将会从源码的层面学习和理解gRPC。

整个系列的文章的计划大概是这样的:我们会先从客户端开始,沿着调用路径逐步分析到服务端,以模块为粒度进行学习,考虑这个模块是为了解决什么问题,然后思考gRPC应该怎么去解决这个问题。在分析完这部分的架构设计后,我们会在接下来的一篇文章中研究具体的代码实现。

因此,这个系列的文章不会像之前的源码分析那样贴一大段的代码,然后加上注释。这样做不但使得阅读成本很高,而且很难学到除了代码实现以外的东西。

我们会先从客户端开始,沿着调用路径,逐步分析到服务端。

2 什么是RPC

在阅读gRPC的源码之前,我们先思考trong>实现一个RPC框架,应该提供什么样的功能?trong>

在我们上一篇文章的内容中,我们已经知道了gRPC的使用方式。简单的来讲,就是对于同一个方法,在服务端实现具体的逻辑,在客户端发起调用,就能够实现“远程过程调用”。

那么,我们要怎么实现这个过程呢?

那么我们很容易可以推测,无论是客户端还是服务端,在我们调用的方法背后肯定还封装了一套复杂的逻辑,负责把客户端的调用“发送”到服务端中,而服务端中也封装了一套复杂的逻辑,负责接收客户端发送过来的请求,并根据接收到的数据选择对应的方法,执行完后把结果“返回”给客户端。

于是我们会接着推测,这部分复杂的逻辑有什么呢?

我们以客户度为例:首先,我们需要跟服务端建立连接。当我们调用某个远程方法的时候,我们需要令服务端得知客户端调用的是哪个方法、有哪些参数等,这意味着我们需要设计一种协议,这个协议承载了以上的信息。最后,把我们的数据塞进这个协议中,编码成二进制的格式,塞进网络中。

而对于服务端来说也是一样的,从网络IO中接收到二进制的数据之后需要进行解码,然后根据解码后的数据得知需要调用的方法名、参数,在执行完相应的方法后将结果发送回客户端中。

这样就足够了吗?

还不够,我们还需要通过一种方式,将以上的逻辑封装起来,避免每次调用的时候都写这么一大堆的重复代码。也就是说,我们的开发人员不需要知道底层调用细节,他只需要定义方法和调用方法,剩下的都交给框架。

至此,我们就实现了一个最基本RPC框架。

但是你可能会有一个问题,如果RPC框架只是提供了一个通信的功能,那么他存在的意义是什么呢?

如果只是为了解决通信的问题,我们不需要费尽心思来开发这么一个新的框架,我们可以用RESTful API,甚至你也可以直接把数据塞进TCP报文中。

答案是这样的,虽然我们称RPC为远程过程调用,但是RPC框架不仅仅是能够实现服务间的通信,它还提供了一些服务治理、负载均衡、流量控制等方面的功能。

因此,当我们谈到了RPC框架这个话题的时候,通常我们说是提供了以远程过程调用为核心的一整套解决方案。

3 如何实现gRPC

上一节中,我们聊了聊一个RPC框架应该提供哪些功能。在这一节中,我们来聊聊gRPC实现了哪些功能。

3.1 连接管理

为了让连接变得更可靠和高效,gRPC需要对连接进行管理。

考虑这样的一种情景,由于公司规模的扩大、流量的增加,gRPC的服务端由单机扩展成了一个集群。这个时候,我们的客户端需要调用服务端中的某一个方法,那么这个客户端需要向哪台机器建立连接,发送数据呢?

如果我们把这个问题划分的更具体,那么可以需要解决的问题如下:

假设现在这个集群里面有很多台机器,那么我们该怎么告知客户端每台服务端机器的ip:port呢?

假设我们新增或减少了一些gRPC的服务端,客户端该怎么更新它所维护的ip:port列表呢?

假设客户端当前请求的服务端,存在了多个ip:port,那么这个客户端该向哪个连接发送数据呢?

这几个问题可以归结为,gRPC如何解决服务注册服务发现负载均衡的问题。

然而,gRPC并没有提供诸如Spring Cloud、Dubbo等框架的服务注册、服务发现的功能。

我想gRPC这么做的原因大概是为了能够提供更灵活的服务发现和负载均衡功能。

3.2 Resolver

Resolver称为解析器,能够将客户端传入的“符合某种规则的名称”解析为IP地址列表。

假设你定义了一种地址格式:aaa:///bbb-project/ccc-srv

然后Resolver会将这个地址解析成好几个ip:port,代表了提供ccc-srv服务集群的所有机器地址。

这就是Resolver的作用。

那么,Resolver是怎么进行解析的呢?换句话说,Resolver是如何做到输入某种地址,输出一串IP地址呢?

这部分的工作需要由用户自己实现。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpyzwg.html