一、使用GRPC需要考慮的問題
忽略底層細節,從使用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生成的代碼來分析。
1.1服務端代碼分析
public static abstract class RouteGuideIMplBase {
public void getFeature(Point request,StreamObserver<Feature> responseObserver) {}
public void listFeatures(Rectangle request,StreamObserver<Feature> responseObserver){}
public StreamObserver<Point> recordRoute(StreamObserver<RouteSummary>responseObserver){}
public treamObserver<RouteNote> routeChat(StreamObserver<RouteNote> responseObserver){}
}
這是需要我們繼承並實現的一個抽象類。從這四個方法簽名去看,發現雖然服務定義有四種類型,但是生成的代碼只有兩種簽名。
不管是否定義了Server-Stream,GRPC框架都爲我們提供了一個responseObserver, 這是一個Stream<ResponseType>,服務端用它來返回相應數據。
至於Client-Stream,纔是對方法簽名產生影響的唯一因素:
未定義爲Client-Stream: 這就是開頭說的最簡單的行式,客戶端將請求數據一次性地發送給服務端,服務端則直接處理完數據,然後通過responseObserver返回處理結果即可。
定義了Client-Stream: 這種情況下,服務端無法知道請求數據何時到達,也不知道請求數據何時結束。因此GRPC框架要求我們自己實現一個處理請求流數據的Observer對象,即方法返回的StreamObserver<RequestType>類型對象。 返回的整個Observer對象會交給GRPC框架去調用。
1.2 客戶端代碼分析
生成的客戶端代碼相對來說複雜一些,因爲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<Feature> responseObserver){}
public void listFeatures(Rectangle request,StreamObserver<Feature> responseObserver){}
public StreamObserver<Point> recordRoute(StreamObserver<RouteSummary> responseObserver){}
public StreamObserver<RouteNote> routeChat(StreamObserver<RouteNote> responseObserver){}
}
客戶端要使用Stub,必須要先實現自己的responseObserver,用來處理服務端返回的消息。
前面也說了,服務端的實現裏面,響應數據都是以Stream的行式返回的,因此這部分很好理解。
區別仍然是Client-Stream部分。
1.未定義Client-Stream,則Stub直接將客戶端準備好的數據放到前兩個方法的參數中,然後調用方法
2.定義了Client-Stream,這說明Client無法一次性準備好請求數據,因此stub的後兩個方法調用後,GRPC框架會返回一個Stream Observer<RequestType>,這個對象供客戶端隨時調用,發送Stream行式的數據。
2.BlockStub :提供完全同步的方法
public static final class RouteGuideBlockingStub{
public Feature getFeature(Point request){……}
public Iterator<Feature> listFeatures(Rectangle request){……}
}
可以看到,BlockStub只實現了未定義Client-Stream的服務,這也很好理解,既然是同步方法,那就要求一次調用就結束。
唯一需要說明的是,同步方法也實現了Server-Stream,其實BlockStub是等服務端把數據都返回後,再一次性處理的,如果服務端一直沒有完成響應數據的返回,那麼listFeatures()方法會一直阻塞等待。
3.FutrueStub
public static final class RouteGuideFutureStub{
public ListenableFuture<Feature> getFeature(Point request) {}
}
可以看到,FutureStub只實現了一個方法,它其實就是解決了BlockStub在處理Server-Stream的方法時阻塞的問題