背景介绍:
gRPC 是由google提供的rpc开发框架,什么是RPC呢(remote procedure call), rc--远程调用,日常调用web服务器的api就是远程调用的一种,假如p理解为函数,就是说像调用本地函数一样调用远程的函数,rpc框架就是提供一套调用方(client)与被调用方(server)的工具设施和代码库用以方便的达到rpc目标。
本文一方面用简单的例子(在官方实例上增删)来阐述grpc的使用,并辅以 protobuffer 的实例,另一方面有别于传统的入门文将调用方和被调用方使用同样的语言和环境,本例调用方本机使用python,服务端使用node并部署于linux虚拟机。这样期望将整个流程描述的更清晰些。
本文读者对象:初中级 web后端开放人员,期望了解protocol buffer、rpc,grpc使用或感兴趣的同学
环境准备
调用方(client),windows 10,安装标准Python , pb工具和 grpc框架.python -m pip install grpcio
python -m pip install grpcio_tools
文档上都写着grpc_tools 而包名却是 grpcio_tools 凭增一段小曲折
被调用方(server),debian8.9, 安装node ,pb工具和 grpc框架
debian上参考官网的,直接从git下载,除了包含库外还在 examples 目录下有实例.apt-get install -y git
git clone -b v1.25.0 https://github.com/grpc/grpc
定义数据和过程
既然涉及跨服务器调用,那就需要定义数据传输和过程定义格式,我们都知道,使用xml 或者json 等格式都可以达到此目的,既然都grpc了,那就绕不开同时google出品的 pb格式, "Protocol buffers are a flexible, efficient, automated mechanism for serializing structured data – think XML, but smaller, faster, and simpler." 需要了解详情的可参考 protocol官方文档
syntax = 'proto3' ;
option java_package = 'io.grpc.examples';
package helloworld;
service Greeter{
rpc SayHello(HelloRequest) returns (HelloReply){}
rpc SendIm(ImMsg) returns (ImRet){}
}
message HelloRequest{
string name = 1;
}
message HelloReply{
string message = 1;
}
message ImMsg{
int32 fromuser = 1;
int32 touser = 2;
string content = 3;
string faces = 16;
string imgs = 17;
}
message ImRet{
int32 retcode = 1;
string retmsg = 2;
string extdata = 16;
string errmsg = 17;
}
以上就是我们定义的一个简单的 helloworld.proto 文件,它定义了两个过程(函数),一个是官方的SayHello,一个是本例新增的发送 IM消息SendIM,随之定义了两个消息结构,im发送消息 和 发送消息后的服务端返回结构 ImRet
工具链使用
client python 端:python -m grpc_tools.protoc -I ./protos --python_out=. --grpc_python_out=. ./protos/helloworld.proto
以上命令注意文件得目录路径。执行后会在本目录生成两个文件,分别是pb和grpc的代码文件
server nodejs 端:
nodejs 使用grpc框架有两种方式,静态和动态引入,所谓静态就是像上面一样提前生成 pb的文件和gprc的文件,动态就是直接在代码中读取.proto 文件,由框架在读入后自动编译,我偷懒使用静态载入可跳过下面。npm install -g grpc-tools
安装可能会失败,错误如下:
权限不足,上网搜找到 npm安装权限问题,
补充命令参数 npm install --unsafe-perm grpc_tools
然后
grpc_tools_node_protoc --js_out=import_style=commonjs,binary:../node/static_codegen/ --grpc_out=../node/static_codegen --plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` helloworld.proto
grpc_tools_node_protoc --js_out=import_style=commonjs,binary:../node/static_codegen/route_guide/ --grpc_out=../node/static_codegen/route_guide/ --plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` route_guide.proto
核心代码
本例所用代码皆由官方示例编辑,为了减小文章长度和无用代码量,特意去掉了收尾和注释,如侵权或造成读者不适本人深表歉意
client端
import grpc
import helloworld_pb2_grpc
import helloworld_pb2
def run():
channel = grpc.insecure_channel('192.168.220.129:50051')
stub = helloworld_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(helloworld_pb2.HelloRequest(name='you'))
print("Greeter client received: " + response.message)
response = stub.SendIm(helloworld_pb2.ImMsg(fromuser=2059, touser=3088, content='转账收到了吗?[#F]', faces='卖萌表情'))
print("sendmsg response data:\n", response)
run()
注意生成的两个py文件和本代码文件放在同级目录. 成功运行结果:
server端
const PROTO_PATH = __dirname + '/../../protos/helloworld.proto';
const grpc = require('grpc');
const protoLoader = require('@grpc/proto-loader');
let packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
let hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
/**
* Implements the SayHello RPC method.
*/
function sayHello(call, callback) {
callback(null, {message: 'Hello ' + call.request.name});
}
/**
add by lwy for demo
*/
function sendIm(call,callback){
console.log(call.request);
callback(null,{retcode:10100,retmsg:"send suc"});
}
function main() {
var server = new grpc.Server();
//server.addService(hello_proto.Greeter.service, {sayHello: sayHello});
server.addService(hello_proto.Greeter.service, {sayHello: sayHello,sendIm: sendIm});
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
server.start();
}
main();
劝退了吗
整个过程是清晰的,但是完整执行起来还是容易磕碰掉坑。
当年我们使用vs开发webservice 时,服务端写好之后,直接在调用方右键添加引用(类比引入包),客户端就将所有的代码和数据生成好了,直接像本地调用函数即可。ms就是这样贴心。 而grpc我理解为分了三步(三层),定义数据和过程,运用工具链生成,编写代码。
这样减少了开发协作流畅度,但是程序效率非常高,一方面文本的pb格式在两端都会被编译后再使用,这让数据的序列化和反序列化效率非常高,另一方面它采用自定义二进制编码,数据占据空间小保证高频网络调用时节省大量数据传输。如果要达到深入的使用, protocal buffer的深入学习必不可少,光它的指南就是一本小册子,还有 grpc本身框架的学医成本,so, 增加了不小时间成本
综上,如果你的项目需要高频的远程调用(1s至少百级别以上),并且传输的数据相对丰富,那么grpc栈是一个非常高效的可选方案, 不然的话,使用普通的微服务框架或常规http restful服务会让开发使用更便捷。