gRPC初探——概念介紹以及如何構建一個簡單的gRPC服務

引言

對於分佈式系統而言,不一樣的服務分佈在不一樣的節點上,一個服務要完成本身的功能常常須要調用其餘服務的接口,好比典型的微服務架構。一般這種服務調用方式有兩種,一種是發送HTTP請求的方式,另外一種則是RPC的方式,RPC是Remote Procedure Call(遠程過程調用)的簡稱,可讓咱們像調用本地接口同樣使用遠程服務。相比HTTP調用,RPC的方式至少在如下幾個方面有優點java

  • 傳輸效率
    RPC能夠自定義TCP報文,基於TCP協議進行通訊,好比dubbo;同時也支持使用HTTP2協議進行通訊,好比gRPC。這相比傳統的HTTP1.1協議報文體積會更小,傳輸效率會更高。
  • 性能消耗
    RPC框架一般自帶高效的序列化機制,序列化和反序列化耗時更低,序列化後的字節數一般也更小。
  • 負責均衡
    RPC框架一般自帶負載均衡策略,而HTTP請求要作負載均衡須要外部應用如Nginx的支持。
  • 服務治理
    下游服務新增,重啓,下線時能自動通知上游使用者,而HTTP的方式須要事先通知並修改相關配置。

正由於基於RPC方式的服務調用有着性能消耗低,傳輸效率高,更容易作負載均衡和服務治理的優勢,因此分佈式系統內部大多采用這種方式進行分佈式服務調用。可供選擇的RPC框架不少,好比Hession,Dubbo,Thrift這些很早就開源,平時項目中使用也不少。不過最近有一個叫gRPC的RPC框架很火,被使用在不少微服務相關的開源項目中,好比華爲的Apache ServiceComb Saga。這篇博客做爲我學習gRPC的入門筆記,只對它的核心概念和簡單用法作些介紹數組

1. gRPC簡介

gRPC是由Google開發並開源的RPC框架,它具備如下特色網絡

  • 語言中立
    支持C,Java,Go等多種語言來構建RPC服務,這是gRPC被普遍的應用在微服務項目中的重要緣由,由於不一樣的微服務可能用不一樣的語言構建。
  • 基於HTTP/2協議
    支持雙向流,消息頭壓縮,單TCP的多路複用,服務端推送等,這些特性使得gRPC更加適用於移動場景下的客戶端和服務端之間的通訊。
  • 基於IDL定義服務
    編寫.proto文件便可生成特定語言的數據結構、服務端接口和客戶端Stub。
  • 支持Protocol Buffer序列化
    Protocol Buffer是由Google開發的一種數據序列化協議(相似於XML、JSON、Hession),平臺無關,壓縮和傳輸效率高,語法簡單,表達能力強。

一個gRPC服務的大致架構能夠用官網上的一幅圖表示
數據結構

gRPC服務端使用C++構建,客戶端可使用Ruby或者Java構建,客戶端經過一個Stub存根(代理)對象發起RPC調用,請求和響應消息都使用Protocol Buffer進行序列化。架構

當咱們在微服務中使用gRPC時,整個服務調用過程以下所示(圖片來自網絡)
負載均衡

經過gRPC,遠程服務的調用對使用者更加簡單和透明,底層的傳輸方式,序列化方式,通訊細節等通通不須要關係,固然這些對其餘RPC框架而言也適用。框架

2. 使用Protocol Buffers進行服務定義

一個直觀的想法,在客戶端調用服務端提供的遠程接口前,雙方必須進行一些約定,好比接口的方法簽名,請求和響應的數據結構等,這個過程稱爲服務定義。服務定義須要特定的接口定義語言(IDL)來完成,gRPC中默認使用protocol buffers。它是google很早就開源的一款序列化框架,其定義了一種數據序列化協議,獨立於語言和平臺,提供了多種語言的實現:Java,C++,Go等,每一種實現都包含了相應語言的編譯器和庫文件。使用它進行服務定義須要編寫.proto後綴的IDL文件,並經過其編譯器生成特定語言的數據結構、服務端接口和客戶端Stub代碼。異步

2.1 定義消息

消息是表示RPC接口的請求參數和響應結果的數據結構。以下定義了一個請求消息和響應消息maven

//定義請求消息的結構
message SearchResponse {
  // repeated表示該字段能夠重複任意次,等價於數組:Result[]
  repeated Result result = 1;
}

//定義響應消息的結構
message Result {
  //required表示該字段的值剛好爲1個
  required string url = 1;
  //optional表示該字段的值爲0或1個
  optional string title = 2;
  
  repeated string snippets = 3;
}

定義消息的關鍵字爲message,至關於java中的class關鍵字,一個消息就至關於java中的一個類。消息內能夠有多個字段,字段的類型能夠分類以下

  • 基本數據類型
    int32表示java中的int,int64表示java中的long,string表示java中的string,具體的對應關係以下表所示

  • 複雜數據類型
    枚舉,map等。
enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
map<key_type, value_type> map_field = N;

和java中類中能夠定義類同樣,Protocol Buffers中消息內也能夠定義消息,造成多層的嵌套結構

message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      required int64 ival = 1;
      optional bool  booly = 2;
    }
  }

關於消息定義,有幾點須要注意的地方
1.消息中的字段前能夠有修飾符,修飾符主要有三種

  • required
    required int64 ival = 1;
    該字段的值剛好只有一個,沒有或傳入多個都將報錯。

  • optional
    optional int32 result_per_page = 3 [default = 10];
    該字段的值有0個或1個,傳入多個將報錯。且以optional修飾的字段能夠設置默認值,若沒有設置,則編譯器會根據類型自動設置一個默認值,好比string設置爲空字符串,bool類型設置爲false等。

  •  repeated
    repeated int32 samples = 4
    該字段至關於java中的數組,能夠有0個或多個值。

2.消息中的字段有惟一編號,以下所示

這個惟一編號用來在消息的二進制格式中進行字段的區分,範圍從1-229 - 1,其中19000-19999是保留編號不能使用。這些字段編號在使用過程當中不能進行修改,不然會出現問題。

2.2 定義服務接口

標題中的接口能夠類比java中的Interface,內部能夠有多個方法。gRPC中使用service關鍵定義服務接口

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
}

該服務接口HelloService內部只有一個rpc方法SayHello,請求參數爲HelloRequest,響應結果爲HelloResponse。

grpc中能夠定義4中類型的rpc方法

  • 1.簡單rpc方法
rpc SayHello(HelloRequest) returns (HelloResponse){
}

客戶端發送一個請求,從服務端得到一個響應,整個過程就像一個本地的方法調用。

  • 2.服務端流式響應的rpc方法
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}

客戶端發送一個請求,並從服務端得到一個流(stream)。服務端能夠往流中寫入N個消息做爲響應,而且每一個消息能夠單獨發送,客戶端能夠從流中按順序讀取這些消息,以下圖所示(圖片來自網絡)

  • 3.客戶端流式請求的rpc方法
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}

客戶端經過流發送一連串的多個請求,並等待從服務端返回的一個響應。

  • 4.雙向流式rpc方法
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}

客戶端經過流發送N個請求,服務端經過流發送N個響應,彼此相互獨立,而且讀寫沒有特定的次序要求,好比服務端能夠收到全部請求後再返回響應,也能夠每讀取一個或K個請求會返回響應。
該特性能夠充分利用HTTP/2.0的多路複用功能,實現了服務端和客戶端的全雙工通訊,以下圖所示(圖片來自網絡)

3.構建簡單的gRPC服務

按照慣例,編寫一個gRPC版本的hello world來說解如何構建一個簡單的gRPC服務——客戶端發送一個請求,服務端返回一個響應。
好比
客戶端:takumiCX
服務端:Hello takumiCX

3.1 編寫proto文件,定義消息和接口

  • 建立proto文件

  • 定義消息和接口
//Protocal Buffers的版本有v2和v3之分,語法有較多變化,且相互不兼容
//這裏使用的v3版本的
syntax = "proto3";

//編譯後生成的消息類HelloRequest和HelloReply是否分別放在單獨的class文件中
option java_multiple_files = true;
//生成代碼的包路徑
option java_package = "com.takumiCX.greeter";

//最外層的類名稱
option java_outer_classname = "HelloWorldProto";

//包命名空間
package helloworld;

// 服務接口
service Greeter {
    // 一個簡單的rpc方法
    rpc SayHello (HelloRequest) returns (HelloReply) {}

}

// 請求消息
message HelloRequest {
    string name = 1;
}

// 響應消息
message HelloReply {
    string message = 1;
}

3.2 經過maven插件生成相應代碼

  • pom文件配置以下
<dependencies>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-netty-shaded</artifactId>
        <version>1.16.1</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-protobuf</artifactId>
        <version>1.16.1</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-stub</artifactId>
        <version>1.16.1</version>
    </dependency>
</dependencies>

<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.5.0.Final</version>
        </extension>
    </extensions>
    <plugins>
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.5.1</version>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>
                <pluginId>grpc-java</pluginId>
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.16.1:exe:${os.detected.classifier}</pluginArtifact>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

在target目錄下能夠看到編譯器經過編譯proto文件爲咱們生成了對應的類,以下圖所示

3.3 gRPC服務端建立

  • 第一步:首先要建立一個具體的服務接口實現類GreeterImpl,擴展gRPC爲咱們生成的服務抽象類GreeterGrpc.GreeterImplBase,重寫服務方法
//擴展gRPC自動生成的服務接口抽象,實現業務功能
    static class GreeterImpl extends GreeterGrpc.GreeterImplBase{

        @Override
        public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {

            //構建響應消息,從請求消息中獲取姓名,在前面拼接上"Hello "
            HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + request.getName()).build();

            //在流關閉或拋出異常前能夠調用屢次
            responseObserver.onNext(reply);

            //關閉流
            responseObserver.onCompleted();

        }
    }
  • 建立server對象,監聽特定端口,註冊具體的服務實現類並啓動
//服務要監聽的端口
        int port=50051;

        //建立server對象,監聽端口,註冊服務並啓動
        Server server = ServerBuilder.
                forPort(port)  //監聽50051端口
                .addService(new GreeterImpl()) //註冊服務
                .build()  //建立Server對象
                .start(); //啓動

        log.info("Server started,listening on "+port);

        server.awaitTermination();

完整代碼以下

/**
 * @author: takumiCX
 * @create: 2018-12-01
 **/
public class HelloWorldServer {

    private static final Logger log=Logger.getLogger(HelloWorldServer.class.getName());


    //擴展gRPC自動生成的服務接口,實現業務功能
    static class GreeterImpl extends GreeterGrpc.GreeterImplBase{

        @Override
        public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {

            //構建響應消息,從請求消息中獲取姓名,在前面拼接上"Hello "
            HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + request.getName()).build();

            //在流關閉或拋出異常前能夠調用屢次
            responseObserver.onNext(reply);

            //關閉流
            responseObserver.onCompleted();

        }
    }


    public static void main(String[] args) throws IOException, InterruptedException {

        //服務要監聽的端口
        int port=50051;

        //建立服務對象,監聽端口,註冊服務並啓動
        Server server = ServerBuilder.
                forPort(port)  //監聽50051端口
                .addService(new GreeterImpl()) //註冊服務
                .build()  //建立Server對象
                .start(); //啓動

        log.info("Server started,listening on "+port);

        server.awaitTermination();

    }

}

gRPC的服務端建立過程以下所示(圖片來自網絡)

3.5 gRPC客戶端建立

整個過程能夠分爲3步

  • 1.根據服務端的ip和端口號,建立ManagedChannel
  • 2.建立供客戶端使用的stub對象,能夠建立兩種類型的stub,一種進行同步調用,一種進行異步調用,後者發起調用的業務線程不會同步阻塞。
  • 3.經過stub對象發起rpc調用,獲取服務端響應。

完整代碼以下:

/**
 * @author: takumiCX
 * @create: 2018-12-01
 **/
public class HelloWorldClient {

    private static final Logger log=Logger.getLogger(HelloWorldClient.class.getName());


    public static void main(String[] args) {


        String host="localhost";

        int port=50051;

        //1.建立ManagedChannel,綁定服務端ip地址和端口
        ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext()
                .build();

        //2.得到同步調用的stub對象
        GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);

//        //得到異步調用的stub對象
//        GreeterGrpc.GreeterFutureStub futureStub = GreeterGrpc.newFutureStub(channel);

        Scanner scanner = new Scanner(System.in);
        while (true){
            //從控制檯讀取用戶輸入
            String name = scanner.nextLine().trim();
            //構建請求消息
            HelloRequest helloRequest = HelloRequest.newBuilder().setName(name).build();
            //經過stub代理對象進行服務調用,獲取服務端響應
            HelloReply helloReply = stub.sayHello(helloRequest);
            final String message = helloReply.getMessage();
            log.warning("Greeting: "+message);
        }
    }
}

gRPC客戶端的調用流程以下所示

3.6 測試

先啓動gRPC服務端,而後啓動gRPC客戶單。客戶端發送gRPC請求takumiCX,收到了來自服務端的響應Hello takumiCX

4. 總結

gRPC做爲開源RPC框架的新勢力,基於HTTP/2.0協議進行設計,使用高性能的Protocol Buffer進行消息的序列化,於是性能很是好,並且提供了完整的負載均衡和服務治理能力,加上其和語言無關、平臺無關的特色,很是適合做爲微服務內部服務間調用的選型。

5. 參考資料

《深刻淺出gRPC》 https://developers.google.com/protocol-buffers/ https://grpc.io/docs/guides/concepts.html#service-definition

相關文章
相關標籤/搜索