1. 概述:
gRPC(gRPC Remote Procedure Calls)是一个高性能、开源的远程过程调用(RPC)框架,由 Google 开发。它支持多种编程语言,旨在简化和优化分布式系统中的服务通信。
2. gRPC的优势:
高性能:使用 HTTP/2 和 protobuf 使得 gRPC 在性能和效率方面表现出色。二进制协议和 HTTP/2 的多路复用特性使其通信开销低,速度快。
简化开发 :自动代码生成和多语言支持简化了微服务的开发和维护。通过 .proto
文件定义接口后,gRPC 工具会生成相应的客户端和服务器代码,大大减少了手动编码的工作量。
强类型:使用 protobuf 定义服务接口和消息类型,确保强类型检查和错误检测。开发人员可以在编译时捕捉到许多错误,提高代码的可靠性和可维护性。
灵活的流处理:支持多种通信模式(单次请求-响应、服务端流、客户端流、双向流),适应不同的使用场景。例如,可以用服务端流实现数据的实时推送,用双向流实现实时聊天功能。
高效的序列化:Protocol Buffers 是一种高效的二进制序列化格式,序列化和反序列化速度快,生成的数据体积小,适合高性能场景。
3. 实现逻辑:
- 定义一个服务,指定被调用的方法(包含参数和返回类型)。
- 运行 gRPC 服务器来处理客户端的调用。
- 在客户端拥有一个存根,能够像服务端一样的方法。
4. Node.js:
Node.js 库从运行时加载的 .proto
文件动态生成服务描述和客户端存根的定义,所以使用此语言时没必要生成任何特殊代码。而是在例子客户端和服务端里,我们 require
gRPC 库,然后用它的 load()
方法,就可以去加载.proto
文件。
5. 为什么 nodejs
推荐动态加载.proto
文件?
使用 @grpc/proto-loader
库在运行时动态加载 .proto
文件。这是官方推荐的方法
优点:
- 开发便捷:不需要在每次修改
.proto
文件后重新生成代码,开发过程更加便捷 - 灵活性:适合快速迭代和频繁修改
.proto
文件的项目 - 减少依赖:不需要安装
protoc
编译器
缺点:性能:由于在运行时解析 .proto
文件,可能会有一些性能开销,但通常可以忽略不计
6. RPC 生命周期:
定义服务的四类方法:
-
单项 RPC:
proto 复制代码 rpc SayHello(HelloRequest) returns (HelloResponse) {}
-
服务端流式 RPC:
proto 复制代码 rpc SayHello(HelloRequest) returns (stream HelloResponse) {}
-
客户端流式 RPC:
proto 复制代码 rpc SayHello(stream HelloRequest) returns (HelloResponse) {}
-
双向流式 RPC:
proto 复制代码 rpc SayHello(stream HelloRequest) returns (stream HelloResponse) {}
截止时间:客户端可以设置响应的过期时间
RPC 终止
取消 RPC:同步调用不能被取消
元数据集 :特殊 RPC
调用对应的信息(键值对形式)
流控制
配置
频道
同步、异步
7. 定义.proto
文件:
关于 Protocol Buffers
的语法教程请看主页对应文章
protobuf
// 使用 proto3 语法,不指定的话默认 proto2
syntax = "proto3";
// 是否需要生成的类拆分为多个
option java_multiple_files = true;
// 生成的类所属的层级
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
// 定义报名,并于避免命名冲突
package helloworld;
// 定义服务
service Greeter {
// 定义:
// 1. 参数
// 2. 返回类型
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}
}
// 结构化数据:使用 message 定义。
message HelloRequest {
string name = 1; // 字段类型 字段名 = 字段编号;
}
message HelloReply {
string message = 1;
}
8. 创建 nodejs
的 gRPC
服务端:
protobuf
// Protocol Buffers 文件
let PROTO_PATH = __dirname + './helloworld.proto';
// 用户实现 gRPC 服务和客户端的核心库
let grpc = require('@grpc/grpc-js');
// 加载 .proto 文件
let protoLoader = require('@grpc/proto-loader');
/**
* protoLoader.loadSync(PROTO_PATH, { ... }) 方法
* 从指定的 .proto 文件加载定义,并根据选项配置进行解析。
* 使用到 protoLoader 一个 Node.js 模块
*/
let packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true, // 保持字段名称的大小写
longs: String, // 将 Protocol Buffers 中的 long 类型字段解析为 JavaScript 字符串
enums: String, // 将枚举类型转换为字符串
defaults: true, // 为所有字段设置默认值
oneofs: true // 支持 oneof 字段,这是一种在 Protocol Buffers 中定义的互斥字段
});
/**
* grpc.loadPackageDefinition(packageDefinition) 方法
* 将 @grpc/proto-loader 生成的描述加载到 gRPC 库中,
* 将加载的 Protocol Buffers 描述转换为 gRPC 服务端可以使用的 JavaScript 对象。
* 以创建客户端和服务端。
*/
let hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
/**
* 定义 RPC 的方法
*/
function sayHello(call, callback) {
callback(null, { message: 'Hello ' + call.request.name });
}
function main() {
// 1. 创建 gRPC 服务器实例
let server = new grpc.Server();
// 2. 将 Greeter 服务和实现方法添加到 gRPC 服务器中
server.addService(hello_proto.Greeter.service, { sayHello: sayHello });
/**
* 3. 绑定服务器到指定的地址和端口,并使用不安全的凭据(没有 SSL/TLS)
* grpc.ServerCredentials.createInsecure(): 创建不安全的服务器凭据
*/
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), (err, port) => {
if (err != null) {
return console.error(err);
}
console.log(`gRPC listening on ${port}`)
});
}
main();
9. 创建 nodejs
的 gRPC
客户端:
protobuf
let PROTO_PATH = __dirname + './helloworld.proto';
// 用户实现 gRPC 服务和客户端的核心库
let grpc = require('@grpc/grpc-js');
// 加载 .proto 文件
let protoLoader = require('@grpc/proto-loader');
// 加载 .proto 文件,并且根据配置项进行解析
let packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true, // 保持字段名称的大小写
longs: String, // 将 Protocol Buffers 中的 long 类型字段解析为 JS 字符串
enums: String, // 将枚举类型转换为字符串
defaults: true, // 为所有字段设置默认值
oneofs: true // 支持 oneof 字段
});
// 将生成的描述添加到 gRPC 库中,并输入可以使用的 JS 对象,来创建服务端和客户端
let hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
function main() {
// 定义 gRPC 服务器的地址和端口
let target = 'localhost:50051';
// 创建一个客户端存根,存根(抽象层):hello_proto.Greeter,来调用远程服务
let client = new hello_proto.Greeter(target,
grpc.credentials.createInsecure());
let user = 'world';
// 调用远程服务方法
client.sayHello({ name: user }, function (err, response) {
console.log('Greeting:', response.message);
});
}
main();
10. 核心特点
- 多语言支持:gRPC 支持多种编程语言,包括 C++, Java, Python, Go, Ruby, Node.js 等,使得跨语言的微服务之间可以无缝通信。
- 基于 HTTP/2:gRPC 使用 HTTP/2 协议,这带来了许多优点,如多路复用、流量控制、头部压缩和双向流。
- 使用 Protocol Buffers:gRPC 使用 Protocol Buffers(protobuf)作为接口定义语言(IDL)和消息交换格式。这种二进制格式既高效又便于跨语言。
- 自动生成代码 :通过使用
.proto
文件定义服务和消息类型,gRPC 工具链可以自动生成客户端和服务器端的代码,大大简化了开发工作。 - 全双工流式处理:gRPC 支持双向流式处理,这意味着客户端和服务器可以在单个连接上独立地发送和接收多个消息。
11. 典型应用场景
- 微服务通信:gRPC 非常适合在微服务架构中用来实现高效的服务间通信。
- 实时通信:通过双向流处理,gRPC 可以用于实时聊天、数据流和其他需要低延迟的应用。
- 跨语言通信:gRPC 的多语言支持使其成为异构系统中不同语言组件之间通信的理想选择。