grpc應用詳解與實例剖析

gRPC 是一個高性能、通用的開源RPC框架,基于HTTP/2協(xié)議標準和Protobuf序列化協(xié)議開發(fā),支持眾多的開發(fā)語言。

概述

在gRPC框架中,客戶端可以像調用本地對象一樣直接調用位于不同機器的服務端方法,如此我們就可以非常方便的創(chuàng)建一些分布式的應用服務。

在服務端,我們實現了所定義的服務和可供遠程調用的方法,運行一個gRPC server來處理客戶端的請求;在客戶端,gRPC實現了一個stub(可以簡單理解為一個client),其提供跟服務端相同的方法。

image

gRPC使用protocol buffers作為接口描述語言(IDL)以及底層的信息交換格式,一般情況下推薦使用 proto3因為其能夠支持更多的語言,并減少一些兼容性的問題。

特性

基于HTTP/2
HTTP/2 提供了連接多路復用、雙向流、服務器推送、請求優(yōu)先級、首部壓縮等機制。可以節(jié)省帶寬、降低TCP鏈接次數、節(jié)省CPU,幫助移動設備延長電池壽命等。gRPC 的協(xié)議設計上使用了HTTP2 現有的語義,請求和響應的數據使用HTTP Body 發(fā)送,其他的控制信息則用Header 表示。
IDL使用ProtoBuf
gRPC使用ProtoBuf來定義服務,ProtoBuf是由Google開發(fā)的一種數據序列化協(xié)議(類似于XML、JSON、hessian)。ProtoBuf能夠將數據進行序列化,并廣泛應用在數據存儲、通信協(xié)議等方面。壓縮和傳輸效率高,語法簡單,表達力強。
多語言支持(C, C++, Python, PHP, Nodejs, C#, Objective-C、Golang、Java)
gRPC支持多種語言,并能夠基于語言自動生成客戶端和服務端功能庫。目前已提供了C版本grpc、Java版本grpc-java 和 Go版本grpc-go,其它語言的版本正在積極開發(fā)中,其中,grpc支持C、C++、Node.js、Python、Ruby、Objective-C、PHP和C#等語言,grpc-java已經支持Android開發(fā)。
gRPC已經應用在Google的云服務和對外提供的API中,其主要應用場景如下:

  • 低延遲、高擴展性、分布式的系統(tǒng)
  • 同云服務器進行通信的移動應用客戶端
  • 設計語言獨立、高效、精確的新協(xié)議
  • 便于各方面擴展的分層設計,如認證、負載均衡、日志記錄、監(jiān)控等

grpc優(yōu)缺點:

優(yōu)點:

  • 1、protobuf二進制消息,性能好/效率高(空間和時間效率都很不錯)
  • 2、proto文件生成目標代碼,簡單易用
  • 3、序列化反序列化直接對應程序中的數據類,不需要解析后在進行映射(XML,JSON都是這種方式)
  • 4、支持向前兼容(新加字段采用默認值)和向后兼容(忽略新加字段),簡化升級
  • 5、支持多種語言(可以把proto文件看做IDL文件)
  • 6、Netty等一些框架集成

缺點:

  • 1、GRPC尚未提供連接池,需要自行實現
  • 2、尚未提供“服務發(fā)現”、“負載均衡”機制
  • 3、因為基于HTTP2,絕大部多數HTTP Server、Nginx都尚不支持,即Nginx不能將GRPC請求作為HTTP請求來負載均衡,而是作為普通的TCP請求。(nginx1.9版本已支持)
  • 4、Protobuf二進制可讀性差(貌似提供了Text_Fromat功能)
  • 5、默認不具備動態(tài)特性(可以通過動態(tài)定義生成消息類型或者動態(tài)編譯支持)

gRPC有四種通信方式:

  • 1、 Simple RPC
    簡單rpc
    這就是一般的rpc調用,一個請求對象對應一個返回對象
    proto語法:
rpc simpleHello(Person) returns (Result) {}
  • 2、 Server-side streaming RPC
    服務端流式rpc
    一個請求對象,服務端可以傳回多個結果對象
    proto語法
rpc serverStreamHello(Person) returns (stream Result) {}
  • 3、 Client-side streaming RPC
    客戶端流式rpc
    客戶端傳入多個請求對象,服務端返回一個響應結果
    proto語法
rpc clientStreamHello(stream Person) returns (Result) {}
  • 4、 Bidirectional streaming RPC
    雙向流式rpc
    結合客戶端流式rpc和服務端流式rpc,可以傳入多個對象,返回多個響應對象
    proto語法
rpc biStreamHello(stream Person) returns (stream Result) {}

服務定義及ProtoBuf

gRPC使用ProtoBuf定義服務, 我們可以一次性的在一個 .proto 文件中定義服務并使用任何支持它的語言去實現客戶端和服務器,反過來,它們可以在各種環(huán)境中,從云服務器到你自己的平板電腦—— gRPC 幫你解決了不同語言及環(huán)境間通信的復雜性。使用 protocol buffers 還能獲得其他好處,包括高效的序列號,簡單的 IDL 以及容易進行接口更新。

protoc編譯工具

protoc工具可在https://github.com/google/protobuf/releases 下載到源碼。 且將protoc的bin目錄配置到環(huán)境變量中,如下圖:

image.png

實際開發(fā)中一般都通過在idea上配置com.google.protobuf插件進行開發(fā),這一點在https://github.com/grpc/grpc-java上的文檔有詳細說明,如果使用gradle進行項目構建的話,https://github.com/google/protobuf-gradle-plugin上有protobuf-gradle-plugin插件的詳細使用說明。

protobuf語法

  • 1、syntax = “proto3”;
    文件的第一行指定了你使用的是proto3的語法:如果你不指定,protocol buffer 編譯器就會認為你使用的是proto2的語法。這個語句必須出現在.proto文件的非空非注釋的第一行。

  • 2、message SearchRequest {……}
    message 定義實體,c/c++/go中的結構體,php中類

  • 3、基本數據類型


    image.png
  • 4、注釋符號: 雙斜線,如://xxxxxxxxxxxxxxxxxxx

  • 5、字段唯一數字標識(用于在二進制格式中識別各個字段,上線后不宜再變動):Tags
    1到15使用一個字節(jié)來編碼,包括標識數字和字段類型(你可以在Protocol Buffer 編碼中查看更多詳細);16到2047占用兩個字節(jié)。因此定義proto文件時應該保留1到15,用作出現最頻繁的消息類型的標識。記得為將來會繼續(xù)增加并可能頻繁出現的元素留一點兒標識區(qū)間,也就是說,不要一下子把1—15全部用完,為將來留一點兒。
    標識數字的合法范圍:最小是1,最大是 229 - 1,或者536,870,911。
    另外,不能使用19000 到 19999之間的數字(FieldDescriptor::kFirstReservedNumber through FieldDescriptor::kLastReservedNumber),因為它們被Protocol Buffers保留使用

  • 6、字段修飾符:
    required:值不可為空
    optional:可選字段
    singular:符合語法規(guī)則的消息包含零個或者一個這樣的字段(最多一個)
    repeated:一個字段在合法的消息中可以重復出現一定次數(包括零次)。重復出現的值的次序將被保留。在proto3中,重復出現的值類型字段默認采用壓縮編碼。你可以在這里找到更多關于壓縮編碼的東西: Protocol Buffer Encoding。
    默認值: optional PhoneType type = 2 [default = HOME];
    proto3中,省略required,optional,singular,由protoc自動選擇。

  • 7、代理類生成
    1)、C++, 每一個.proto 文件可以生成一個 .h 文件和一個 .cc 文件
    2)、Java, 每一個.proto文件可以生成一個 .java 文件
    3)、Python, 每一個.proto文件生成一個模塊,其中為每一個消息類型生成一個靜態(tài)的描述器,在運行時,和一個metaclass一起使用來創(chuàng)建必要的Python數據訪問類
    4)、Go, 每一個.proto生成一個 .pb.go 文件
    5)、Ruby, 每一個.proto生成一個 .rb 文件
    6)、Objective-C, 每一個.proto 文件可以生成一個 pbobjc.h 和一個pbobjc.m 文件
    7)、C#, 每一個.proto文件可以生成一個.cs文件.
    8)、php, 每一個message消息體生成一個.php類文件,并在GPBMetadata目錄生成一個對應包名的.php類文件,用于保存.proto的二進制元數據。

  • 8、字段默認值

  • strings, 默認值是空字符串(empty string)
  • bytes, 默認值是空bytes(empty bytes)
  • bools, 默認值是false
  • numeric, 默認值是0
  • enums, 默認值是第一個枚舉值(value必須為0)
  • message fields, the field is not set. Its exact value is langauge-dependent. See the generated code guide for details.
  • repeated fields,默認值為empty,通常是一個空list
  • 9、枚舉
// 枚舉類型,必須從0開始,序號可跨越。同一包下不能重名,所以加前綴來區(qū)別
enum WshExportInstStatus {
    INST_INITED = 0;
    INST_RUNNING = 1;
    INST_FINISH = 2;
    INST_FAILED = 3;
}
  • 10、Maps字段類型
map<key_type, value_type> map_field = N;

其中key_type可以是任意Integer或者string類型(所以,除了floating和bytes的任意標量類型都是可以的)value_type可以是任意類型。
例如,如果你希望創(chuàng)建一個project的映射,每個Projecct使用一個string作為key,你可以像下面這樣定義:

map<string, Project> projects = 3;

Map的字段可以是repeated。
序列化后的順序和map迭代器的順序是不確定的,所以你不要期望以固定順序處理Map
當為.proto文件產生生成文本格式的時候,map會按照key 的順序排序,數值化的key會按照數值排序。
從序列化中解析或者融合時,如果有重復的key則后一個key不會被使用,當從文本格式中解析map時,如果存在重復的key。

  • 11、默認值
    字符串類型默認為空字符串
    字節(jié)類型默認為空字節(jié)
    布爾類型默認false
    數值類型默認為0值
    enums類型默認為第一個定義的枚舉值,必須是0

  • 12、服務
    服務使用service{}包起來,每個方法使用rpc起一行申明,一個方法包含一個請求消息體和一個返回消息體

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
}

更多protobuf參考(google)
更多protobuf參考(csdn)

對于開發(fā)者而言:

  • 1、需要使用protobuf定義接口,即.proto文件

  • 2、然后使用compile工具生成特定語言的執(zhí)行代碼,比如JAVA、C/C++、Python等。類似于thrift,為了解決跨語言問題。

  • 3、啟動一個Server端,server端通過偵聽指定的port,來等待Client鏈接請求,通常使用Netty來構建,GRPC內置了Netty的支持。

  • 4、啟動一個或者多個Client端,Client也是基于Netty,Client通過與Server建立TCP長鏈接,并發(fā)送請求;Request與Response均被封裝成HTTP2的stream Frame,通過Netty Channel進行交互。

實例

引入maven或gradle的jar依賴

//maven
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-netty-shaded</artifactId>
  <version>1.19.0</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-protobuf</artifactId>
  <version>1.19.0</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-stub</artifactId>
  <version>1.19.0</version>
</dependency>

//gradle
compile 'io.grpc:grpc-netty-shaded:1.19.0'
compile 'io.grpc:grpc-protobuf:1.19.0'
compile 'io.grpc:grpc-stub:1.19.0'

生成的代碼步驟

  • 1、對于基于protobuf的codegen,將原型文件Xxx.proto放在src/main/proto 和src/test/proto目錄中。

  • 2、protobuf的插件的使用
    對于使用Maven構建系統(tǒng)集成基于protobuf的,代碼生成,您可以使用protobuf-maven-plugin

<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.6.1:exe:${os.detected.classifier}</protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.19.0:exe:${os.detected.classifier}</pluginArtifact>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>compile-custom</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

對于與Gradle構建系統(tǒng)集成的基于protobuf的codegen,您可以在build.gradle文件中增加protobuf-gradle-plugin插件:

apply plugin: 'com.google.protobuf'

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.5'
  }
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.6.1"
    }
    plugins {
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.19.0'
        }
    }

    //生成的grpc的message類的路徑
    generatedFilesBaseDir = "src"
    
    generateProtoTasks {
        all()*.plugins {
            grpc {
                //生成的grpc的service服務類的路徑
                outputSubDir = 'java'
            }
        }
    }
}

然后在idea的控制臺上輸入:gradle generateProto即可生成grpc代碼

image.png

1、Simple RPC 簡單rpc,這就是一般的rpc調用,一個請求對象對應一個返回對象

定義Student.proto文件

syntax = "proto3";

package com.yibo.proto;

option java_package = "com.yibo.proto";
option java_outer_classname = "StudentProto";
option java_multiple_files = true;

service StudentService {
    rpc GetRealnameByUsername (MyRequest) returns (MyResponse) {
    }
}

message MyRequest {
    string username = 1;
}

message MyResponse {
    string realname = 1;
}

服務端實現proto中的接口:

public class StudentServiceImpl extends StudentServiceGrpc.StudentServiceImplBase {

    @Override
    public void getRealnameByUsername(MyRequest request, StreamObserver<MyResponse> responseObserver) {
        System.out.println("接收到客戶端信息:" + request.getUsername());

        //將返回結果構造完,并且返回給客戶端
        responseObserver.onNext(MyResponse.newBuilder().setRealname("張三").build());
        //告訴客戶端方法執(zhí)行完畢
        responseObserver.onCompleted();
    }
}

grpc服務端的實現

public class GrpcServer {

    private Server server;

    private void start() throws IOException {
        this.server = ServerBuilder.forPort(8899).addService(new StudentServiceImpl()).build().start();

        System.out.println("server started!");

        //這是在服務端jvm關閉之前主動退出grpc服務,且關閉其相應的資源
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("關閉jvm");
            GrpcServer.this.stop();
        }));
    }

    private void stop(){
        if(null != this.server){
            this.server.shutdown();
        }
    }

    //讓服務啟動后處于等待狀態(tài),不然服務已啟動馬上就停止了
    private void awaitTermination() throws InterruptedException {
        if(null != this.server){
            this.server.awaitTermination();
        }
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        GrpcServer grpcServer = new GrpcServer();

        grpcServer.start();
        grpcServer.awaitTermination();
    }
}

grpc客戶端的實現

public class GrpcClient {

    public static void main(String[] args) {
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",8899)
                .usePlaintext().build();
        StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel);

        MyResponse myResponse = blockingStub.getRealnameByUsername(MyRequest.newBuilder().setUsername("李四").build());
        System.out.println(myResponse.getRealname());
    }
}

2、Server-side streaming RPC 服務端流式rpc,一個請求對象,服務端可以傳回多個結果對象

定義Student.proto文件

syntax = "proto3";

package com.yibo.proto;

option java_package = "com.yibo.proto";
option java_outer_classname = "StudentProto";
option java_multiple_files = true;

service StudentService {
    rpc GetStudentByAge (StudentRequest) returns (stream StudentResponse){}
}

message StudentRequest {
    int32 age = 1;
}

message StudentResponse {
    string name = 1;
    int32 age = 2;
    string city = 3;
}

服務端實現proto中的接口:

public class StudentServiceImpl extends StudentServiceGrpc.StudentServiceImplBase {
    @Override
    public void getStudentByAge(StudentRequest request, StreamObserver<StudentResponse> responseObserver) {
        System.out.println("接收到客戶端信息:" + request.getAge());

        responseObserver.onNext(StudentResponse.newBuilder().setName("張三").setAge(50).setCity("北京").build());
        responseObserver.onNext(StudentResponse.newBuilder().setName("李四").setAge(40).setCity("上海").build());
        responseObserver.onNext(StudentResponse.newBuilder().setName("王五").setAge(30).setCity("深圳").build());
        responseObserver.onNext(StudentResponse.newBuilder().setName("趙六").setAge(20).setCity("重慶").build());
        responseObserver.onCompleted();
    }
}

grpc服務端的實現沿用Simple RPC例子里面的服務端實現

grpc客戶端的實現

public class GrpcClient {

    public static void main(String[] args) {
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",8899)
                .usePlaintext().build();
        StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel);
        Iterator<StudentResponse> iterator = blockingStub.getStudentByAge(StudentRequest.newBuilder().setAge(20).build());
        while(iterator.hasNext()){
            StudentResponse studentResponse = iterator.next();
            System.out.println(studentResponse.getName() + ":" + studentResponse.getName() + ":" + studentResponse.getCity());
        }
    }
}

3、Client-side streaming RPC 客戶端流式rpc,客戶端傳入多個請求對象,服務端返回一個響應結果

定義Student.proto文件

syntax = "proto3";

package com.yibo.proto;

option java_package = "com.yibo.proto";
option java_outer_classname = "StudentProto";
option java_multiple_files = true;

service StudentService {
    rpc GetStudentsWrapperByAges (stream StudentRequest) returns (StudentResponseList){}
}

message StudentRequest {
    int32 age = 1;
}

message StudentResponse {
    string name = 1;
    int32 age = 2;
    string city = 3;
}

message StudentResponseList {
    repeated StudentResponse studentResponse = 1;
}

服務端實現proto中的接口:

public class StudentServiceImpl extends StudentServiceGrpc.StudentServiceImplBase {
    @Override
    public StreamObserver<StudentRequest> getStudentsWrapperByAges(StreamObserver<StudentResponseList> responseObserver) {
        return new StreamObserver<StudentRequest>() {
            @Override
            public void onNext(StudentRequest value) {
                System.out.println("onNext" + value.getAge());
            }

            @Override
            public void onError(Throwable t) {
                System.out.println(t.getMessage());
            }

            @Override
            public void onCompleted() {
                StudentResponse studentResponse1 = StudentResponse.newBuilder().setName("張三").setAge(20).setCity("深圳").build();
                StudentResponse studentResponse2 = StudentResponse.newBuilder().setName("李四").setAge(22).setCity("重慶").build();
                StudentResponseList studentResponseList = StudentResponseList.newBuilder()
                        .addStudentResponse(studentResponse1).addStudentResponse(studentResponse2).build();
                responseObserver.onNext(studentResponseList);
                responseObserver.onCompleted();
            }
        };
    }
}

grpc服務端的實現沿用Simple RPC例子里面的服務端實現

grpc客戶端的實現

public class GrpcClient {

    public static void main(String[] args) {
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",8899)
                .usePlaintext().build();
        StudentServiceGrpc.StudentServiceStub stub = StudentServiceGrpc.newStub(managedChannel);    

        StreamObserver<StudentResponseList> studentResponseListStreamObserver = new StreamObserver<StudentResponseList>() {
            @Override
            public void onNext(StudentResponseList value) {
                value.getStudentResponseList().forEach(studentResponse -> {
                    System.out.println(studentResponse.getName() + ":" + studentResponse.getAge() + ":" + studentResponse.getCity());
                });
            }

            @Override
            public void onError(Throwable t) {
                System.out.println(t.getMessage());
            }

            @Override
            public void onCompleted() {
                System.out.println("completed!");
            }
        };

        StreamObserver<StudentRequest> studentRequestStreamObserver = stub.getStudentsWrapperByAges(studentResponseListStreamObserver);
        studentRequestStreamObserver.onNext(StudentRequest.newBuilder().setAge(10).build());
        studentRequestStreamObserver.onNext(StudentRequest.newBuilder().setAge(20).build());
        studentRequestStreamObserver.onNext(StudentRequest.newBuilder().setAge(30).build());
        studentRequestStreamObserver.onNext(StudentRequest.newBuilder().setAge(40).build());
        studentRequestStreamObserver.onCompleted();

        try {
            //因為grpc調用參數為stream的時候,都是采取異步處理,客戶端程序不會等待結果響應
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4、 Bidirectional streaming RPC 雙向流式rpc,結合客戶端流式rpc和服務端流式rpc,可以傳入多個對象,返回多個響應對象

定義Student.proto文件

syntax = "proto3";

package com.yibo.proto;

option java_package = "com.yibo.proto";
option java_outer_classname = "StudentProto";
option java_multiple_files = true;

service StudentService {
    rpc BiTalk (stream StreamRequest) returns (stream StreamResponse){}
}

message StreamRequest {
    string request_info = 1;
}

message StreamResponse {
    string response_info = 1;
}

服務端實現proto中的接口:

public class StudentServiceImpl extends StudentServiceGrpc.StudentServiceImplBase {

    @Override
    public StreamObserver<StreamRequest> biTalk(StreamObserver<StreamResponse> responseObserver) {
        return new StreamObserver<StreamRequest>() {
            @Override
            public void onNext(StreamRequest value) {
                System.out.println("onNext" + value.getRequestInfo());
                responseObserver.onNext(StreamResponse.newBuilder().setResponseInfo(UUID.randomUUID().toString()).build());
            }

            @Override
            public void onError(Throwable t) {
                System.out.println(t.getMessage());
            }

            @Override
            public void onCompleted() {
                responseObserver.onCompleted();
            }
        };
    }
}

grpc服務端的實現沿用Simple RPC例子里面的服務端實現

grpc客戶端的實現

public class GrpcClient {

    public static void main(String[] args) {
        ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost",8899)
                .usePlaintext().build();

        StudentServiceGrpc.StudentServiceStub stub = StudentServiceGrpc.newStub(managedChannel);

        StreamObserver<StreamRequest> streamRequestStreamObserver = stub.biTalk(new StreamObserver<StreamResponse>() {
            @Override
            public void onNext(StreamResponse value) {
                System.out.println(value.getResponseInfo());
            }

            @Override
            public void onError(Throwable t) {
                System.out.println(t.getMessage());
            }

            @Override
            public void onCompleted() {
                System.out.println("completed!");
            }
        });

        for(int i = 0 ;i < 10 ; i++){
            streamRequestStreamObserver.onNext(StreamRequest.newBuilder().setRequestInfo(LocalDateTime.now().toString()).build());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        try {
            //因為grpc調用參數為stream的時候,都是采取異步處理,客戶端程序不會等待結果響應
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

至此,grpc中grpc-java的大體內容與用法就基本完結了,grpc四種通信方式中,第一種Simple RPC方式是用的最多的,至于其他三種通信方式什么時候用,也是根據具體業(yè)務場景而言的。

grpc在grpc-node的客戶端和服務端的實例。

在WebStorm上創(chuàng)建一個項目,并創(chuàng)建package.json文件

image.png

然后通過npm install將依賴下載到本地

image.png

相比較于Java等語言來說,node.js在grpc的實現過程中有一些明顯的不同,具體體現代碼的編寫方式上,那么grpc在node.js的實現中有2種情況,即動態(tài)代碼生成和靜態(tài)代碼生成。

  • 動態(tài)代碼生成:不需要提前由proto文件生成對應的js代碼,通過提前指定proto文件的位置,在運行的過程中動態(tài)的取生成proto文件對應的文件。動態(tài)生成包括客戶端的動態(tài)生成和服務器端的動態(tài)生成。
  • 靜態(tài)代碼生成:和前面的Java類似,通過grpc針對node.js的編譯器,提前由proto文件取生成對應的js文件。

定義Student.proto文件

syntax = "proto3";

package com.yibo.proto;

option java_package = "com.yibo.proto";
option java_outer_classname = "StudentProto";
option java_multiple_files = true;

service StudentService {
    rpc GetRealnameByUsername (MyRequest) returns (MyResponse) {}

    rpc GetStudentByAge (StudentRequest) returns (stream StudentResponse){}

    rpc GetStudentsWrapperByAges (stream StudentRequest) returns (StudentResponseList){}

    rpc BiTalk (stream StreamRequest) returns (stream StreamResponse){}
}

message MyRequest {
    string username = 1;
}

message MyResponse {
    string realname = 1;
}

message StudentRequest {
    int32 age = 1;
}

message StudentResponse {
    string name = 1;
    int32 age = 2;
    string city = 3;
}

message StudentResponseList {
    repeated StudentResponse studentResponse = 1;
}

message StreamRequest {
    string request_info = 1;
}

message StreamResponse {
    string response_info = 1;
}

動態(tài)代碼生成

創(chuàng)建grpc-node的客戶端
在根目錄下創(chuàng)建app目錄,然后創(chuàng)建grpcClient.js

var PROTO_FILE_PATH = '/Users/yibo/front_end/rpc_demo/proto/Student.proto';
var grpc = require('grpc');
var grpcService = grpc.load(PROTO_FILE_PATH).com.yibo.proto;

var client = new grpcService.StudentService('localhost:8899',grpc.credentials.createInsecure());

client.getRealnameByUsername({username : 'lisi'},function(error,respData){
    console.log(respData);
});

創(chuàng)建grpc-node的服務端
在app目錄下,創(chuàng)建grpcServer.js

var PROTO_FILE_PATH = '/Users/yibo/front_end/rpc_demo/proto/Student.proto';
var grpc = require('grpc');
var grpcService = grpc.load(PROTO_FILE_PATH).com.yibo.proto;

var server = new grpc.Server();

server.addService(grpcService.StudentService.service,{
    getRealnameByUsername: getRealnameByUsername,
    getStudentByAge: getStudentByAge,
    getStudentsWrapperByAges: getStudentsWrapperByAges,
    biTalk: biTalk
});

server.bind('localhost:8899',grpc.ServerCredentials.createInsecure());
server.start();

function getRealnameByUsername(call,callback){
    console.log("username:" + call.request.username);
    callback(null, {realname : '張三'});
}

function getStudentByAge,(call,callback){
    //演示例子用不上,所有采用空實現
}

function getStudentsWrapperByAges:(call,callback){
    //演示例子用不上,所有采用空實現
}

function biTalk(call,callback){
    //演示例子用不上,所有采用空實現
}

靜態(tài)代碼生成(和grpc-java類似)

首先要保證本機裝有protoc
grpc_node_plugin插件的安裝說明:https://github.com/grpc/grpc/tree/v1.19.0/examples/node/static_codegen
在WebStorm控制臺上敲入npm install -g grpc-tools安裝grpc_node_plugin插件

image.png

然后在在WebStorm控制臺上執(zhí)行(可能會報static_codegen/: No such file or directory錯誤,那么需要在根目錄下創(chuàng)建static_codegen目錄):

grpc_tools_node_protoc --js_out=import_style=commonjs,binary:static_codegen/ --grpc_out=static_codegen --plugin=protoc-gen-grpc=/usr/local/bin/grpc_tools_protoc_plugin node proto/Student.proto

執(zhí)行命令后,如果沒有任何提示表示成功,如下圖:


image.png

創(chuàng)建grpc-node的客戶端
在app目錄下,創(chuàng)建grpcClient2.js

var service = require('../static_codegen/proto/Student_grpc_pb');
var messages = require('../static_codegen/proto/Student_pb');

var grpc = require('grpc');

var client = new service.StudentServiceVlient('localhost:8899',grpc.credentials.createInsecure());

var request = new message.MyRequest();
request.setUsername('lisi');

client.getRealNameByUsername(request,function(error,respData){
    console.log(respData.getRealname());
});

創(chuàng)建grpc-node的服務端
在app目錄下,創(chuàng)建grpcServer2.js

var service = require('../static_codegen/proto/Student_grpc_pb');
var messages = require('../static_codegen/proto/Student_pb');

var grpc = require('grpc');

var server = new grpc.Server();

server.addService(service.Student.ServiceService,{
    getRealnameByUsername: getRealnameByUsername,
    getStudentByAge: getStudentByAge,
    getStudentsWrapperByAges: getStudentsWrapperByAges,
    biTalk: biTalk
});

server.bind('localhost:8899',grpc.ServerCredentials.createInsecure());
server.start();

function getRealnameByUsername(call,callback){
    console.log('request:' + call.request.getUsername());

    var myResponse = new message.MyResponse();
    myResponse.setRealname('王五');
    callback(null,myResponse);
}

function getStudentByAge,(call,callback){
    //演示例子用不上,所有采用空實現
}

function getStudentsWrapperByAges:(call,callback){
    //演示例子用不上,所有采用空實現
}

function biTalk(call,callback){
    //演示例子用不上,所有采用空實現
}

綜合上面的grpc-java和grpc-node的例子,grpc就可以實現跨語言異構平臺之間的rpc調用。而且grpc是一個很有前途的跨語言異構平臺rpc框架,SpringBoot已經提供了整合,加上google的背書,grpc未來一定會得到更廣泛的應用。

參考:

grpc

GRPC快速入門

gRPC

淺析gRPC

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。