
动机
我们 Node 项目的后端请求没有直接使用 HTTP 模块,而是采用了 gRPC on Node.js[1](@grpc/grpc-js),而之前没有接触过。
概念
学一个东西最快的方式,就是先找个 DEMO 跑起来(我已经找到了),直接感受下。不过 gPRC 属实有点复杂,我们还是要从几个简单的概念开始认识。
gRPC
是 Google RPC 的简写,是 Google 基于 RPC 开发的一个通信框架(相当于是 PRC 一个实现方), 也是一套通用的通信方案。因此,「gRPC 是语言无关的,并不限制是用哪个语言」(比如:JavaScript(Node 平台) 、Java 等),你可以在官网《Supported languages》[2]里看到 gRPC 支持的语言,并跟随里面的例子,快速开始。
RPC
全称是“Remote Procedure Call”,即“远程过程调用”,是一种远程通信模型,这个模型定义了一个计算机程序在不同的地址空间中,如何调用另外一个地址空间的子程序或函数。
「RPC 中并没有规定使用何种通信协议传输数据」,既可以使用 HTTP,也可以使用 TCP、UDP、HTTP2。像 gRPC 内部就是使用 HTTP2 这个 HTTP 协议的最新版本实现数据传输的。
Protocol Buffers
Protocol Buffers[3] 简称 ProtoBuf,跟 JSON 一样,就是一种数据格式,官方叫“序列化协议”,存储这种数据格式的文件使用的是 .proto
后缀。它是由 Google 开发的,与相对于数据格式协议(如 XML 和 JSON 等)一样,与平台、语言无关,不过更小、更快。
在 gRPC 中,Protocol Buffers 被用作 IDL(Interface Definition Language,接口定义语言)使用(目前使用最新的第 3 版居多,又称“proto 3”[4]),所以你会看到一个个服务都是在一个个 .proto
文件中定义的。
最后要说的是,相对于在 Node 端直接使用 HTTP 模块,从使用感受上,gRPC 对开发者而言,更像是是一个本地调用,而不是远程调用。
快速开始
❝
我们这里的快速开始教程,基于官方 Node 端教程[5]和仓库代码(grpc/grpc-node)[6]内容整合而成,学有余力的同学可以自己看看。
❞
下载 example
# Clone the repository to get the example code
$ git clone https://github.com/grpc/grpc-node.git
# Navigate to the node example
$ cd grpc-node/examples
# Install the example's dependencies
$ npm install
# Navigate to the dynamic codegen "hello, world" Node example:
$ cd helloworld/dynamic_codegen
运行 gPRC 应用
# 启动服务
$ node greeter_server.js
# 运行客户端
$ node greeter_client.js
Greeting: Hello world
程序运行后,我们会在控制台看到“Greeting: Hello world”的消息输出。
至此,我们就成功运行起来了一个 gPRC 应用 了。
增加一个新的 gRPC 服务方法
现在,我们修改下服务,增加一个调用方法供客户端消费。查看 greeter_server.js 文件,会看到以下代码:
var PROTO_PATH = __dirname + '/../../protos/helloworld.proto';
var grpc = require('@grpc/grpc-js');
var protoLoader = require('@grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
这里 「helloworld.proto
就是定义服务接口的地方」,使用 Protocol Buffers 数据格式定义;helloworld.proto
文件接口都定义在了 helloworld
这个命名空间下了。
现在打开 grpc-node/examples/protos/helloworld.proto
文件:
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
目前,我们只要知道服务器和客户端“stub”都定义了一个 SayHello
的 RPC 方法,这个方法从客户端接收一个 HelloRequest
类型的参数,并从服务器返回一个 HelloReply
类型的响应。
我们更新一下,给 Greeter
服务增加一个新的 SayHelloAgain
方法,请求和响应类型与之前相同:
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
保存文件!
更新并运行程序
我们加了一个新的服务定义,但是没有具体实现,这块实现代码需要我们人工编写。打开 greeter_server.js
文件,在 main()
方法内部增加 sayHelloAgain
方法实现:
function sayHello(call, callback) {
callback(null, {message: 'Hello ' + call.request.name});
}
function sayHelloAgain(call, callback) {
callback(null, {message: 'Hello again, ' + call.request.name});
}
function main() {
var server = new grpc.Server();
server.addService(hello_proto.Greeter.service,
{sayHello: sayHello, sayHelloAgain: sayHelloAgain});
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
server.start();
});
}
我们照葫芦(sayHello
)画瓢(sayHelloAgain
),完成 sayHelloAgain
服务方法的代码实现。
更新客户端
在同一个目录中,打开 greeter_client.js
文件,像这样调用新的方法:
function main() {
var client = new hello_proto.Greeter('localhost:50051',
grpc.credentials.createInsecure());
client.sayHello({name: 'you'}, function(err, response) {
console.log('Greeting:', response.message);
});
client.sayHelloAgain({name: 'you'}, function(err, response) {
console.log('Greeting:', response.message);
});
}
在客户端,我们增加了调用 sayHelloAgain
服务的代码,然后打印响应数据。
运行!
再一次运行我们的项目代码:
# 启动服务
$ node greeter_server.js
# 运行客户端
$ node greeter_client.js
Greeting: Hello world
Greeting: Hello again,world
多了一个 “Greeting: Hello again,world”输出,我们的修改完成!
增加拦截器
gRPC 还支持客户端拦截器的添加。在客户端调用服务时,可以在携带参数之后,跟上选项参数(options),其中支持通过 interceptors 参数,传入一个由拦截器函数组成的一个数组。
client.sayHello({ name: 'world' }, { interceptors: [logger] }, (err, response) => {
console.log('Greeting:', response.message)
})
logger
拦截器的定义如下:
const logger = function (options, nextCall) {
console.log("[interceptor]", { options, nextCall });
return new grpc.InterceptingCall(nextCall(options), {
start: function (metadata, listener, next) {
// 1) 执行一些前置任务
console.log("start", metadata);
// 调用下一步
next(metadata, {
onReceiveMessage: function (message, next) {
// 3) 在请求过程中拦截消息
console.log("onReceiveMessage", message);
// 调用下一步
next(message);
},
onReceiveStatus: function (status, next) {
// 4) 在请求处理完成后拦截响应状态
console.log("onReceiveStatus", status);
// 调用下一步
next(status);
},
});
},
sendMessage: function (message, next) {
// 2) 发送消息前的处理
console.log("sendMessage", message);
// 调用下一步
next(message);
},
halfClose: function (next) {
next()
},
cancel: function (message, next) {
next(message)
}
});
};
拦截器内部提供了 start
、sendMessage
、halfClose
、ca
这些涉及请求周期范围内的钩子函数。另外 start
钩子函数内部,还提供 onReceiveMessage
、onReceiveStatus
钩子函数,针对响应数据的过程进行干预。在这些钩子函数中,通过 next()
函数将请求传递给下一个拦截器处理
进一步了解
如果想要进一步了解项目文件代码的含义,可以参考官方《Basics tutorial》[7]教程。
参考资料
gRPC on Node.js: https://github.com/grpc/grpc-node
[2]
《Supported languages》: https://grpc.io/docs/languages/
[3]
Protocol Buffers: https://protobuf.dev/overview/
[4]
第 3 版居多,又称“proto 3”: https://protobuf.dev/programming-guides/proto3
[5]
官方 Node 端教程: https://grpc.io/docs/languages/node/
[6]
仓库代码(grpc/grpc-node): https://github.com/grpc/grpc-node
[7]
《Basics tutorial》: https://grpc.io/docs/languages/node/basics/
原文始发于微信公众号(写代码的宝哥):gRPC:由 Google 开发的一个高性能、开源和通用性强的 RPC 框架
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/243944.html