gRPC入门
RPC 和 gRPC 其实是笔者在几个月前就开始接触的东西,但是由于当时在 Windows 上配不好环境加上学起来确实有点难度,所以就放下没去做项目,就简单地了解了一下。最近几天实在是没什么想学的,在拿起来看看吧,环境已经在 WSL 上配置好了,回头可以直接开始做项目了。
那今天就先来学习总结一下基础概念吧。
关于 RPC
对 RPC 不了解地人,或许会纠结其与 TCP、HTTP 等的关系。后者是网络传输种的协议,而 RPC 是一种设计、实现框架。通讯协议只是其中一部分,RPC 不仅要解决协议通讯的问题,还有序列化与反序列化,以及消息通知。
是什么?
从本质上讲,它使一台机器上的程序能够调用另一台机器上的子程序,而不会意识到它是远程的。RPC 是一种软件通信协议,一个程序可以用来向位于网络上另一台计算机的程序请求服务,而不必了解网络的细节。
一个完整的 RPC 框架里面包含了四个核心的组件,分别是 CLient
, Server
, ClientOptions
以及 ServerOptions
,这个 Options 就是 RPC 需要设计实现的东西。
- 客户端(Client):服务的调用方。
- 服务端(Server):真正的服务提供方。
- 客户端存根(ClientOption):socket 管理,网络收发包的序列化。
- 服务端存根(ServerOption):socket 管理,提醒 server 层 rpc 方法调用,以及网络收发包的序列化。
RPC 的逻辑示意图如下:
为什么?
为什么会有 RPC?RPC 和 HTTP 是什么关系?
Socket 和 HTTP 编程使用消息传递范式。客户端向服务器发送一个消息,而服务器通常会发送一个消息回来。双方都负责以双方都能理解的格式创建消息,并从这些消息中读出数据。
然而,大多数独立的应用程序并没有那么多地使用消息传递技术。一般来说,首选的机制是函数(或方法或过程)的调用。在这种方式中,程序将调用一个带有参数列表的函数,并在完成函数调用后有一组返回值。这些值可能是函数值,或者如果地址被作为参数传递,那么这些地址的内容可能已经被改变。地址的内容可能已经被改变。
RPC 就是将这种编程方式引入网络世界的一种尝试。因此,客户端将进行在它看来是正常的过程调用。客户端会将其打包成网络消息并传送给服务器。服务器会将其解包,并在服务器端将其转回为过程调用。服务器端的过程调用。这个调用的结果将被打包,以便返回给客户端。
上面的解释可谓是非常官方,看过之后也不是很清楚 RPC 到底是什么,有什么用,建议去看这篇博客,解释的非常清楚,这里给一下结论:
- 纯裸 TCP 是能收发数据,但它是个无边界的数据流,上层需要定义消息格式用于定义消息边界。于是就有了各种协议,HTTP 和各类 RPC 协议就是在 TCP 之上定义的应用层协议。
- RPC 本质上不算是协议,而是一种调用方式,而像 gRPC 和 Thrift 这样的具体实现,才是协议,它们是实现了 RPC 调用的协议。目的是希望程序员能像调用本地方法那样去调用远端的服务方法。同时 RPC 有很多种实现方式,不一定非得基于 TCP 协议。
- 从发展历史来说,HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。但现在其实已经没分那么清了,B/S 和 C/S 在慢慢融合。很多软件同时支持多端,所以对外一般用 HTTP 协议,而内部集群的微服务之间则采用 RPC 协议进行通讯。
- RPC 其实比 HTTP 出现的要早,且比目前主流的 HTTP/1.1 性能要更好,所以大部分公司内部都还在使用 RPC。
- HTTP/2.0 在 HTTP/1.1 的基础上做了优化,性能可能比很多 RPC 协议都要好,但由于是这几年才出来的,所以也不太可能取代掉 RPC。
RPC 和 REST 的区别
RPC 与 REST 最大的区别就在于 RPC 提供了更好的抽象,RPC 甚至将网络传输细节彻底隐藏了,而 REST 没有。具体来说,REST 至少要求用于提供 URL 以及请求参数,而 RPC 隐藏了与网络传输的相关实现细节。另一方面,RPC 可以基于任何网络通信协议,而 REST 通常基于 HTTP(或者 HTTPS)协议。RPC 调用者并不会关心具体的协议是:HTTP、TCP 还是其他任何自定义协议。
优缺点
以下是 RPC 为开发人员和应用程序管理员提供的一些优势:
- 帮助客户端通过传统使用高级语言的过程调用与服务器进行通信。
- 可以在分布式环境以及本地环境中使用。
- 支持面向进程和面向线程的模型。
- 对用户隐藏内部消息传递机制。
- 只需极少的努力即可重写和重新开发代码。
- 提供抽象,即对用户隐藏网络通信的消息传递性质。
- 省略许多协议层以提高性能。
另一方面,RPC 的一些缺点包括:
- 客户端和服务器对各自的例程使用不同的执行环境,并且资源(例如,文件)的使用也更加复杂。因此,RPC 系统并不总是适合传输大量数据。
- RPC 非常容易发生故障,因为它涉及通信系统,另一台计算机和另一个进程。
- RPC 没有统一的标准;它可以通过多种方式实现。
- RPC 只是基于交互的,因此,在硬件架构方面,它不提供任何灵活性。
关于 gRPC
gRPC 是 RPC 的一种,它使用 Protocol Buffer (简称 Protobuf) 作为序列化格式,Protocol Buffer 是来自 Google 的序列化框架,比 Json 更加轻便高效,同时基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特性。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。用 protoc 就能使用 proto 文件帮助我们生成上面的 option 层代码。
在 gRPC 中,客户端应用程序可以直接在另一台计算机上的服务器应用程序上调用方法,就好像它是本地对象一样,从而使您更轻松地创建分布式应用程序和服务。
gRPC 的调用模型如下:
适用场景
- 分布式场景 :gRPC 设计为低延迟和高吞吐量通信,非常适用于效率至关重要的轻型微服务。
- 点对点实时通信: gRPC 对双向流媒体提供出色的支持,可以实时推送消息而无需轮询。
- 多语言混合开发 :支持主流的开发语言,使 gRPC 成为多语言开发环境的理想选择。
- 网络受限环境 : 使用 Protobuf(一种轻量级消息格式)序列化 gRPC 消息。gRPC 消息始终小于等效的 JSON 消息。
四种调用方式
学习 gRPC 使用之前,先介绍一下 RPC 中的客户端与服务端。在 RPC 中,服务端会开启服务供客户端调用,每一句 RPC 调用都是一次客户端发请求到服务器获得相应的过程,中间过程被封装了,看起来像本地的一次调用一样,一次 RPC 调用也就是一次通讯过程。
RPC 调用通常根据双端是否流式交互,分为了单项 RPC (Simple RPC)
、服务端流式(Server-side streaming RPC)
、客户端流式(Client-side streaming RPC)
、和双向流式(Bidirectional streaming RPC)
四种方式。
单项 RPC(Simple RPC)
:客户端发起请求并等待服务端响应,就是普通的 Ping-Pong 模式。服务端流式 RPC(Server-side streaming RPC)
:服务端发送数据,客户端接收数据。客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。客户端流式 RPC(Client-side streaming RPC)
:与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。双向流式 RPC(Bidirectional streaming RPC)
:双方使用读写流去发送一个消息序列,两个流独立操作,双方可以同时发送和同时接收。
为了便于大家理解四种 gRPC 调用的应用场景,这里举一个例子,假设你是小超,有一个女朋友叫婷婷,婷婷的每种情绪代表一个微服务,你们之间的每一次对话可以理解为一次 PRC 调用,为了便于画流程图,RPC 请求被封装成 client.SayHello,请求包为 HelloRequest,响应为 HelloReply。
单项 RPC
当你在等婷婷回去吃饭,婷婷在加班时,你们之间的 rpc 调用可能是这样的:
小超:回来吃饭吗
婷婷:还在加班
这就是单项 RPC,即客户端发送一个请求给服务端,从服务端获取一个应答,就像一次普通的函数调用。
- client 层调用 SayHello 接口,把 HelloRequest 包进行序列化
- client option 将序列化的数据发送到 server 端
- server option 接收到 rpc 请求
- 将 rpc 请求返回给 server 端,server 端进行处理,将结果给 server option
- server option 将 HelloReply 进行序列化并发给 client
- client option 做反序列化处理,并返回给 client 层
服务端流式 RPC
当你比赛输了给婷婷发消息时:
小超:今天比赛输了
婷婷:没事,一次比赛而已
婷婷:晚上带你去吃好吃的
这就是服务端流式 RPC,即客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取直到没有更多消息为止。
- client 层调用 SayHello 接口,把 HelloRequest 包进行序列化
- client option 将序列化的数据发送到 server 端
- server option 接收到 rpc 请求
- 将 rpc 请求返回给 server 端,server 端进行处理,将将数据流给 server option
- server option 将 HelloReply 进行序列化并发给 client
- client option 做反序列化处理,并返回给 client 层
客户端流式 RPC
当你惹婷婷生气的时候:
小超:怎么了,宝贝
小超:别生气了,带你吃好吃的
婷婷:滚
客户端流式 RPC,即客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答。
- client 层调用 SayHello 接口,把 HelloRequest 包进行序列化
- client option 将序列化的数据流发送到 server 端
- server option 接收到 rpc 请求
- 将 rpc 请求返回给 server 端,server 端进行处理,将结果给 server option
- server option 将 HelloReply 进行序列化并发给 client
- client option 做反序列化处理,并返回给 client 层
双向流 RPC
当你哄好婷婷时:
小超:今天看了一个超好看的视频
婷婷:什么视频
小超:发给你看看
婷婷:这也叫好看?
双向流 RPC,即两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,例如:服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者是读写相结合的其他方式。每个数据流里消息的顺序会被保持。
总结
其实弄懂了单项 RPC、服务端流式 RPC、客户端流式 RPC、双向流 PRC 四种 grpc 应用场景,实现起来非常容易
- 根据应用场景选择好哪种 gRPC 服务
- 写好 proto 文件,用 protoc 生成.pb.go 文件
- 服务端实现接口 ->listen -> grpc.NewServer () -> pb.RegisterGreetsServer (server, &Server {}) -> s.Serve (lis)
- 客户端 grpc.Dial->pb.NewGreetsClient->context.WithTimeout->client.SayHello (调用接口)-> 如果是流传输则循环读取数据
代码实现这里就不实现了,因为就算写也不是我实现的,所以这里就我自己在下面理解了。
最近这两天什么都没干,做什么事都提不起来兴趣,有一种抽离感,好煎熬啊,快点出结果吧,祝我面试顺利!!!
参考
https://learnku.com/articles/58641
https://grpc.io/blog/principles/