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进行传输,扩展性强,有一定的前瞻性。确定是目前官方没有自己的线程池,没有负载均衡,也没有很成熟的服务治理方案,需要使用者自己实现。

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