gRPC 的一些實(shí)踐

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 的版本周霉。
    跨語(yǔ)言跨平臺(tái)
  • 入手簡(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
啟動(dòng)服務(wù)端
啟動(dòng)客戶(hù)端

我們看看示例中的幾個(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)容胞谈。

    HelloRequest.class 及對(duì)應(yīng)的幾個(gè)內(nèi)部類(lè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)容卿捎。

    `HelloReply.class 及對(duì)應(yīng)的幾個(gè)內(nèi)部類(lè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
      GreeterRrpc.class 及對(duì)應(yīng)的幾個(gè)內(nèi)部類(lèi)

下面來(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();
    }
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宙项,一起剝皮案震驚了整個(gè)濱河市乏苦,隨后出現(xiàn)的幾起案子株扛,更是在濱河造成了極大的恐慌,老刑警劉巖汇荐,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洞就,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡掀淘,警方通過(guò)查閱死者的電腦和手機(jī)旬蟋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)革娄,“玉大人倾贰,你說(shuō)我怎么就攤上這事冕碟。” “怎么了匆浙?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵安寺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我首尼,道長(zhǎng)挑庶,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任软能,我火速辦了婚禮迎捺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘查排。我一直安慰自己凳枝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布跋核。 她就那樣靜靜地躺著范舀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪了罪。 梳的紋絲不亂的頭發(fā)上锭环,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音泊藕,去河邊找鬼辅辩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛娃圆,可吹牛的內(nèi)容都是我干的玫锋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼讼呢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼撩鹿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起悦屏,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤节沦,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后础爬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體甫贯,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年看蚜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了叫搁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖渴逻,靈堂內(nèi)的尸體忽然破棺而出疾党,到底是詐尸還是另有隱情,我是刑警寧澤惨奕,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布仿贬,位于F島的核電站,受9級(jí)特大地震影響墓贿,放射性物質(zhì)發(fā)生泄漏茧泪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一聋袋、第九天 我趴在偏房一處隱蔽的房頂上張望队伟。 院中可真熱鬧,春花似錦幽勒、人聲如沸嗜侮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锈颗。三九已至,卻和暖如春咪惠,著一層夾襖步出監(jiān)牢的瞬間击吱,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工遥昧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留覆醇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓炭臭,卻偏偏與公主長(zhǎng)得像永脓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鞋仍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • GRPC是基于protocol buffers3.0協(xié)議的. 本文將向您介紹gRPC和protocol buffe...
    二月_春風(fēng)閱讀 17,989評(píng)論 2 28
  • gRPC是由Google主導(dǎo)開(kāi)發(fā)的RPC框架常摧,使用HTTP/2協(xié)議并用ProtoBuf作為序列化工具。其客戶(hù)端提供...
    CZ_Golang閱讀 82,148評(píng)論 9 71
  • 一.Grpc簡(jiǎn)介 一個(gè)2016年才由google正式發(fā)布的的RPC框架威创,基于http2,protobuf協(xié)議 官網(wǎng)...
    我也是玄沖閱讀 8,670評(píng)論 0 2
  • 1)簡(jiǎn)介 gRPC負(fù)載平衡的主要實(shí)現(xiàn)機(jī)制是外部負(fù)載平衡落午,即通過(guò)外部負(fù)載平衡器來(lái)向客戶(hù)端提供更新后的服務(wù)器列表。 g...
    Jay_Guo閱讀 13,268評(píng)論 6 22
  • JACK那婉,手劃過(guò)這個(gè)名字時(shí)板甘,妮妮才發(fā)現(xiàn),心里還是會(huì)隱隱作痛详炬。 在那次培訓(xùn)會(huì)前,妮妮看見(jiàn)滿(mǎn)滿(mǎn)當(dāng)當(dāng)?shù)娜藚s沒(méi)幾個(gè)認(rèn)識(shí)的時(shí)...
    妮妮魔豆閱讀 550評(píng)論 0 1