• gRPC三种客户端类型实践【Java版】


    本文承袭Grpc服务开发和接口测试初探【Java】内容,学会了基本的gRPC的基本Demo之后,自然要开始了各类客户端的学习。由于服务端的代码都是由开发写好的,所以作为新手测试来说,我觉得学好客户端的代码优先级更高一些。

    书接上文,gRPC客户端有三种实现方式,其实就是从io.grpc.ManagedChannel创建客户端Stub的过程。三种方式分别为:newBlockingStubnewStubnewFutureStub。下面通过代码演示来分享三种的区别和优劣。

    gRPC客户端目前用起来跟HTTP协议一样,调用方式跟HttpClient调用一样。分成了阻塞、异步和future,有兴趣可以移步HTTP异步连接池和多线程实践

    服务端

    服务端是上期进行改造,主要是增加了响应等待时间和时间信息,方便后面验证不同客户端功能。代码如下:

    package com.funtester.grpc;
    
    import com.funtester.frame.SourceCode;
    import com.funtester.fungrpc.HelloRequest;
    import com.funtester.fungrpc.HelloResponse;
    import com.funtester.fungrpc.HelloServiceGrpc;
    import com.funtester.utils.Time;
    import io.grpc.stub.StreamObserver;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    
    public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
    
        private static final Logger logger = LogManager.getLogger(HelloServiceImpl.class);
    
        @Override
        public void executeHi(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
            HelloResponse response = HelloResponse.newBuilder()
                    .setMsg("你好 " + request.getName()+ Time.getDate())
                    .build();
            SourceCode.sleep(1.0);
            logger.info("用户{}来了",request.getName());
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        }
    
    }
    
    

    newBlockingStub

    顾名思义,这个是阻塞调用的gRPC客户端类型,实际使用中跟HTTP接口请求->响应一样,代码如下:

    package com.funtest.grpc
    
    import com.funtester.frame.SourceCode
    import com.funtester.fungrpc.HelloRequest
    import com.funtester.fungrpc.HelloResponse
    import com.funtester.fungrpc.HelloServiceGrpc
    import io.grpc.ManagedChannel
    import io.grpc.ManagedChannelBuilder
    
    import java.util.concurrent.ExecutionException
    
    class BlockClient extends SourceCode {
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 8080)
                    .usePlaintext()
                    .build()
    
            HelloServiceGrpc.HelloServiceBlockingStub helloServiceBlockingStub = HelloServiceGrpc.newBlockingStub(managedChannel).withCompression("gzip")
    
            HelloRequest helloRequest = HelloRequest.newBuilder()
                    .setName("FunTester")
                    .build()
            5.times {
                HelloResponse orderResponse = helloServiceBlockingStub.executeHi(helloRequest)
                output("收到响应: " + orderResponse.getMsg())
            }
            managedChannel.shutdown()
        }
    
    }
    
    

    控制台输出:

    20:46:04.664 main 当前用户:oker,工作目录:/Users/oker/IdeaProjects/funtester/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
    20:46:04.675 main 
      ###### #     #  #    # ####### ######  #####  ####### ######  #####
      #      #     #  ##   #    #    #       #         #    #       #    #
      ####   #     #  # #  #    #    ####    #####     #    ####    #####
      #      #     #  #  # #    #    #            #    #    #       #   #
      #       #####   #    #    #    ######  #####     #    ######  #    #
    
    20:46:06.517 main 收到响应: 你好 FunTester2022-05-09 20:46:05
    20:46:07.521 main 收到响应: 你好 FunTester2022-05-09 20:46:06
    20:46:08.531 main 收到响应: 你好 FunTester2022-05-09 20:46:07
    20:46:09.542 main 收到响应: 你好 FunTester2022-05-09 20:46:08
    20:46:10.552 main 收到响应: 你好 FunTester2022-05-09 20:46:09
    
    进程已结束,退出代码0
    

    比较简单,这里不多做介绍了。

    newStub

    看名字有点猜不出来,这是个纯异步调用客户端。写上去代码可能比较多,但是如果把io.grpc.stub.StreamObserver对象拆开看就会比较容易懂一些。代码如下:

    package com.funtest.grpc
    
    import com.funtester.frame.SourceCode
    import com.funtester.fungrpc.HelloRequest
    import com.funtester.fungrpc.HelloResponse
    import com.funtester.fungrpc.HelloServiceGrpc
    import io.grpc.ManagedChannel
    import io.grpc.ManagedChannelBuilder
    import io.grpc.stub.StreamObserver
    
    import java.util.concurrent.ExecutionException
    
    class SyncClient extends SourceCode {
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 8080)
                    .usePlaintext()
                    .build()
    
            HelloServiceGrpc.HelloServiceStub helloServiceStub = HelloServiceGrpc.newStub(managedChannel).withCompression("gzip")
    
            HelloRequest helloRequest = HelloRequest.newBuilder()
                    .setName("FunTester")
                    .build()
    
    
            StreamObserver<HelloResponse> helloResponseStreamObserver = new StreamObserver<HelloResponse>() {
    
                @Override
                void onNext(HelloResponse value) {
                    output(value.getMsg())
                }
    
                @Override
                void onError(Throwable t) {
                    output(t.getMessage())
                }
    
                @Override
                void onCompleted() {
    
                }
            }
            5.times {helloServiceStub.executeHi(helloRequest, helloResponseStreamObserver)}
            sleep(2000)
            managedChannel.shutdown()
        }
    
    }
    
    

    由于都是异步,所以相当于自动多线程了。控制台输出如下:

    
    20:50:59.053 main 
      ###### #     #  #    # ####### ######  #####  ####### ######  #####
      #      #     #  ##   #    #    #       #         #    #       #    #
      ####   #     #  # #  #    #    ####    #####     #    ####    #####
      #      #     #  #  # #    #    #            #    #    #       #   #
      #       #####   #    #    #    ######  #####     #    ######  #    #
    
    20:51:00.816 grpc-default-executor-4 你好 FunTester2022-05-09 20:50:59
    20:51:00.816 grpc-default-executor-1 你好 FunTester2022-05-09 20:50:59
    20:51:00.816 grpc-default-executor-3 你好 FunTester2022-05-09 20:50:59
    20:51:00.816 grpc-default-executor-0 你好 FunTester2022-05-09 20:50:59
    20:51:00.816 grpc-default-executor-2 你好 FunTester2022-05-09 20:50:59
    
    进程已结束,退出代码0
    

    可以看到,所有请求响应的结果时间都是一样的,说明请求到达服务端时间是一样的。

    newFutureStub

    这种客户端也是异步的,之所以放在最后将是因为它具有同步客户端的属性,在实际使用中,既可以当做异步客户端使用也可以当做一个同步的客户端使用。下面是演示代码:

    package com.funtest.grpc
    
    import com.funtester.frame.SourceCode
    import com.funtester.fungrpc.HelloRequest
    import com.funtester.fungrpc.HelloResponse
    import com.funtester.fungrpc.HelloServiceGrpc
    import com.google.common.util.concurrent.ListenableFuture
    import io.grpc.ManagedChannel
    import io.grpc.ManagedChannelBuilder
    
    import java.util.concurrent.ExecutionException
    
    class FutureClient extends SourceCode {
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 8080)
                    .usePlaintext()
                    .build()
    
            HelloServiceGrpc.HelloServiceFutureStub helloServiceFutureStub = HelloServiceGrpc.newFutureStub(managedChannel).withCompression("gzip")
            HelloRequest helloRequest = HelloRequest.newBuilder()
                    .setName("FunTester")
                    .build()
            //同步客户端的使用方式
            5.times {
                ListenableFuture<HelloResponse> helloResponseListenableFuture = helloServiceFutureStub.executeHi(helloRequest)
                HelloResponse helloResponse = helloResponseListenableFuture.get()
                output(helloResponse.getMsg())
            }
            //异步客户端的使用方式
            def res = []
            5.times {
                ListenableFuture<HelloResponse> helloResponseListenableFuture = helloServiceFutureStub.executeHi(helloRequest)
                res << helloResponseListenableFuture
            }
    
            res.each {
                output(it.get().getMsg())
            }
    
            managedChannel.shutdown()
        }
    
    }
    
    

    控制台输出:

    21:03:07.312 main 
      ###### #     #  #    # ####### ######  #####  ####### ######  #####
      #      #     #  ##   #    #    #       #         #    #       #    #
      ####   #     #  # #  #    #    ####    #####     #    ####    #####
      #      #     #  #  # #    #    #            #    #    #       #   #
      #       #####   #    #    #    ######  #####     #    ######  #    #
    
    21:03:09.226 main 你好 FunTester2022-05-09 21:03:08
    21:03:10.232 main 你好 FunTester2022-05-09 21:03:09
    21:03:11.238 main 你好 FunTester2022-05-09 21:03:10
    21:03:12.247 main 你好 FunTester2022-05-09 21:03:11
    21:03:13.255 main 你好 FunTester2022-05-09 21:03:12
    21:03:14.262 main 你好 FunTester2022-05-09 21:03:13
    21:03:14.281 main 你好 FunTester2022-05-09 21:03:13
    21:03:14.281 main 你好 FunTester2022-05-09 21:03:13
    21:03:14.282 main 你好 FunTester2022-05-09 21:03:13
    21:03:14.282 main 你好 FunTester2022-05-09 21:03:13
    
    进程已结束,退出代码0
    
    

    可以看到,前面五个请求是串行的,后面的五个请求是并行的。在实际工作中,使用到异步调用又要处理结果的地方也是这种类型使用较多,而使用Java的线程同步类,往往比较麻烦也不够优雅。

    Have Fun ~ Tester !

  • 相关阅读:
    springboot定时任务框架Quartz
    Linux中安装Erlang
    prometheus常用函数详解
    Prometheus+Grafana+SpringBoot业务埋点可视化监控
    Prometheus+Grafana可视化监控SpringBoot项目
    prometheus的数据类型介绍
    DS:顺序栈
    DS:顺序队列
    Linux:06进程
    primer5:chap09顺序容器
  • 原文地址:https://www.cnblogs.com/FunTester/p/16257429.html
Copyright © 2020-2023  润新知