在第 5 篇写 Interceptor 中,我介绍了可以横向纵向将 Interceptor 分为 Unary 和 Stream 的,但是并没有细说他们之间的区别,所以这里准备来细说一下。其实在 GRPC 的定义中,他们是区分了 4 种类型的服务方法的,其实也就是我们在第 5 篇中的横向纵向组合,其中 Unary 就是我们比较熟悉的一种,类似于函数调用或者说普通的 HTTP 请求一样,由一端发送(调用)请求,然后等待(接收)另外一端的响应,当收到响应或者调用失败返回为止,作为一个回合,然后这个回合就结束了,没有更多的情况;与之对应的就是 Stream 了,顾名思义,这是一个流,也就意味着一端可以多次发送数据给对端,直到没有数据可以发送或者中途出错,在 GRPC 中,可以有客户端请求是 Stream 服务器响应是 Unary,客户端请求是 Unary 服务端是 Stream 或者客户端和服务器都是 Stream 的情况。

那么这里问题就来了,如果是 Stream 的哈,怎么知道这个 Stream 到头了?这是个核心的问题,但是解答这个问题之前,我还是决定先来个 Sample 看看 Stream 的用法,也许看完之后都不需要我回答了。

在开始之前,还是先看下 Proto 是怎么写的:

这里我设计了 3 个接口,用于示例不同的功能,第一个是求和,要求客户端可以发送不等数量的数字,然后服务端最后一次性返回客户端发送数字的和;第二个是算斐波那契数列的示例,客户端发送一个上限,然后服务端每次都传递一个结果回来,直到超过上限;第三个就是客户端和服务器双向流的通信了,客户端和服务器都是流式的处理。通过这 3 个例子,希望能够给你对 GRPC 的 Stream 有一些了解。

下面就先来第一个的服务端实现和客户端请求:

这是一个 Client 端是 Stream 的,但是 Server 端是 Unary 的一个示例,从 Server 端的接收代码可以看到,对于 Stream,需要持续得进行 Recv,然后关注每次 Recv 的结果,如果有错误得细分一下错误得类型,当接收完毕之后,会又一个 io.EOF 的 error 类型,这个需要注意,别直接 return 回去了。这里还有一个注意点就是即使 Server 端是 Unary 的,但是结果也不是直接就 return 回去了,而是需要 SendAndClose,这也是 Stream 的一个区别点。

看完 Server 端,再来看看 Client 端,在 Client 这里,我们调用函数不是直接就传参数了,而是获得一个和 Server 端通信的 Client 客户端,然后通过这个客户端我们可以多次得向 Server 发送数据,当我们觉得发完之后,那么就可以 CloseAndRecv 了,毕竟这是单向的数据流,如果我们还没完,也不会去拿服务端的结果。

那么看完 Stream Client + Unary Server 之后,那么再看 Unary Client 和 Stream Server 就显得比较简单了:

当试完这两个例子之后,你可能会想那么是不是 Stream Client + Stream Server 就是两种方式的合集?但是可能结果和你想的会稍微有点不太一样,GRPC 的 Go 实现是对这 3 种情况进行了不同的处理,反而 Bidirectional Streaming(双向流式)会更简单一些,这里我就以 价值一个亿的 AI 代码 为示例进行介绍一个。

前面两个都不看效果,这个价值不菲的代码肯定得看下结果啦:

虽然 GRPC 是支持双向流,但是从上面的 3 个示例也可以看出,使用流无论是使用者还是开发者都是需要做一些额外的处理,比较麻烦,所以这就要求我们在开发选择的时候需要根据自己的业务场景恰当得选择合适的方式,恰如其分得以较好的架构实现较好的业务。本文的所有代码都可以在这里找到: 点击跳转