1、RPC
1.1 什么是RPC
RPC(Remote Procedure Call),即远程过程调用,过程就是方法,简单来说,它就是一种能够像调用本地方法一样调用远程计算机进程中的方法的技术,在这种调用中,我们不需要了解任何网络通信的细节(当然,就使用来说)
最终解决的问题:让分布式或者微服务系统中不同服务之间的调用像本地调用一样简单
1.2 RPC和HTTP
调用远程服务,HTTP 就可以完成的任务,为什么还需要 RPC 呢?需要注意,这两个并不是同一层次的概念,HTTP 是一种传输协议,RPC 应该是比 HTTP 更高层级的概念。完整的 RPC 实现包含有 传输协议 和 序列化协议,其中,传输协议既可以使用 HTTP,也可以使用 TCP 等,不同的选择可以适应不同的场景
RPC 并不是一个崭新的概念,它实际上就是远程通信的一个更高层级的封装,不同传输协议和序列化协议的组合构成了不同的具体 RPC 实现,比如我们熟知的 RESTful,就是 HTTP + JSON + 一些其他细节构成
1.3 RPC技术的演化
早期有一些很流行的 RPC 实现,比如 CORBA(通用对象请求代理体系结构),Java RMI(远程方法调用),它们都用来构建和链接服务或应用程序,但是,大多数传统 RPC 实现极其复杂,因为它们构建在 TCP 之上,并且还有大量的规范限制
鉴于以上 RPC 实现的局限性,SOAP(简单对象访问协议)应运而生,SOAP 是 SOA(面向服务的架构)中的标准通信技术,能够基于任意底层通信协议进行通信,最常用的是 HTTP,序列化协议使用的是 XML
REST(描述性状态转移)是 ROA(面向资源的架构)的基础,在这种架构中,将应用程序建模为各种资源的集合,客户端可以变更这些资源的状态(增删改查)。REST 的通用实现是 HTTP + JSON,通过 HTTP 将应用程序建模为能够通过唯一标识符表示的资源集合,状态变更操作会采用 HTTP 动作(GET,POST,PUT,DELETE等)。实际上,REST 架构风格已经成为了各种服务间通信中非常流行的方法,但是,随着微服务大行其道以及网络交互的激增,REST 已经无法满足现代化的需求了,其主要原因是以下三个主要的局限性:
- 基于文本的消息协议效率太低。REST 服务建立在基于文本的传输协议 HTTP1.x 上,使用人类可读的文本格式如 JSON,但是,很多时候我们并不需要这种可读性,如果能够直接发送映射服务和客户端业务逻辑的二进制内容,将大大提高效率
- 缺乏强类型接口。开发 REST 服务时,应用程序之间并不需要共享服务定义和类型定义,我们要么通过网络查看文本格式,要么通过 API 文档,构建这种分散的应用程序时,会遇到很多不兼容、运行时错误和互操作等问题
- REST架构风格难以实施。REST 架构风格有很多 “好的实践”,遵循这些实践能构建出真正好用的 REST 服务,但是,它们并没有作为协议的一部分进行强制要求,事实上,大多数 REST 服务不过是通过网络公开的 HTTP 服务,并没有很好地遵循基础的架构风格
由于 REST 的局限性,出现了许多新兴的 RPC 技术,较为流行的有 gRPC、Thrift、GraphQL等
2、gRPC
2.1 gRPC简介
gRPC 是一个现代化的开源 RPC 框架,一开始由 google 开发,是一款语言中立、平台中立、的 RPC 系统,与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个 服务,指定能够被远程调用的 方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个gRPC 服务器来处理客户端调用,在客户端拥有一个 stub 连接服务端上的方法
2.2 gRPC的优势
gRPC 的优势是它被越来越多人采用的关键所在,主要有以下几个方面:
- 提供高效的进程间通信。使用一个基于 protocol buffers 的二进制协议而不是文本格式与客户端通信,同时在 HTTP2 上实现,拥有更好的性能
- 具有简单且定义良好的服务接口。契约优先,必须首先定义服务接口,然后才能去处理细节,简单一致,可扩展
- 强类型。服务契约清晰地定义了应用程序间通信所使用的类型,分布式应用程序的开发更加稳定
- 支持多语言。基于 protocol buffers 的服务定义是语言中立的,可以选择任意一种语言具体实现
- 支持双工流。与传统的 REST 相比,gRPC 能够同时构建传统的请求-响应风格的消息以及客户端流和服务端流
- 具备内置的商业化特性。如认证、加密、弹性时间、元数据交换、压缩、负载均衡以及服务发现等
- 与云原生生态进行了集成。gRPC 是 CNCF(云原生计算基金会)的一部分,大多数现代框架和技术都对 gRPC 提供了原生支持
- 业界成熟。通过在谷歌进行的大量实战测试,gRPC 已经发展成熟,被许多公司采用
2.3 gRPC的缺点
gRPC 也存在一定劣势,选择它用来构建应用程序时,需要注意以下三点:
- gRPC 不太适合面向外部的服务。gRPC 具有契约驱动、强类型等特点,这会限制向外部暴露服务的灵活性,对客户端有诸多限制,所以更适合用在内部服务器之间通信
- 避免巨大的服务定义变更。如果出现巨大的服务定义变更,通常需要重新生成客户端代码和服务端代码,会让整个开发生命周期变得复杂,需要小心引入破坏性的变更
- 生态系统相对较小。与传统 REST 等协议相比,gRPC 仍然处于起步阶段,浏览器和移动应用程序对 gRPC 的支持才刚刚起步
3、一个简单gRPC服务的golang实现
3.1 环境准备
- 下载 protoc 编译器:protobuf,选择合适的平台,解压后将可执行文件加入环境变量,此编译器用来编译服务定义文件 .proto 生成指定语言的目标代码,这些代码用来实现 gRPC 服务以及客户端 stub
- 安装 grpc-go 插件用来生成 go 目标代码
go install google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
- 创建代码目录 product-info,实现一个简单的查看商品信息和添加商品的 rpc 服务,在其中新建三个文件夹 proto、server、client 分别存放服务定义文件和生成的目标代码、服务端程序实现、客户端程序实现,然后执行
go mod init product-info
初始化模块。当然这里只是示例程序,实际场景中服务代码和客户端代码一般都不在同一个机器上,更不可能在同一个模块下了,最终目录结构如下: