目的
了解什么是gRPC并掌握其客户端的编写
什么是RPC?
RPC,远程过程调用,是分布式系统常见的一种通信方法。
原因
在系统还是单体应用时,服务被封装在本地方法中进行调用,屏蔽实现细节(调用方和服务方的一套约定)。但随着系统越来越复杂,考虑到高性能和高可靠等因素,需要将系统改造为分布式应用,将服务拆分,通过一个服务去调用另一个服务。
Rpc解决的问题:
– 分布式系统中服务之间的调用问题
– 像本地调用一样方便
Rpc调用实际也是一个很复杂的过程,考虑到性能和兼容等,内容传输是一大难题,而如今针对协议约定和网络传输有几个的解决方案,其中gRPC能较好的解决性能和版本兼容的问题。
什么是gRPC?
gRPC是一款结合http2.0和protobuf的RPC框架,它使用protobuf作为接口定义。
什么是protobuf?
全称Protocol Buffers,是由google开发的一套开源序列化协议框架,该框架不依赖开发语言,也不依赖运行平台,扩展性好。类似于XML,JSON,用于数据存储、通讯协议,其序列化结果为二进制数据,它的体积相比XML和JSON却小很多,快很多。
序列化:将数据结构或者对象的状态转换成可以存储或者传输的格式
编写一个proto文件
syntax = "proto3"; // 指定协议类型,否则默认为proto2
package hello; // 定义proto的包名
service Greeter { // 定义服务为Greeter
// 定义一个sayHello的rpc
rpc sayHello (SayHelloRequest) returns (SayHelloResponse);
}
message SayHelloRequest { // 定义请求的消息类型,还可以通过enum定义枚举类型
string message = 1;
}
message SayHelloResponse { // 定义接受的消息类型
string message = 1;
}
编写gRPC通信demo(nodeJS)
步骤1: 编写proto文件(同上代码),另外再加了一个test.proto,统一放在proto目录下
syntax = "proto3";
package test;
service ToTest {
rpc saySomething (AnyRequest) returns (AnyResponse);
}
message AnyRequest {
string name = 1;
}
message AnyResponse {
string message = 1;
}
步骤2:抽离proto的方法
不管客户端还是服务端,proto 文件都需要 proto-loader 进行加载进来,可以单独抽离为一个方法
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
// 传入proto文件路径和包名
export const loadProto = (protoPath, packageName) => {
const packageDefinition = protoLoader.loadSync(protoPath, {
keepCase: true, // 保持原有命名规范,否则默认转驼峰
longs: String, // 用字符串表示长值,默认是对象
enums: String, // 用字符串表示枚举值,默认是数字
defaults: true, // 输出对象有默认值
oneofs: true,
});
return grpc.loadPackageDefinition(packageDefinition)[packageName]; // 导出对应包名的proto
};
步骤3: 编写客户端代码
为了对接口定义校验,可以将proto编译为typescript文件,统一输出到proto-ts目录下
# 安装ts-proto包
npm i ts-proto -D
# 编写脚本
# 输出为只包含type的文件
protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_opt=outputEncodeMethods=false,outputJsonMethods=false,outputClientImpl=false --ts_proto_out=./proto-ts ./proto/*.proto -I./proto
import * as grpc from "@grpc/grpc-js";
import { loadProto } from "./utils/load-proto";
import { SayHelloRequest } from "./proto-ts/hello";
import { AnyRequest } from "./proto-ts/test";
// 根据package名获取proto
const hello = loadProto(`{__dirname}/proto/hello.proto`, 'hello');
const test = loadProto(`{__dirname}/proto/test.proto`, 'test');
const query1 = {
message: 'Hello',
};
const query2 = {
name: 'jennifer',
};
function grpcClient(service: any, rpcName: string, query: any) {
// 创建客户端
const client = new service('localhost:3001', grpc.credentials.createInsecure());
client[rpcName](query, function(err, res) {
if (err) {
console.log(err);
} else {
console.log(res.message);
}
})
}
// 引入编译为ts的proto文件对接口传参进行校验
function main(){
grpcClient(hello.Greeter, 'sayHello', query1 as Partial<SayHelloRequest>);
grpcClient(test.ToTest, 'saySomething', query2 as Partial<AnyRequest>);
}
main();
步骤4:编写服务端代码
import * as grpc from "@grpc/grpc-js";
import { loadProto } from './utils/load-proto';
// 根据package名获取proto
const hello = loadProto(`{__dirname}/proto/hello.proto`, 'hello');
const test = loadProto(`{__dirname}/proto/test.proto`, 'test');
let name = 'jennifer';
function sayHello(call, callback) {
callback(null, { message: `{name}:{call.request.message}`});
}
function saySomething(call, callback) {
callback(null, { message: `${call.request.name}: who are you?`});
}
function main() {
// 创建一个服务器
const server = new grpc.Server();
server.addService(hello.Greeter.service, { sayHello });
server.addService(test.ToTest.service, { saySomething });
server.bindAsync('0.0.0.0:3001', grpc.ServerCredentials.createInsecure(), () => {
server.start();
console.log('grpc server started');
});
}
main();
步骤5:运行代码
由于通过ts编写的文件,需要
npm i -g ts-node全局安装,运用ts-node运行客户端和服务端
