gRPC使用初探

gRPC是谷歌開源的一款RPC框架,基於HTTP/2協議,使用protocol buffer作爲接口定義語言(Interface Definition Language)和底層數據交換格式。gRPC最大的優點是能跨語言,因爲使用和語言無關的protobuf來定義接口,並且使用標準的http協議來進行傳輸,能使不同語言的服務端和客戶端互相兼容。

gRPC-java的github地址是https://github.com/grpc/grpc-java。它是gRPC的java語言實現。在它的源代碼中已經包含了一個example,裏面有最簡單的gRPC作爲rpc框架的demo。本文是基於它,探討單獨建立一個項目時,如何通過gRPC進行遠程過程調用。

一.下載gRPC源碼

可以直接下載github上的源碼,也可以先fork到自己的倉庫,再從自己的倉庫下載。推薦第二種。

二.通過Intellij新建一個基於maven的項目grpc-demo

三.pom文件中加入如下內容:

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <grpc.version>1.19.0</grpc.version><!-- CURRENT_GRPC_VERSION -->
        <protobuf.version>3.6.1</protobuf.version>
        <protoc.version>3.6.1</protoc.version>
        <!-- required for jdk9 -->
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-bom</artifactId>
                <version>${grpc.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.2</version>
            <scope>provided</scope> <!-- not needed at runtime -->
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-testing</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
            <version>${protobuf.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>2.25.1</version>
            <scope>test</scope>
        </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:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-enforcer-plugin</artifactId>
                <version>1.4.1</version>
                <executions>
                    <execution>
                        <id>enforce</id>
                        <goals>
                            <goal>enforce</goal>
                        </goals>
                        <configuration>
                            <rules>
                                <requireUpperBoundDeps/>
                            </rules>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

以上內容是從grpc源代碼的example的pom中拷貝出來的。

三.編輯proto文件

在src/main下新建文件夾proto,並且新建文件grpc-demo.proto。添加如下內容:

syntax = "proto3";

package GrpcDemo;

option java_package="com.wts.grpc.demo";
option java_outer_classname="GrpcDemoProto";
option java_multiple_files=true;

service Greeter {
    rpc sayHello(HelloRequest) returns (HelloReply){}
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

proto文件的作用是通過它來自動生成實現rpc調用的輔助代碼。

簡單說一下這個文件的內容:

syntax指定了使用的是protobuf3這個版本;

java_package是指定了生成的輔助類所在的package;

java_outer_classname是生成的Protobuf描述類的類名;

java_multiple_files是指定是否將生成的類放到不同的文件中,如果選擇false,那麼所有類都會存在一個文件中(以內部類的形式)

service定義了要生成的rpc接口。

msssage則是定義了rpc接口要傳輸的對象。

四.編譯proto文件生成java類

在項目目錄下執行maven的compile命令(可以直接在idea的右側的maven選項卡中選擇compile)。會生成以下文件:

有了這些類,就可以實現rpc調用,將它們拷貝到源代碼目錄中對應的包下面。

四.編輯server和client類:

server:

package com.wts.grpc.demo;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

import java.io.IOException;
import java.util.logging.Logger;

public class GrpcDemoServer {
    private static final Logger logger = Logger.getLogger(GrpcDemoServer.class.getName());

    private Server server;

    private void start() throws IOException {
        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() {
                System.err.println("*** shutting down gRPC server since JVM is shutting down");
                GrpcDemoServer.this.stop();
                System.err.println("*** server shut down");
            }
        });
    }

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

    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        final GrpcDemoServer server = new GrpcDemoServer();
        server.start();
        server.blockUntilShutdown();
    }

    static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
        @Override
        public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
            HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + request.getName()).build();
            responseObserver.onNext(reply);
            responseObserver.onCompleted();
        }
    }
}

client:

package com.wts.grpc.demo;

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;

public class GrpcDemoClient {
    private static final Logger logger = Logger.getLogger(GrpcDemoClient.class.getName());

    private final ManagedChannel channel;

    private final GreeterGrpc.GreeterBlockingStub blockingStub;

    public GrpcDemoClient(String host, int port) {
        this(ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext()
                .build());
    }

    GrpcDemoClient(ManagedChannel channel) {
        this.channel = channel;
        blockingStub = GreeterGrpc.newBlockingStub(channel);
    }

    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    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());
    }

    public static void main(String[] args) throws InterruptedException {
        GrpcDemoClient client = new GrpcDemoClient("localhost", 50051);
        try {
            String user = "world";
            if (args.length > 0) {
                user = args[0];
            }
            client.greet(user);
        } finally {
            client.shutdown();
        }
    }
}

這兩個類的代碼也是從grpc-java的example中拷貝過來的。

五.測試

執行GrpcDemoServer的main方法,控制檯輸出:

執行GrpcDemoClient的main方法,控制檯輸出

調用成功。

六.總結

本文根據grpc-java的源代碼中的示例,新建項目實現了基於gRPC的遠程調用。可以看到,因爲需要跨語言,所以gRPC需要藉助protobuf這種獨立的接口定義語言。定義好之後,通過不同語言的插件,可以生成對應語言的輔助類,通過輔助類可以實現遠程調用。gRPC的優點是跨語言,通過http2進行傳輸,擴展性強,有一定的前瞻性。確定是目前官方沒有自己的線程池,沒有負載均衡,也沒有很成熟的服務治理方案,需要使用者自己實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章