初识gRPC——Java

本文参考文章:
https://blog.csdn.net/sunsun314/article/details/73780169
https://www.cnblogs.com/mzsg/p/5643367.html
https://grpc.io/docs/tutorials/basic/java.html
http://doc.oschina.net/grpc?t=60134

如果你不了解gRPC的话,可以先看一下这篇文章

这些文章都有相应的代码实现,这里我就不重复劳动了。
本文主要以gRPC官方例子中的RouteGuide示例为准,总结一下我粗浅的理解。这篇例子的文章也就是gRPC Basics - Java
代码地址:https://github.com/grpc/grpc-java/tree/master/examples/src/main/java/io/grpc/examples/routeguide

那么首先,我们来看如何实现一个能够运行的gRPC服务。

构建gRPC的步骤

  1. 在pom文件中添加相关依赖及插件
<properties>
    <grpc.version>1.4.0</grpc.version><!-- CURRENT_GRPC_VERSION -->
</properties>
<dependencies>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-netty</artifactId>
        <version>${grpc.version}</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-protobuf</artifactId>
        <version>${grpc.version}</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-stub</artifactId>
        <version>${grpc.version}</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-testing</artifactId>
        <version>${grpc.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>1.9.5</version>
        <scope>test</scope>
    </dependency>
</dependencies>
<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.4.1.Final</version>
        </extension>
    </extensions>
    <plugins>
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.5.0</version>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:3.3.0: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>
    </plugins>
</build>
  1. 接着需要在src/main/proto文件夹下创建.proto文件,之后使用命令mvn install,可以在target包中找到两个生成文件,一个是Proto文件,是生成的数据结构javaBean,其中包含input和output;另一个是接口方法类ServiceGrpc这类的,这个类提供接口方法的调用。

  2. 我们将这两个文件复制到对应java代码目录中,接下来需要新建3个类(也可以是两个):接口方法类的实现类Impl;server类;client类。

到这里,一个gRPC服务的构建就完成了。

由于第二步中的文件是通过插件自动生成的,所以我们可操作的部分就是在第三步了,接下来就看一下我们如何实现这3个类。

相关代码的实现

  1. Impl实现类中主要就是继承ServiceGrpc接口方法类中的一个基础实现类ServiceImplBase,并实现我们在.proto文件中定义的那些方法。也就是说,该类就是我们的业务逻辑类。
    如:
package com.mzsg.demo.grpc.qryaccount;

import io.grpc.stub.StreamObserver;

/**
 * @Auther: yubt
 * @Description:
 * @Date: Created in 10:24 2018/12/24
 * @Modified By:
 */
public class QryAccountServiceImpl extends QryAccountServiceGrpc.QryAccountServiceImplBase {

    @Override
    public void qry(com.mzsg.demo.grpc.qryaccount.QryAccountProto.AccountQryRequest request, StreamObserver<QryAccountProto.AccountQryResponse> responseObserver) {
        System.out.println("qry " + request.getUserId());
        QryAccountProto.AccountQryResponse response = QryAccountProto.AccountQryResponse.newBuilder().setRc(1).setAmount(666).build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

  1. server类主要实现一个start()方法,能够将server启动,需要通过ServerBuilder来新建出对应的server,并且,在build server的时候,需要加上对应的端口及相应的服务,而这个服务就是我们刚刚实现的impl实现类。
    如:
package com.mzsg.demo.grpc.qryaccount;

import java.io.IOException;

/**
 * @Auther: yubt
 * @Description:
 * @Date: Created in 10:30 2018/12/24
 * @Modified By:
 */
public class Server {
    private static int port = 8848;
    private static io.grpc.Server server;
	// 就是我在上面说的start()方法,只是命名不同
    public void run(){
        QryAccountServiceGrpc.QryAccountServiceImplBase modifyAccountServiceImpl = new QryAccountServiceImpl();
        server = io.grpc.ServerBuilder.forPort(port).addService(modifyAccountServiceImpl).build();
        try {
            server.start();
            System.out.println("Server start success on port:" + port);
            server.awaitTermination();
        }catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.run();
    }
}

  1. client方法主要就是调用相关方法了。它需要根据.proto文件中定义的request去新建对应的request,之后build channel,再根据channel新建stub,之后通过stub调用相应的方法(接口中的)并等待回应response,这个response也是.proto文件中定义的。
    如:
package com.mzsg.demo.grpc.qryaccount;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * @Auther: yubt
 * @Description:
 * @Date: Created in 10:35 2018/12/24
 * @Modified By:
 */
public class Client {
    public static void main(String[] args) throws FileNotFoundException, IOException {
        QryAccountProto.AccountQryRequest request = QryAccountProto.AccountQryRequest.newBuilder().setUserId("20012").setRequestId("123").build();
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8848).usePlaintext(true).build();
        QryAccountServiceGrpc.QryAccountServiceBlockingStub stub = QryAccountServiceGrpc.newBlockingStub(channel);
        for (int j = 0; j < 20; j++) {
            long start = System.currentTimeMillis();
            for(int i=0; i<1000; i++){
                QryAccountProto.AccountQryResponse rsp = stub.qry(request);
//                System.out.println(rsp);
            }
            System.out.println(System.currentTimeMillis() - start + " MS");
        }
    }
}

从上述代码中,我们可以比较直观的了解到各个类的功能以及应该实现什么内容。

不过,上述例子过于简单,当然非常直观。下面我们就看一下复杂的实现。也就是官网的例子RouteGuide。

我们先看.proto文件中的定义

syntax = "proto3";

option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";

package routeguide;

// Interface exported by the server.
service RouteGuide {
  rpc GetFeature(Point) returns (Feature) {}
  rpc ListFeatures(Rectangle) returns (stream Feature) {}
  rpc RecordRoute(stream Point) returns (RouteSummary) {}
  rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

message Rectangle {
  Point lo = 1;
  Point hi = 2;
}

message Feature {
  string name = 1;
  Point location = 2;
}

message FeatureDatabase {
  repeated Feature feature = 1;
}

message RouteNote {
  Point location = 1;
  string message = 2;
}

message RouteSummary {
  int32 point_count = 1;
  int32 feature_count = 2;
  int32 distance = 3;
  int32 elapsed_time = 4;
}

首先我们看Server中的实现,因为例子中将server和impl写到一起了,我们拆开来看。

server的代码

package io.grpc.examples.routeguide;

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

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @Auther: yubt
 * @Description:
 * @Date: Created in 14:27 2018/12/24
 * @Modified By:
 */
public class RouteGuideServer {
    private static final Logger logger = Logger.getLogger(RouteGuideServer.class.getName());

    private final int port;
    private final Server server;

    public RouteGuideServer(int port) throws IOException {
        this(port, RouteGuideUtil.getDefaultFeaturesFile());
    }

    public RouteGuideServer(int port, URL featureFile) throws IOException {
        this(ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile));
    }

    public RouteGuideServer(ServerBuilder<?> serverBuilder, int port, Collection<RouteGuideProto.Feature> features) {
        this.port = port;
        server = serverBuilder.addService(new RouteGuideService(features))
                .build();
    }

    public void start() throws IOException {
        server.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");
                RouteGuideServer.this.stop();
                System.err.println("*** server shut down");
            }
        });
    }

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

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

    public static void main(String[] args) throws Exception{
        RouteGuideServer server = new RouteGuideServer(8980);
        server.start();
        server.blockUntilShutdown();
    }
}

可以看到核心内容还是构建server,实现start()方法,以及阻塞方法。和我们上面的说法基本一致。

impl实现类,我们将内容精简。

private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
        private final Collection<RouteGuideProto.Feature> features;
        private final ConcurrentMap<RouteGuideProto.Point, List<RouteGuideProto.RouteNote>> routeNotes =
                new ConcurrentHashMap<RouteGuideProto.Point, List<RouteGuideProto.RouteNote>>();

        RouteGuideService(Collection<RouteGuideProto.Feature> features) {
            this.features = features;
        }

        @Override
        public void getFeature(RouteGuideProto.Point request, StreamObserver<RouteGuideProto.Feature> responseObserver) {
            responseObserver.onNext(checkFeature(request));
            responseObserver.onCompleted();
        }

        @Override
        public void listFeatures(RouteGuideProto.Rectangle request, StreamObserver<RouteGuideProto.Feature> responseObserver) {
            ...
        }

        @Override
        public StreamObserver<RouteGuideProto.Point> recordRoute(final StreamObserver<RouteGuideProto.RouteSummary> responseObserver) {
            ...
        }

        @Override
        public StreamObserver<RouteGuideProto.RouteNote> routeChat(final StreamObserver<RouteGuideProto.RouteNote> responseObserver){
            ...
        }

		// 其他辅助方法
        ...
    }

我们可以看到impl类的核心功能还是实现我们在.proto文件中定义的接口。

// Interface exported by the server.
service RouteGuide {
  rpc GetFeature(Point) returns (Feature) {}
  rpc ListFeatures(Rectangle) returns (stream Feature) {}
  rpc RecordRoute(stream Point) returns (RouteSummary) {}
  rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}

我们再看client类的实现,代码也做相应的精简

package io.grpc.examples.routeguide;

import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.Message;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @Auther: yubt
 * @Description:
 * @Date: Created in 15:10 2018/12/24
 * @Modified By:
 */
public class RouteGuideClient {
    private static final Logger logger = Logger.getLogger(RouteGuideClient.class.getName());

    private final ManagedChannel channel;
    private final RouteGuideGrpc.RouteGuideBlockingStub blockingStub;
    private final RouteGuideGrpc.RouteGuideStub asyncStub;

    private Random random = new Random();
    private TestHelper testHelper;

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

    public RouteGuideClient(ManagedChannelBuilder<?> channelBuilder){
        channel = channelBuilder.build();
        blockingStub = RouteGuideGrpc.newBlockingStub(channel);
        asyncStub = RouteGuideGrpc.newStub(channel);
    }

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

	// 该方法主要用于构造request及处理response,调用了server中的相关方法,而server中又使用的impl中的实现方法
    public void getFeature(int lat, int lon){
        ...
    }
	// 该方法主要用于构造request及处理response,同上
    public void listFeatures(int lowLat, int lowLon, int hiLat, int hiLon){
       ...
    }
	// 该方法主要用于构造request及处理response,同上
    public void recordRoute(List<RouteGuideProto.Feature> features, int numPoints) throws InterruptedException {
        ...
    }
	// 该方法主要用于构造request及处理response,同上
    public CountDownLatch routeChat(){
        ...
    }

	// 进行了相关方法的调用
    public static void main(String[] args) throws InterruptedException{
        List<RouteGuideProto.Feature> features;
        try {
            features = RouteGuideUtil.parseFeatures(RouteGuideUtil.getDefaultFeaturesFile());
        } catch (IOException ex) {
            ex.printStackTrace();
            return;
        }

        RouteGuideClient client = new RouteGuideClient("localhost", 8980);
        try {
            // Looking for a valid feature
            client.getFeature(409146138, -746188906);

            // Feature missing.
            client.getFeature(0, 0);

            // Looking for features between 40, -75 and 42, -73.
            client.listFeatures(400000000, -750000000, 420000000, -730000000);

            // Record a few randomly selected points from the features file.
            client.recordRoute(features, 10);

            // Send and receive some notes.
            CountDownLatch finishLatch = client.routeChat();

            if (!finishLatch.await(1, TimeUnit.MINUTES)) {
                client.warning("routeChat can not finish within 1 minutes");
            }
        } finally {
            client.shutdown();
        }
    }

	// 相关辅助方法等
    ...
}

将代码精简后,我们可以看到这个类的功能一目了然。

不过,当我们重新审视省略的代码时,就会有新的发现。

当我们使用到流式请求/响应时,我们需要实现对应的流式处理方法,包括处理request及response。

当我们在实现impl类时,会实现相应的流式请求/响应,此时会用到一个接口StreamObserver,该接口有三个方法onNext()、onError()、onCompleted().

当我们在返回流式响应数据的时候,我们需要新建对应的StreamObserver,并实现其对应的相关方法逻辑。以供客户端调用,并处理对应的流式响应。

举一例如下:
client端构建的routeChat()方法中的request

StreamObserver<RouteGuideProto.RouteNote> requestObserver = asyncStub.routeChat(new StreamObserver<RouteGuideProto.RouteNote>() {
            @Override
            public void onNext(RouteGuideProto.RouteNote routeNote) {
                info("Got message \"{0}\" at {1}, {2}", routeNote.getMessage(), routeNote.getLocation()
                        .getLatitude(), routeNote.getLocation().getLongitude());
                if (testHelper != null) {
                    testHelper.onMessage(routeNote);
                }
            }

            @Override
            public void onError(Throwable throwable) {
                warning("RouteChat Failed: {0}", Status.fromThrowable(throwable));
                if (testHelper != null) {
                    testHelper.onRpcError(throwable);
                }
                finishLatch.countDown();
            }

            @Override
            public void onCompleted() {
                info("Finished RouteChat");
                finishLatch.countDown();
            }
        });

而我们在server端是这么处理这个request的:

@Override
        public StreamObserver<RouteGuideProto.RouteNote> routeChat(final StreamObserver<RouteGuideProto.RouteNote> responseObserver){
            return new StreamObserver<RouteGuideProto.RouteNote>() {
                @Override
                public void onNext(RouteGuideProto.RouteNote routeNote) {
                    List<RouteGuideProto.RouteNote> notes = getOrCreateNotes(routeNote.getLocation());

                    for (RouteGuideProto.RouteNote prevNote : notes.toArray(new RouteGuideProto.RouteNote[0])){
                        responseObserver.onNext(prevNote);
                    }

                    notes.add(routeNote);
                }

                @Override
                public void onError(Throwable throwable) {
                    logger.log(Level.WARNING, "routeChat cancelled");
                }

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

代码中的responseObserver就是我们client中的requestObserver。可以看到当我们需要返回对应的流式响应时,我们也需要实现StreamObserver接口,并在对应方法中使用另一端实现的对应方法。

grpc与http的性能比较

之后我使用了这篇文章中的代码来测试。

http使用httpclient包进行测试,代码如下:

package com.yubotao.http;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;

/**
 * @Auther: yubt
 * @Description:
 * @Date: Created in 15:05 2018/12/25
 * @Modified By:
 */
public class TestWithHttpClient {
    public static void main(String[] args) {
        try {
            for (int j = 0; j < 20; j++) {

                long start = System.currentTimeMillis();
                for (int i=0; i<1000; i++) {
                    HttpClient client = HttpClientBuilder.create().build();
                    HttpGet get = new HttpGet("http://localhost:8081/qry/20010");
                    HttpResponse res = client.execute(get);
                }
                System.out.println(System.currentTimeMillis() - start + " MS");
            }

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

@RequestMapping(value = "/qry/{userId}", method = RequestMethod.GET)
    public Response qry(@PathVariable String userId){
        System.out.println("qry " + userId);
        Response response = new Response();
        response.setRc("1");
        response.setAmount("666");
        return response;
    }

    private class Response {
        private String rc;
        private String amount;

        public String getRc() {
            return rc;
        }

        public void setRc(String rc) {
            this.rc = rc;
        }

        public String getAmount() {
            return amount;
        }

        public void setAmount(String amount) {
            this.amount = amount;
        }

        @Override
        public String toString() {
            return "Response{" +
                    "rc='" + rc + '\'' +
                    ", amount='" + amount + '\'' +
                    '}';
        }
    }

请求单次时
意外的发现,http性能要略优于grpc。
grpc
在这里插入图片描述

http
在这里插入图片描述

在只测试方法执行时间上,http优于grpc,测试完整请求(包括构建client,执行方法,打印结果等等),http优于grpc。

多次重复请求时
20组测试数据,每组请求1000次方法。

grpc如图:
在这里插入图片描述

由于http每次请求都需要重新建立连接,所以十分耗时。如下图
在这里插入图片描述

还是能够非常明显的看出,http请求和grpc请求的差距的,主要还是因为http需要每次都重新建立连接,但是grpc由于是基于http2的,所以可以保持连接。


经过上面的测试,可以得到相应的结论了,就是grpc关于大量的重复请求,确实是比http节省时间,因为省去了建立连接的时间;但是如果针对单点请求来说,httpclient的性能比grpc略强。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值