gRPC實現跨語言的微服務間通訊 -- 精通外語的電報員與煲電報粥的小怪獸

做者:亞瑟、文遠java

1. 微服務框架 -- 從系統怪物到服務小怪獸

一個小巧的單體應用會隨着公司業務的擴張而慢慢成長,逐漸演化成一個龐大且複雜的系統怪物,系統任何一處的問題都將影響整個怪物的表現,不多有單獨的開發者能理清系統怪物全部的肌理脈絡,致使bug的定位和新功能的擴展都變得愈來愈困難,對系統的任一改動都要求整個怪物一塊兒迴歸測試並從新部署,效率必然不高。因此公司發展到了必定階段,總會須要從架構上尋找解決系統怪物之道,而微服務就是目前最流行的架構方案之一,它將系統怪物拆分紅多個獨立自治的服務小怪獸,讓咱們有能力分而治之。spring

插畫:牛肉
插畫:牛肉bash

2. RPC框架 -- 小怪獸的電報員

一旦系統怪物被拆分紅了多個服務小怪獸,小怪獸們如何溝通協做就成了咱們最關心的問題。服務小怪獸間的通訊就好像發電報同樣,涉及到數據序列化、反序列化、鏈接管理、收發線程、超時處理等多個問題,RPC框架的出現解決了這些問題,就好像經過電報員發電報同樣,使用RPC框架讓小怪獸們沒必要關心通訊的底層細節。網絡

插畫:牛肉
插畫:牛肉架構

RPC調用細節

  1. 服務消費方(小怪獸A)以本地調用方式調用服務
  2. client stub(小怪獸A的電報員)接受到調用後負責將方法、參數等編碼成可以進行網絡傳輸的消息體(電報)
  3. client stub(小怪獸A的電報員)找到服務地址,並將消息發送到服務端
  4. server stub(小怪獸B的電報員)收到消息(電報)後進行解碼
  5. server stub(小怪獸B的電報員)根據解碼結果調用本地的服務(小怪獸B)
  6. 本地服務(小怪獸B)執行並將結果返回給server stub(小怪獸B的電報員)
  7. server stub(小怪獸B的電報員)將結果編碼成消息(電報)併發送至客戶端
  8. client stub(小怪獸A的電報員)接受到消息(電報)並進行解碼
  9. 服務消費方(小怪獸A)獲得最終的結果

3. gRPC -- 這位電報員是語言天才

若是通訊的小怪獸們語言不通,那麼咱們須要對電報員(亦即RPC框架)的人選提出更高的要求,不管小怪獸們用的是什麼語言,協助通訊的兩位電報員都必須把它們的話翻譯成電報員彼此能理解的同一種語言,亦即IDL(Interface Description Language),是的,電報員在這種狀況下還必須承擔翻譯的角色,而gRPC就是一位如此優秀的電報員。併發

插畫:牛肉
插畫:牛肉框架

4. gRPC Demo

實現Node客戶端小怪獸發送"今晚的月色真美",Java服務端小怪獸收到電報內容,並回復"I love you too"。jvm

  1. 經過Spring Boot建立Java項目,pom.xml中加入以下依賴maven

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>1.21.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.21.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.21.0</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.7.1:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.21.0:exe:${os.detected.classifier}</pluginArtifact>
                    <!--指定生成文件目錄-->
                    <outputDirectory>src/main/java</outputDirectory>
                    <!--從新生成文件時不清除 原有src/main/java下的內容-->
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  2. 定義IDL文件tcp

    syntax = "proto3";
    
    option java_multiple_files = true;
    option java_package = "net.changjinglu.proto";
    option java_outer_classname = "TelegraphProto";
    
    package telegraph;
    
    // The greeting service definition.
    service TelegraphService {
        // Sends a greeting
        rpc SayLove (LoveRequest) returns (LoveReply) {}
    }
    
    // The request message containing the user's name.
    message LoveRequest {
        string message = 1;
    }
    
    // The response message containing the greetings
    message LoveReply {
        string message = 1;
    }
  3. 編譯生成IDL定義的Java服務接口,相關代碼會生成到配置對應的路徑下

    mvn clean install

    clipboard.png
     

  4. 實現IDL定義的Java服務接口

    public class TelegraphGreeterImpl extends TelegraphServiceGrpc.TelegraphServiceImplBase {
    
        @Override
        public void sayLove(LoveRequest request, StreamObserver<LoveReply> responseObserver) {
            System.out.println("收到Node小怪獸的消息:"+request.getMessage());
    
            responseObserver.onNext(LoveReply.newBuilder().setMessage("I Love U Too").build());
    
            //結束
            responseObserver.onCompleted();
        }
    }
  5. 編寫並啓動Java服務端

    public class GrpcServer {
    
        /** GRPC 服務端 */
        private Server server;
    
        public static void main(String[] args) throws IOException, InterruptedException {
            GrpcServer grpcService = new GrpcServer();
    
            grpcService.start();
            System.out.println("GRPC 服務端啓動成功");
    
            //GRPC 服務端須要手動阻塞線程
            grpcService.waitTermination();
    
    
        }
    
        private void start() throws IOException {
            //綁定接口、啓動服務
            this.server = ServerBuilder.forPort(8899)
                    .addService(new TelegraphGreeterImpl())
                    .build()
                    .start();
    
            System.out.println("server start!");
    
            //這裏是爲了防止jvm關閉了,可是tcp尚未關閉的狀況
            Runtime.getRuntime().addShutdownHook(new Thread(()->{
                System.out.println("關閉jvm");
                GrpcServer.this.stop();
            }));
        }
    
        private void stop() {
            if (this.server != null) {
                this.server.shutdown();
            }
        }
    
        private void waitTermination() throws InterruptedException {
    
            if (this.server != null) {
                server.awaitTermination();
            }
        }
    }

    clipboard.png
     

  6. 編寫並啓動Nodejs客戶端,客戶端使用相同的IDL

    var PROTO_FILE_PATH = '/Users/wenyuan/Nodejs/grpc/proto/telegraph.proto';
    var grpc = require('grpc');
    var grpcService = grpc.load(PROTO_FILE_PATH).telegraph;
    
    
    function main() {
        var stub = new grpcService.TelegraphService('localhost:8899',grpc.credentials.createInsecure());
    
        stub.sayLove({message:'今晚的月色真美'},function (error, result) {
            console.log('收到Java小怪獸的消息: ' + result.message);
        });
    }
    
    main();
  7. Java服務端收到消息並回復
    clipboard.png
     
  8. Nodejs客戶端收到Java服務端的回覆
    clipboard.png
相關文章
相關標籤/搜索