GRPC
grpc是谷歌的rpc框架,通过一个小小的demo,理解一下其工作原理。构建一个java语言调度python语言组成的服务的这样一个demo,对grpc进行充分理解。
首先对于rpc其实之前有过学习,如下图所示:
服务端中的每个服务包含许多的方法,通过一个对外的IP以及端口对外提供服务,客户端即服务调用者在本地开放多个动态代理,在本地需要调度远程服务的时候,仅仅需要调度动态代理提供的服务,看起来貌似就是本地调用一样。。。
总得来说就是,服务端整合出一个服务模块对外提供服务,然后客户端通过动态代理的方式实现服务调用。一个服务一个动态代理,然后每个服务中可能存在不同的方法,因此服务中的方法通过不同的方法名进行区分。
接下来,分析一下,grpc的实现。
具体的实现参考:
服务以及服务中的方法的参数以及返回值的封装
在grpc中,存在一个.proto文件,运行这个文件会在服务端以及客户端生成一些文件,而这些文件就是对服务、服务中的方法以及方法中的各个参数的封装。
syntax = "proto3";
package com.jjw.grpc;
#服务名称,一个服务中包括多个方法,明确方法的参数类型以及返回值
service MsgService {
rpc GetMsg (MsgRequest) returns (MsgResponse){}
rpc two_add(addRequest) returns(addResponse){}
}
#magRequest是对GetMsg方法的参数的封装,
message MsgRequest {
#参数仅有一个String的name,并且这个参数是第一个位置
string name = 1;
}
message MsgResponse {
string msg = 1;
}
message addRequest{
#存在有两个参数,分别是int类型的a以及int类型的b,并且a是第一个参数,b是第二个参数
int32 a=1;
int32 b=2;
}
message addResponse{
int32 result=1;
}
之后运行这个文件就会生成两个文件,分别是msg_pd2以及msg_pb2_grpc,这两个文件分别封装了服务中各个方法以及方法中各个参数。以及一些通信调度的封装。
服务端
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
#定义一个服务,这个服务就是msg_pb2_MsgServiceServicer这个服务
class MsgServicer(msg_pb2_grpc.MsgServiceServicer):
#实现服务中的一个方法
def GetMsg(self, request, context):
#拿到请求的参数request
print("Received name: %s" % request.name)
#将结果封装到这个方法的返回参数中
return msg_pb2.MsgResponse(msg='Hello, %s!' % request.name)
def two_add(self,request,context):
print("服务端开始计算:"+str(request.a)+"+"+str(request.b)+"....")
a=request.a
b=request.b
return msg_pb2.addResponse(result=a+b)
def serve():
#服务端
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
#实例化,注册本地服务
msg_pb2_grpc.add_MsgServiceServicer_to_server(MsgServicer(), server)
# 监听端口
server.add_insecure_port('127.0.0.1:19999')
server.start()
try:
while True:
print("running....")
time.sleep(_ONE_DAY_IN_SECONDS)
except KeyboardInterrupt:
print("stopping...")
server.stop(0)
if __name__ == '__main__':
serve()
客户端
python客户端:
def run():
# NOTE(gRPC Python Team): .close() is possible on a channel and should be
# used in circumstances in which the with statement does not fit the needs
# of the code.
with grpc.insecure_channel('localhost:19999') as channel:
#指定服务端中的某个服务,stub是生成文件生成的在客户端这里的动态代理的角色
stub = msg_pb2_grpc.MsgServiceStub(channel)
#指定服务的某个方法
response1 = stub.GetMsg(msg_pb2.MsgRequest(name='world'))
response2=stub.two_add(msg_pb2.addRequest(a=1,b=2))
print("Client received: " + response1.msg)
print("结果是:"+str(response2.result))
if __name__ == '__main__':
run()
java客户端也是类似的,通过协议文件生成一些文件,java客户端如下:
public class grpc_client {
private final ManagedChannel channel;
private final MsgServiceGrpc.MsgServiceBlockingStub blockingStub;
public grpc_client(String host,int port) {
this(ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.build());
}
//客户端stub封装以及通信通道
grpc_client(ManagedChannel channel){
this.channel=channel;
blockingStub=MsgServiceGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public void greet(String name){
//将参数封装成定义好的类型
MsgRequest request=MsgRequest.newBuilder().setName(name).build();
System.out.println("发送消息");
//通过封装好的结果从stub中获取结果
MsgResponse response=blockingStub.getMsg(request);
System.out.println(response.getMsg());
}
public int add(int a,int b){
addRequest addRequest= com.jjw.grpc.addRequest.newBuilder().setA(a).setB(b).build();
addResponse response=blockingStub.twoAdd(addRequest);
System.out.println("计算"+a+"+"+b+"的结果是:"+response.getResult());
return response.getResult();
}
public static void main(String[] args) throws InterruptedException {
grpc_client client = new grpc_client("127.0.0.1", 19999);
try{
// client.greet("佳佳文");
client.add(1,2);
}finally {
client.shutdown();
}
}
}
总结
整个上,以上构建的grpc调用模型示意图,如下图所示:
所以整个grpc实际上就是对rpc的封装实现,相关的servicer以及stub 都是用过一个协议文件自动生成的,相关的调用参数以及调用的返回方法也需要进行封装,服务端实现的时候是对外服务的实现并且相关参数是预定义好的类型,而客户端的服务调用也是,需要将请求参数封装成预定义好的类型,然后调用stub中的方法,相关的返回值用之前预定义好的类型接收。
所以整个grpc实际上就是对rpc的封装实现,相关的servicer以及stub 都是用过一个协议文件自动生成的,相关的调用参数以及调用的返回方法也需要进行封装,服务端实现的时候是对外服务的实现并且相关参数是预定义好的类型,而客户端的服务调用也是,需要将请求参数封装成预定义好的类型,然后调用stub中的方法,相关的返回值用之前预定义好的类型接收。