gRPC 是啥
A high performance, open-source universal RPC framework
一款高性能的開(kāi)源 RPC 框架狂票。
gRPC 的特點(diǎn):
- 簡(jiǎn)單的服務(wù)定義:使用 Protocol Buffers(做序列化的工具)來(lái)定義服務(wù)
-
跨語(yǔ)言跨平臺(tái):可以自動(dòng)生成不同語(yǔ)言對(duì)應(yīng)的 Client Stubs 和 Server Stubs候齿。如下圖所示熙暴,服務(wù)端可以用 C++ 來(lái)實(shí)現(xiàn)闺属,但是客戶(hù)端來(lái)說(shuō),可以提供 Ruby 的版本和 Java 的版本周霉。
- 入手簡(jiǎn)單掂器,并且可擴(kuò)展
- 雙向數(shù)據(jù)流
基本思想
參考:https://grpc.io/docs/guides/
- 通過(guò)一種方式來(lái)定義服務(wù) Service 及這個(gè)服務(wù)下面包含的方法,同時(shí)定義這些方法需要的參數(shù)類(lèi)型和返回值類(lèi)型俱箱,這些方法就是遠(yuǎn)程調(diào)用的對(duì)象
- 在服務(wù)提供者這里国瓮,需要實(shí)現(xiàn)上面定義好的接口,運(yùn)行 gRPC Server 來(lái)處理調(diào)用請(qǐng)求
- 在服務(wù)調(diào)用者這里狞谱,通過(guò)使用 Client Stub 來(lái)調(diào)用上面定義好的方法
關(guān)于 Protocol Buffers
什么是 Protocol Buffers乃摹,請(qǐng)參考:https://developers.google.com/protocol-buffers/docs/overview
Protocol buffers are a flexible, efficient, automated mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.
Protocol buffers 是一個(gè)序列化結(jié)構(gòu)化數(shù)據(jù)的方法,類(lèi)似于 XML跟衅,但是比 XML 更節(jié)省空間孵睬,更快,更簡(jiǎn)單伶跷。你首先通過(guò)一種語(yǔ)法定義好你想要的數(shù)據(jù)結(jié)構(gòu)掰读,然后可以編譯成不同語(yǔ)言對(duì)應(yīng)的源代碼(例如 Java 中的實(shí)體類(lèi))秘狞,隨后你就可以通過(guò)這些源代碼來(lái)進(jìn)行數(shù)據(jù)的讀寫(xiě)。
Protocol Buffers 與 XML 的比較:
- are simpler 更簡(jiǎn)單
- are 3 to 10 times smaller 更節(jié)省空間
- are 20 to 100 times faster 更快
- are less ambiguous
- generate data access classes that are easier to use programmatically
gRPC 與 Protocol Buffers
gRPC 默認(rèn)使用 Protocol Buffers 來(lái):
- 作為接口定義語(yǔ)言(Interface Definition Language)來(lái)定義服務(wù) Service
- 定義傳輸過(guò)程中的數(shù)據(jù)結(jié)構(gòu)
我們可以創(chuàng)建一個(gè) .proto
后綴名的文件來(lái)表示一個(gè)結(jié)構(gòu)化數(shù)據(jù)蹈集,例如:
message Person {
string name = 1;
int32 id = 2;
bool has_ponycopter = 3;
}
隨后可以將這個(gè) .proto
文件編譯成不同語(yǔ)言對(duì)應(yīng)的源代碼烁试,例如編譯成 Java 中的 Person.class
類(lèi),這個(gè)類(lèi)里提供了 get
set
方法拢肆。
gRPC 的基本概念
在 gRPC 中可以定義四種類(lèi)型的服務(wù)方法:
- 一元 RPC(Unary RPCs)减响,客戶(hù)端發(fā)送簡(jiǎn)單請(qǐng)求,得到簡(jiǎn)單響應(yīng)郭怪,類(lèi)似于一個(gè)函數(shù)調(diào)用:
rpc SayHello(HelloRequest) returns (HelloResponse){
}
- 服務(wù)端流式 RPC(Server streaming RPCs)辩蛋,客戶(hù)端發(fā)送請(qǐng)求,得到一個(gè)流 Stream移盆,然后從 Stream 讀取內(nèi)容:
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}
- 客戶(hù)端流式 RPC(Client streaming RPCs)悼院,客戶(hù)端通過(guò)流 Stream 來(lái)寫(xiě)入內(nèi)容,然后發(fā)送給服務(wù)端咒循,最后得到響應(yīng):
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
- 雙向流式 RPC(Bidirectional streaming RPCs)据途,上面兩種方式的結(jié)合體:
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}
關(guān)于同步調(diào)用 Vs 異步調(diào)用
- 同步 RPC 會(huì)阻塞客戶(hù)端的當(dāng)前線(xiàn)程,直到獲得了響應(yīng)
- 異步 RPC 不會(huì)阻塞客戶(hù)端的當(dāng)前線(xiàn)程
關(guān)于 gRPC 的超時(shí) Timeout
- 客戶(hù)端可以定超時(shí) Timeout叙甸。
- 服務(wù)端可以知道某一個(gè) RPC Call 是否已超時(shí)颖医。
關(guān)于 gRPC 調(diào)用的中斷 termination
服務(wù)端和客戶(hù)端都可以隨時(shí)中斷調(diào)用。
關(guān)于 gRPC 調(diào)用的取消 cancel
服務(wù)端和客戶(hù)端都可以隨時(shí)取消調(diào)用裆蒸。
Java gRPC 快速入門(mén)
參考:https://grpc.io/docs/quickstart/java.html
gRPC Java API 文檔:https://grpc.io/grpc-java/javadoc/
// 從 Github 上獲取示例代碼
git clone -b v1.18.0 https://github.com/grpc/grpc-java
// 進(jìn)入示例代碼目錄
cd grpc-java/examples
// 編譯服務(wù)端和客戶(hù)端
./gradlew installDist
// 啟動(dòng)服務(wù)端
./build/install/examples/bin/hello-world-server
// 啟動(dòng)客戶(hù)端
./build/install/examples/bin/hello-world-client
我們看看示例中的幾個(gè)重要文件熔萧。
首先是 /examples/src/main/proto/helloworld.proto
,在這里我們通過(guò) protocol buffers 來(lái)定義了 gRPC 所提供的服務(wù)僚祷,也就是 Services佛致。可以看出辙谜,服務(wù)名稱(chēng)為 Greeter
俺榆,它提供了一個(gè) RPC,名稱(chēng)為 SayHello
装哆,其中請(qǐng)求輸入為 HelloRequest
罐脊,包含一個(gè)字符串 name
,請(qǐng)求響應(yīng)為 HelloReply
蜕琴,包含一個(gè)字符串 message
萍桌。
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) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
隨后我們通過(guò) protoc
編譯器來(lái)進(jìn)行編譯,這里使用的是 proto3
版本凌简。protoc
編譯器可以作為插件集成到主流的 Java 構(gòu)建工具中上炎,例如 Gradle 和 Maven。
這個(gè)文件在編譯過(guò)后号醉,會(huì)產(chǎn)生對(duì)應(yīng)的字節(jié)碼文件反症,也就是產(chǎn)生了對(duì)應(yīng)的類(lèi)辛块,位置在 /examples/build/classes/java/main/io/grpc/examples/helloworld
。
java_outer_classname = "HelloWorldProto"
編譯后會(huì)產(chǎn)生HelloWorldProto.class
-
message HelloRequest
編譯后會(huì)產(chǎn)生HelloRequest.class
及對(duì)應(yīng)的幾個(gè)內(nèi)部類(lèi)铅碍,代表了 gRPC 請(qǐng)求的實(shí)體润绵。可以通過(guò)它們來(lái)構(gòu)造請(qǐng)求并獲取請(qǐng)求中的內(nèi)容胞谈。
-
message HelloReply
編譯后會(huì)產(chǎn)生HelloReply.class
及對(duì)應(yīng)的幾個(gè)內(nèi)部類(lèi)尘盼,代表了 gRPC 響應(yīng)的實(shí)體》成可以通過(guò)它們來(lái)構(gòu)造響應(yīng)并獲取響應(yīng)中的內(nèi)容卿捎。
-
service Greeter
編譯后會(huì)產(chǎn)生GreeterRrpc.class
及對(duì)應(yīng)的幾個(gè)內(nèi)部類(lèi),代表了服務(wù)的實(shí)體- 注意:這個(gè)
GreeterRrpc
類(lèi)及對(duì)應(yīng)的幾個(gè)內(nèi)部類(lèi)在下面都會(huì)用到径密,作為 Client Stubs 和 Server Stubs -
GreeterGrpc.GreeterImplBase
里面有sayHello()
方法午阵,可以作為 Server Stubs -
GreeterGrpc.GreeterBlockingStub
里面有sayHello()
方法,可以作為 Client Stubs
- 注意:這個(gè)
下面來(lái)看 RPC 服務(wù)是如何被提供的享扔,文件位置 /examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java
底桂。可以看出:
- 服務(wù)端通過(guò)重載
GreeterGrpc.GreeterImplBase
里的sayHello()
方法來(lái)提供服務(wù)的具體實(shí)現(xiàn) - 通過(guò)
ServerBuilder
來(lái)啟動(dòng) gRPC server 來(lái)處理請(qǐng)求 - gRPC 框架負(fù)責(zé)幫我們解碼請(qǐng)求(將請(qǐng)求內(nèi)容轉(zhuǎn)換為
HelloRequest
類(lèi))惧眠,執(zhí)行服務(wù)方法籽懦,編碼響應(yīng)(將HelloReply
類(lèi)轉(zhuǎn)換成 Protocol Buffers 對(duì)應(yīng)的格式)
package io.grpc.examples.helloworld;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.logging.Logger;
/**
* Server that manages startup/shutdown of a {@code Greeter} server.
*/
public class HelloWorldServer {
private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
private Server server;
private void start() throws IOException {
/* The port on which the server should run */
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
HelloWorldServer.this.stop();
System.err.println("*** server shut down");
}
});
}
private void stop() {
if (server != null) {
server.shutdown();
}
}
/**
* Await termination on the main thread since the grpc library uses daemon threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
/**
* Main launches the server from the command line.
*/
public static void main(String[] args) throws IOException, InterruptedException {
final HelloWorldServer server = new HelloWorldServer();
server.start();
server.blockUntilShutdown();
}
static 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();
}
}
}
下面來(lái)看 RPC 服務(wù)是如何被調(diào)用的,文件位置 /examples/src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java
氛魁∧核常可以看出:
- 通過(guò)指定主機(jī)和端口來(lái)創(chuàng)建 gRPC channel 管道,代表對(duì) gRPC server 的連接
- 客戶(hù)端通過(guò)
GreeterGrpc.newBlockingStub(channel)
來(lái)得到客戶(hù)端調(diào)用的 Stub秀存,然后再調(diào)用具體的方法 - gRPC 框架負(fù)責(zé)幫我們編碼請(qǐng)求捶码,發(fā)送請(qǐng)求,得到響應(yīng)应又,解碼相應(yīng)
package io.grpc.examples.helloworld;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A simple client that requests a greeting from the {@link HelloWorldServer}.
*/
public class HelloWorldClient {
private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
/** Construct client connecting to HelloWorld server at {@code host:port}. */
public HelloWorldClient(String host, int port) {
this(ManagedChannelBuilder.forAddress(host, port)
// Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
// needing certificates.
.usePlaintext()
.build());
}
/** Construct client for accessing HelloWorld server using the existing channel. */
HelloWorldClient(ManagedChannel channel) {
this.channel = channel;
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
/** Say hello to server. */
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());
}
/**
* Greet server. If provided, the first element of {@code args} is the name to use in the
* greeting.
*/
public static void main(String[] args) throws Exception {
HelloWorldClient client = new HelloWorldClient("localhost", 50051);
try {
/* Access a service running on the local machine on port 50051 */
String user = "world";
if (args.length > 0) {
user = args[0]; /* Use the arg as the name to greet if provided */
}
client.greet(user);
} finally {
client.shutdown();
}
}
}