目录
[一、gRCP - 面向未来的第二代 RPC 技术](#一、gRCP - 面向未来的第二代 RPC 技术)
[1.1、gRPC 简介](#1.1、gRPC 简介)
[1.1.1、gRPC 是个啥?](#1.1.1、gRPC 是个啥?)
[1.1.2、gRPC 核心设计思路](#1.1.2、gRPC 核心设计思路)
[1.1.3、gRPC 和 ThriftRPC 区别](#1.1.3、gRPC 和 ThriftRPC 区别)
[1.1.4、为什么使用 gRPC?(好处)](#1.1.4、为什么使用 gRPC?(好处))
[1.2、HTTP2.0 协议](#1.2、HTTP2.0 协议)
[1.2.1、回顾 HTTP1.0 和 HTTP1.1](#1.2.1、回顾 HTTP1.0 和 HTTP1.1)
[1.2.2、HTTP2.0 协议](#1.2.2、HTTP2.0 协议)
[1.3、Protocol Buffers(Protobuf)](#1.3、Protocol Buffers(Protobuf))
[1.3.1、protobuf 是什么](#1.3.1、protobuf 是什么)
[1.3.2、安装 Protobuf 编译器](#1.3.2、安装 Protobuf 编译器)
[1.3.3、protobuf 语法](#1.3.3、protobuf 语法)
一、gRCP - 面向未来的第二代 RPC 技术
1.1、gRPC 简介
1.1.1、gRPC 是个啥?
gRPC 是 Google 开源的一个高性能的 RPC 框架,高效实现进程间通信。Studdy Google 内部的 RPC 演化而来,2015 正式开源. 云原生时代是一个 RPC 标准.
1.1.2、gRPC 核心设计思路
对于远程调用的的设计思路,一般都是以下四个方面:
- 网络通信:gRPC 自己封装网络通信的部分,并提供多种语言的封装(C、Java[Nerry]、GO).
- 协议:gRPC 使用 HTTP2 传输数据(二进制数据内容),支持双工(双向流)连接的多路复用.
- 序列化:基于 Protobuf 的序列化方式,时间效率和空间效率都是 JSON 的 3~5 倍.
- 代理的创建:让调用者像调用本地方法一样,去调用远端的服务方法.
1.1.3、gRPC 和 ThriftRPC 区别
共同点:支持异构语言的 RPC.
不同点:
- 网络通信:ThriftRPC 使用 TCP 专属协议. gRPC 使用 HTTP2 协议.
- 性能方面:ThriftRPC 高于 gRPC.
- 应用广度:gRPC 大厂背书(Google),云原生时代和其他组件合作很顺利,因此应用更广泛.
1.1.4、为什么使用 gRPC?(好处)
- 高效:基于 HTTP2 协议,传输二进制数据内容,又基于 Protobuf 实现序列化,高效的进行进程间通信.
- 支持多种语言:原生支持 C、GO、Java 实现。C语言版本上可扩展 C++、C#、NodeJS、Python、Ruby、PHP.
- 跨平台:支持多平台运行 linux、Android、IOS、MacOS、Windows.
- 大厂背书:Google 力推.
1.2、HTTP2.0 协议
1.2.1、回顾 HTTP1.0 和 HTTP1.1
HTTP1.0 协议:
- 请求响应模式:一个请求对应一个响应.
- 短链接协议:无状态协议,客户端不认识服务器,反之也一样,可引入 Session - Cookie 机制解决.
- 传输数据:文本结构.
- 单工:无法实现服务端推送机制,要实现必须让客户端 "轮询".
HTTP1.1 协议:
- 请求响应模式:一个请求对应一个响应.
- 有限长连接:可升级为 WebSocket 协议,实现服务器向客户端的消息推送机制.
- 传输数据:文本结构.
- 双工:服务器可对客户端进行消息推送.
可以看出,HTTP1.x 协议的共性如下:
- 传输数据都是文本格式,可读性好,但是效率差.
- 本质上 HTTP1.x 协议无法实现双工通信.
- 关于资源请求,需要发送多次请求,建立多个连接才可以完成. 例如一般发送一个请求第一次请求到的是一个 HTML 格式的数据,然后还需要继续发两次请求,以异步的方式请求 JS 和 CSS 文件(网络通信也是需要开销的,三次握手、四次挥手......).
1.2.2、HTTP2.0 协议
- 二进制通信:HTTP2.0 协议是一个二进制协议,效率高于 HTTP1.x,但是可读性较差.
- 实现双工通信:服务器可对客户端进行消息推送.
- 实现了多路复用机制:一个连接可以请求多个数据.
具体的通信过程,首先要明确 HTTP2.0 协议以下三个重要概念:
- 数据流(Stream):客户端和服务器之间传输数据的通道(一个连接中可以有多个数据流).
- 消息(message):一个消息中就包含了多个帧.
- 帧(frame):就是一些具体的请求头,请求体信息.
如下图:
Ps:
- 数据流的优先级:可以通过权重的方式设置的,用来限制的不同流的传输顺序.
- 流控效果:client 发送数太快了,server 处理不过来,就会通知 client 暂停数据的发送.
1.3、Protocol Buffers(Protobuf)
1.3.1、protobuf 是什么
protobuf 是一种与编程语言无关,与具体平台无关(任意操作系统)的序列化工具,自定义了中间语言(IDL),使得数据在 client 和 server 中进行 RPC 传输.
Ps:protobuf 有两个版本(proto2 和 proto3),主流应用都是 proto3.
1.3.2、安装 Protobuf 编译器
protobuf 安装编译器的目的就是为了把 protobuf 的 IDL 语言,转化成某一种开发语言,例如 Java.
a)下载地址:Releases · protocolbuffers/protobuf · GitHub
新版没有提供 windows 版本的安装包,可以去老版本找到,例如Protocol Buffers v23.1
b)下载好后解压,配置环境变量 path
c)打开终端,输入 protoc --version,检查是否配置成功(查看版本)
1.3.3、protobuf 语法
a)文件格式:文件都是以 proto 为后缀,例如 UserService.proto、OrderService.proto.
b)版本设定:使用 proto3 即可
syntax = "proto3";
c)注释:// 表示单行注释,/* */ 表示多行注释.
d)Java 语言相关
//protobuf 生成的 Java 代码,是一个源文件还是多个?false 表示一个(一般开发就用 false)
option java_multiple_files = false;
//指定 protobuf 生成的类,放置在哪个包中
option java_package = "com.cyk";
//指定 protobuf 生成的外部类的名字(用来管理内部类[内部类才是真正开发使用的])
option java_outer_classname = "UserService";
e)逻辑包:protobuf 对文件内容的管理(作为 Java 工程师,可以不用逻辑包,用 Java 包就够了,逻辑包了解就行)
package xxx
f)导入:假设有 A.proto 和 B.proto 文件,现在需要在 B.proto 文件中引入 A.proto 文件的内容,就需要在 B.proto 文件中导入 A.proto.
// 在 B.proto 文件中导入 A.proto 文件
import xxx/A.proto
g)枚举:枚举值必须是从 0 开始.
Kotlin
enum SEASON {
SPRING = 0;
SUMMER = 1;
}
h)数据类型:就是消息中定义的数据类型(我们主要关心 .proto 对应的 Java 类型).
以下列表来自官网:Language Guide (proto 3) | Protocol Buffers Documentation
.proto Type | C++ Type | Java/Kotlin Type[1] | Python Type[3] | Go Type | Ruby Type | C# Type | PHP Type | Dart Type | |
---|---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | double | |
float | float | float | float | float32 | Float | float | float | double | |
int32 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int | |
int64 | int64 | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | |
uint32 | uint32 | int[2] | int/long[4] | uint32 | Fixnum or Bignum (as required) | uint | integer | int | |
uint64 | uint64 | long[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 | |
sint32 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int | |
sint64 | int64 | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | |
fixed32 | uint32 | int[2] | int/long[4] | uint32 | Fixnum or Bignum (as required) | uint | integer | int | |
fixed64 | uint64 | long[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 | |
sfixed32 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int | |
sfixed64 | int64 | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | bool | |
string | string | String | str/unicode[5] | string | String (UTF-8) | string | string | String | |
bytes | string | ByteString | str (Python 2) bytes (Python 3) | []byte | String (ASCII-8BIT) | ByteString | string | List |
i)消息(Message):定义了客户端和服务器一次请求和相应的具体格式
Haskell
//1.Ps: 编号范围是[1, 2^29-1],但是 19000~19999 不能使用,因为他是 protobuf 自己保留的
message LoginRequest {
string username = 1;
string password = 2;
int32 age = 3;
}
//2.singular: 表示这个字段的值只能有 0 个或 1 个,也就是 null 或者是一个具体的值
//repeated: 表示 Java 中的 List 类型
message LoginResponse {
string content = 1;
repeated string status = 2;
}
//3.消息可以嵌套
message LogoutRequest {
message User {
int64 userId = 1;
string username = 2;
}
string aaa = 1;
string bbb = 2;
User user = 3;
}
//4.可以使用其他消息的属性
message Test1Message {
string aaa = 1;
LogoutRequest.User bbb = 2;
}
//5.oneof 表示其中一个(实际开发中用的很少)
message Test2Message {
oneof test_oneof {
string aaa = 1;
string bbb = 2;
}
}
j)服务:用来定义服务接口,一个接口中可以有多个服务方法(gRPC 的 4 个服务方式下一章再展开讲~).
语法如下:
Haskell
service 自定义接口名 {
rpc 自定义方法名(参数类型) returns(返回值类型) {}
//......
}
例如:
Haskell
message LoginRequest {
string username = 1;
string password = 2;
int32 age = 3;
}
message LoginResponse {
string content = 1;
repeated string status = 2;
}
service UserService {
rpc login(LoginRequest) returns(LoginResponse) {}
}