GRPC是基于protocol buffers3.0協(xié)議的.
本文將向您介紹gRPC和protocol buffers。 gRPC可以使用protocol buffers作為其IDL(接口描述)和其底層消息交換格式。 如果您是gRPC和protocol buffers的新手,請(qǐng)繼續(xù)閱讀! 如果您只是想首先看到gRPC,請(qǐng)參閱我們的快速入門。
指南
在gRPC中,客戶端應(yīng)用程序可以直接在不同機(jī)器上的服務(wù)器應(yīng)用程序上調(diào)用方法,就像它是本地對(duì)象一樣,使您更容易創(chuàng)建分布式應(yīng)用程序和服務(wù)。 與許多RPC系統(tǒng)一樣,gRPC基于定義服務(wù)的思想,指定可以使用其參數(shù)和返回類型遠(yuǎn)程調(diào)用的方法。 在服務(wù)器端,服務(wù)器實(shí)現(xiàn)此接口并運(yùn)行g(shù)RPC服務(wù)器來(lái)處理客戶端調(diào)用。 在客戶端,客戶端有一個(gè)stub(簡(jiǎn)稱為一些語(yǔ)言的客戶端),提供與服務(wù)器相同的方法。
gRPC客戶端和服務(wù)器可以在各種環(huán)境中運(yùn)行和交互,從Google內(nèi)部的服務(wù)器到您自己的應(yīng)用,并且可以使用任何gRPC支持的語(yǔ)言編寫。 所以,例如,您可以輕松地創(chuàng)建一個(gè)Java開發(fā)的服務(wù),使用Go,Python或Ruby中的客戶端。In addition, the latest Google APIs will have gRPC versions of their interfaces, letting you easily build Google functionality(功能) into your applications.
Protocol buffer 版本
雖然Protocol buffer已經(jīng)可用于開源用戶一段時(shí)間,但我們的示例使用了proto3的Protocol buffer的新風(fēng)格,它具有略微簡(jiǎn)化的語(yǔ)法,一些有用的新功能,并支持更多的語(yǔ)言。這是目前可用于Java,C ++,Python,Objective-C,C#,lite-runtime(Android Java),Ruby和JavaScript的Protocol buffer Github repo,以及來(lái)自golang/protobuf Github的Go語(yǔ)言自動(dòng)生成,還有更多的語(yǔ)言在開發(fā)中。您可以在proto3語(yǔ)言指南和每種支持的語(yǔ)言的參考文檔(如果可用)查看更多內(nèi)容,并且可以在發(fā)行說(shuō)明中查看與當(dāng)前默認(rèn)版本的主要區(qū)別。更多的proto3文檔即將推出。
一般來(lái)說(shuō),雖然您可以使用proto2(當(dāng)前的默認(rèn)Protocol buffer版本),但我們建議您使用proto3協(xié)議為基礎(chǔ)的gRPC,因?yàn)樗梢宰屇褂萌盗械膅RPC支持的語(yǔ)言,并避免與proto2客戶端與proto3服務(wù)器的兼容性問(wèn)題,反之亦然。
注:因?yàn)閜roto2只提供了消息定義,而沒有提供服務(wù)接口的定義。
服務(wù)定義
像許多RPC系統(tǒng)一樣,gRPC基于定義服務(wù)的思想,指定可以使用其參數(shù)和返回類型遠(yuǎn)程調(diào)用的方法。 默認(rèn)情況下,gRPC使用 protocol buffers作為接口定義語(yǔ)言(IDL),用于描述有效負(fù)載消息的服務(wù)接口和結(jié)構(gòu)。
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
pf3.0出現(xiàn)了接口定義。
gRPC 定義了四種類型的服務(wù)接口:
- 一元RPC,客戶端向服務(wù)器發(fā)送請(qǐng)求并獲得響應(yīng),就像正常的函數(shù)調(diào)用一樣。
rpc SayHello(HelloRequest) returns (HelloResponse){
}
- 服務(wù)器流RPC,客戶端發(fā)送一個(gè)對(duì)象服務(wù)器端返回一個(gè)Stream(流式消息)
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}
- 客戶端流式RPC,客戶端發(fā)送一個(gè)Stream(流式消息)服務(wù)端返回一個(gè)對(duì)象。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
- 雙向流RPC
其中雙方使用讀寫流發(fā)送消息序列。 兩個(gè)流獨(dú)立運(yùn)行,所以客戶端和服務(wù)器可以按照他們喜歡的順序進(jìn)行讀取和寫入:例如,服務(wù)器可能在寫入響應(yīng)之前等待接收所有客戶端消息,或者可以交替地讀取消息然后寫入消息, 或讀取和寫入的其他組合。 每個(gè)流中消息的順序被保留。
類似于WebSocket(長(zhǎng)連接),客戶端可以向服務(wù)端請(qǐng)求消息,服務(wù)器端也可以向客戶端請(qǐng)求消息)。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}
使用api
從.proto文件中的服務(wù)定義開始,gRPC提供了生成客戶機(jī)和服務(wù)器端代碼的protocol buffer編譯器插件。 gRPC用戶通常在客戶端調(diào)用這些API,并在服務(wù)器端實(shí)現(xiàn)相應(yīng)的API。
- 在服務(wù)器端,服務(wù)器實(shí)現(xiàn)服務(wù)聲明的方法,并運(yùn)行g(shù)RPC服務(wù)器來(lái)處理客戶端調(diào)用。 gRPC基礎(chǔ)設(shè)施解碼傳入請(qǐng)求,執(zhí)行服務(wù)方法和編碼服務(wù)響應(yīng)。
- 在客戶端,客戶端具有稱為stub的本地方法定義(對(duì)于一些語(yǔ)言,首選項(xiàng)是客戶端),其實(shí)現(xiàn)與服務(wù)相同的方法。 然后,客戶端可以直接在本地對(duì)象上調(diào)用這些方法,將調(diào)用的參數(shù)包含在適當(dāng)?shù)膒rotocol buffer消息類型中 - gRPC在將請(qǐng)求發(fā)送到服務(wù)器并返回服務(wù)器的protocol buffer響應(yīng)之后。
同步和異步
同步RPC調(diào)用阻塞,直到響應(yīng)從服務(wù)器返回是與RPC渴望的過(guò)程調(diào)用最接近(差不多這個(gè)意思)。 另一方面,網(wǎng)絡(luò)本質(zhì)上是異步的,并且在許多情況下,能夠在不阻塞當(dāng)前線程的情況下啟動(dòng)RPC是有用的。
大多數(shù)語(yǔ)言中的gRPC編程表面都包含同步和異步語(yǔ)言。 您可以在每種語(yǔ)言的教程和參考文檔中找到更多內(nèi)容(完整的參考文檔即將推出)。
RPC生命周期
現(xiàn)在讓我們仔細(xì)看看當(dāng)gRPC客戶端調(diào)用gRPC服務(wù)器方法時(shí)會(huì)發(fā)生什么。 我們不會(huì)查看實(shí)現(xiàn)細(xì)節(jié),可以在特定語(yǔ)言頁(yè)面中了解更多信息。
一元RPC
客戶端向服務(wù)器發(fā)送單個(gè)請(qǐng)求并獲得單個(gè)響應(yīng),就像正常的函數(shù)調(diào)用一樣。
一旦客戶端調(diào)用stub/客戶端對(duì)象上的方法,就會(huì)通知服務(wù)器RPC客戶端的元數(shù)據(jù)用于此調(diào)用,方法名稱和指定的期限(如果配置的話)
然后,服務(wù)器可以立即發(fā)回自己的初始元數(shù)據(jù)(必須在任何響應(yīng)之前發(fā)送),或等待客戶端的請(qǐng)求消息 - 由應(yīng)用程序決定誰(shuí)先執(zhí)行。
一旦服務(wù)器有客戶端的請(qǐng)求消息,它就做任何創(chuàng)建和填充其響應(yīng)所需的工作。 然后將響應(yīng)(如果成功)返回給客戶端以及狀態(tài)詳細(xì)信息(狀態(tài)代碼和可選狀態(tài)消息)以及可選的尾隨元數(shù)據(jù)。
- 狀態(tài)是ok的,客戶端得到響應(yīng),客戶端完成所有的調(diào)用。
服務(wù)器流RPC
服務(wù)器流RPC與我們的簡(jiǎn)單示例類似,除了服務(wù)器在獲取客戶端請(qǐng)求消息之后發(fā)送回響應(yīng)流。 在發(fā)回所有響應(yīng)后,服務(wù)器端的狀態(tài)信息(狀態(tài)碼和可選狀態(tài)消息)和可選的尾隨元數(shù)據(jù)將被發(fā)送回完成。 客戶端完成直到接收到服務(wù)器的所有響應(yīng)。
客戶端流式RPC
客戶端流RPC也類似于我們的簡(jiǎn)單示例,除了客戶端將請(qǐng)求流發(fā)送到服務(wù)器,而不是單個(gè)請(qǐng)求。 服務(wù)器返回單個(gè)響應(yīng),通常但不一定在收到所有客戶端的請(qǐng)求后返回響應(yīng),以及其狀態(tài)詳細(xì)信息和可選的尾隨元數(shù)據(jù)。
雙向流RPC
在雙向流RPC中,調(diào)用發(fā)起與客戶端調(diào)用方法然后服務(wù)器端接收客戶端的元數(shù)據(jù),方法名稱,和調(diào)用期限。 服務(wù)器也可以選擇發(fā)回其初始元數(shù)據(jù)或等待客戶端開始發(fā)送請(qǐng)求。
接下來(lái)發(fā)生的情況取決于應(yīng)用程序,因?yàn)榭蛻舳撕头?wù)器可以以任何順序讀取和寫入 - 流完全獨(dú)立運(yùn)行。 因此,例如,服務(wù)器可能會(huì)等到收到所有客戶端的消息后再寫入響應(yīng),否則服務(wù)器和客戶端可能會(huì)“ping-pong”:服務(wù)器獲取請(qǐng)求,然后發(fā)送回應(yīng),然后客戶端發(fā)送基于響應(yīng)的另一個(gè)請(qǐng)求等等。
截止日期和超時(shí)
gRPC允許客戶端指定在RPC服務(wù)調(diào)用終止之前愿意等待RPC完成的時(shí)間,否則顯示錯(cuò)誤DEADLINE_EXCEEDED。 在服務(wù)器端,服務(wù)器可以查詢特定的RPC是否超時(shí),還是剩下多少時(shí)間來(lái)完成RPC調(diào)用。
如何指定期限或超時(shí)時(shí)間因語(yǔ)言而異 - 例如,并非所有語(yǔ)言都有默認(rèn)的最后期限,某些語(yǔ)言API的工作時(shí)間是截止時(shí)間(固定時(shí)間點(diǎn)),而某些語(yǔ)言API的工作范圍是超時(shí) (一段時(shí)間內(nèi))。
RPC 終止
在gRPC中,客戶端和服務(wù)器都相對(duì)獨(dú)立和本地確定本地調(diào)用成功,并且其結(jié)論可能不匹配另一端。 這意味著,例如,您可以在服務(wù)器端成功完成一個(gè)RPC返回(“我已發(fā)送我的所有回復(fù)”),但在客戶端失敗(“我的截止日期之后回復(fù)”)。 服務(wù)器也可以在客戶端發(fā)送所有請(qǐng)求之前決定完成。
取消RPC調(diào)用
客戶端或服務(wù)器可以隨時(shí)取消RPC。取消立即被終止以便不再進(jìn)行任何工作。它不是一個(gè)“撤消”:在取消之前的修改不會(huì)被回滾。
元數(shù)據(jù)
元數(shù)據(jù)是關(guān)于鍵值對(duì)列表形式的特定RPC調(diào)用(例如認(rèn)證細(xì)節(jié))的信息,其中鍵是字符串,并且值通常是字符串(但可以是二進(jìn)制數(shù)據(jù))。 元數(shù)據(jù)對(duì)gRPC本身是不透明的 - 它允許客戶端提供與對(duì)服務(wù)器的調(diào)用相關(guān)聯(lián)的信息,反之亦然。
對(duì)元數(shù)據(jù)的訪問(wèn)取決于依賴的語(yǔ)言。
通道
gRPC通道提供與指定主機(jī)和端口上的gRPC服務(wù)器的連接,并在創(chuàng)建客戶端stub(或僅某些語(yǔ)言的“客戶端”)時(shí)使用。 客戶端可以指定通道參數(shù)來(lái)修改gRPC的默認(rèn)行為,例如打開和關(guān)閉消息壓縮。 通道有狀態(tài),包括連接和空閑。 > gRPC如何處理關(guān)閉頻道與語(yǔ)言有關(guān)。 某些語(yǔ)言也允許查詢通道狀態(tài)。
快速入門
下載example
? # Clone the repository at the latest release to get the example code:
? git clone -b v1.4.0 https://github.com/grpc/grpc-java
? # Navigate to the Java examples:
? cd grpc-java/examples
-b v1.4.0
表示指定的是1.4.0
這個(gè)版本,不指定版本默認(rèn)的是master
版本。
運(yùn)行一個(gè)grpc的應(yīng)用
編譯服務(wù)器端和客戶端,這一步需要很長(zhǎng)時(shí)間,
? ./gradlew installDist
運(yùn)行服務(wù)器:
? ./build/install/examples/bin/hello-world-server
六月 25, 2017 6:58:58 下午 io.grpc.examples.helloworld.HelloWorldServer start
信息: Server started, listening on 50051
運(yùn)行客戶端:
?./build/install/examples/bin/hello-world-client
六月 25, 2017 6:59:08 下午 io.grpc.examples.helloworld.HelloWorldClient greet
信息: Will try to greet world ...
六月 25, 2017 6:59:09 下午 io.grpc.examples.helloworld.HelloWorldClient greet
信息: Greeting: Hello world
修改gRPC服務(wù)
進(jìn)入src/main/proto/
目錄,修改helloworld.proto
文件,增加一個(gè)SayHelloAgain
方法,
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
helloworld.proto
文件中增加了一個(gè)SayHelloAgain
方法,入?yún)⑹?code>HelloRequest,返回參數(shù)是HelloReply
。
修改服務(wù)器代碼,增加SayHelloAgain
方法的實(shí)現(xiàn)
重新編譯.prto文件,將會(huì)重新生成客戶端,服務(wù)端代碼,也重新生成序列化,我們?cè)诜?wù)端去增加剛才helloworld.proto
文件中增加了一個(gè)SayHelloAgain
方法的實(shí)現(xiàn):
在src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java
文件增加sayHelloAgain
方法實(shí)現(xiàn):
private class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
@Override
public void sayHelloAgain(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello again " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
...
修改客戶端
修改src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java
文件,增加blockingStub.sayHelloAgain
的調(diào)用。
public void greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
try {
response = blockingStub.sayHelloAgain(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
}
編譯文件,重新執(zhí)行
? ./gradlew installDist
運(yùn)行服務(wù)器:
? ./build/install/examples/bin/hello-world-server
運(yùn)行客戶端:
? ./build/install/examples/bin/hello-world-client
客戶端打印:
? ./build/install/examples/bin/hello-world-client
六月 25, 2017 7:43:29 下午 io.grpc.examples.helloworld.HelloWorldClient greet
信息: Will try to greet world ...
六月 25, 2017 7:43:29 下午 io.grpc.examples.helloworld.HelloWorldClient greet
信息: Greeting: Hello world
六月 25, 2017 7:43:30 下午 io.grpc.examples.helloworld.HelloWorldClient greet
信息: Greeting: Hello again world
總結(jié)
運(yùn)行一個(gè)簡(jiǎn)單的gRPC列子,首先你需要學(xué)會(huì)下面三個(gè)步驟:
- 使用一個(gè).proto文件定義服務(wù)(定義了消息體和服務(wù)接口)
- 使用protocol buffer編譯器去編譯.proto文件并且去生成客戶端和服務(wù)端代碼(不能使用protoc指令,因?yàn)間RPC是使用的基于protocol buffer3.0的,maven和gradle都提供了對(duì)應(yīng)的編譯器,而protoc 指令是protocol buffer2.0的指令)。
- 編寫自己的客戶端和服務(wù)端(服務(wù)器端額外編寫接口實(shí)現(xiàn))。
發(fā)現(xiàn)所有的RPC框架的基礎(chǔ)流程都是如此。
參考資料
官網(wǎng)地址
官網(wǎng)java快速指南
github地址
protocol buffer3.0協(xié)議