忽略底层细节,从使用RPC的角度,最主要的就是要定义一个方法签名。这个方法,由服务端去实现,由客户端去调用。
因此我们关心一下几方面:
①方法的参数:决定了客户端要请求的数据;
②方法的返回值:决定了服务端要返回的数据;
③方法的名称:决定了RPC要实现什么功能;
从这个角度去看GRPC的文档,发现他的方法定义不太好理解。
我们先看一下官网给的案例:
syntax = "proto3";
service RouteGuide {
// A simple RPC.
rpc GetFeature(Point) returns (Feature) {}
// A server-to-client streaming RPC.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
// A client-to-server streaming RPC.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
// A Bidirectional streaming RPC.
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}
以上定义中
关键字rpc代表这是一个服务的定义
另外一个关键字 stream则是GRPC的重要特性。我们从客户端和服务端通信的角度去理解它
最简单的情况下:
这种情况很简单,但是如果客户端或者服务端之间的交互很频繁时,每次方法调用都要建立连接,所以开销比较大,GRPC考虑了这一点,所以需要再定义客户端和服务端是否为Stream。这四种定义方式带来的影响,我们通过GRPC生成的代码来分析。
public static abstract class RouteGuideIMplBase {
public void getFeature(Point request,StreamObserver responseObserver) {}
public void listFeatures(Rectangle request,StreamObserver responseObserver){}
public StreamObserver recordRoute(StreamObserverresponseObserver){}
public treamObserver routeChat(StreamObserver responseObserver){}
}
这是需要我们继承并实现的一个抽象类。从这四个方法签名去看,发现虽然服务定义有四种类型,但是生成的代码只有两种签名。
不管是否定义了Server-Stream,GRPC框架都为我们提供了一个responseObserver, 这是一个Stream
至于Client-Stream,才是对方法签名产生影响的唯一因素:
未定义为Client-Stream: 这就是开头说的最简单的行式,客户端将请求数据一次性地发送给服务端,服务端则直接处理完数据,然后通过responseObserver返回处理结果即可。
定义了Client-Stream: 这种情况下,服务端无法知道请求数据何时到达,也不知道请求数据何时结束。因此GRPC框架要求我们自己实现一个处理请求流数据的Observer对象,即方法返回的StreamObserver
生成的客户端代码相对来说复杂一些,因为GRPC为客户端生成了三种存根:
public static RouteGuideStub newStub(Channel channel) {
return new RouteGuideStub(channel);
}
public static RouteGuideBlockingStub newBlockingStub(Channel channel) {
return new RouteGuideBlockingStub(channel);
}
public static RouteGuideFutureStub newFutureStub(Channel channel) {
return new RouteGuideFutureStub(channel);
}
这三种存根从名字也大概能猜出他们的功能
1.Stub:提供完全异步的方法
public static final class RouteGuideStub {
public void getFeature(Point request,StreamObserver responseObserver){}
public void listFeatures(Rectangle request,StreamObserver responseObserver){}
public StreamObserver recordRoute(StreamObserver responseObserver){}
public StreamObserver routeChat(StreamObserver responseObserver){}
}
客户端要使用Stub,必须要先实现自己的responseObserver,用来处理服务端返回的消息。
前面也说了,服务端的实现里面,响应数据都是以Stream的行式返回的,因此这部分很好理解。
区别仍然是Client-Stream部分。
1.未定义Client-Stream,则Stub直接将客户端准备好的数据放到前两个方法的参数中,然后调用方法
2.定义了Client-Stream,这说明Client无法一次性准备好请求数据,因此stub的后两个方法调用后,GRPC框架会返回一个Stream Observer
2.BlockStub :提供完全同步的方法
public static final class RouteGuideBlockingStub{
public Feature getFeature(Point request){……}
public Iterator listFeatures(Rectangle request){……}
}
可以看到,BlockStub只实现了未定义Client-Stream的服务,这也很好理解,既然是同步方法,那就要求一次调用就结束。
唯一需要说明的是,同步方法也实现了Server-Stream,其实BlockStub是等服务端把数据都返回后,再一次性处理的,如果服务端一直没有完成响应数据的返回,那么listFeatures()方法会一直阻塞等待。
3.FutrueStub
public static final class RouteGuideFutureStub{
public ListenableFuture getFeature(Point request) {}
}
可以看到,FutureStub只实现了一个方法,它其实就是解决了BlockStub在处理Server-Stream的方法时阻塞的问题