• RPC框架之gRPC简单使用


    前言

    gRPC是一个谷歌推出的高性能的RPC框架,遵从server/client模型,可以使client调用server的接口就像调用本地方法一样简单,gRPC使用ProtoBuf来定义数据类型和接口,相比RestAPI,传输数据更少,性能更高。

    下载proto编译器

    不仅需要下载protoc编译器,使用gRPC还需要下载grpc-protoc插件(Java需要),这里是windows版本。

    定义proto文件

    syntax = "proto3";
    
    package tutorial;
    
    option java_package = "com.imooc.sourcecode.java.google.grpc.test1";
    option java_outer_classname = "PersonProto";
    option java_generic_services = true;
    
    // The greeting service definition.
    service Greeter {
      // Sends a greeting
      rpc SayHello (HelloRequest) returns (HelloReply) {}
      // Sends another greeting
      rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
    }
    
    // The request message containing the user's name.
    message HelloRequest {
      string name = 1;
    }
    
    // The response message containing the greetings
    message HelloReply {
      string message = 1;
    }
    

    例子来源于官方文档
    service 用来定义服务接口,可以看做Java中的接口。

    使用Java编写服务器端和客户端代码

    添加maven依赖

    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-netty-shaded</artifactId>
      <version>1.45.1</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-protobuf</artifactId>
      <version>1.45.1</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-stub</artifactId>
      <version>1.45.1</version>
    </dependency>
    <dependency> <!-- necessary for Java 9+ -->
      <groupId>org.apache.tomcat</groupId>
      <artifactId>annotations-api</artifactId>
      <version>6.0.53</version>
      <scope>provided</scope>
    </dependency>
    

    根据proto文件创建Java数据类型和接口代码

    有两种方式,一种是使用命令行,另一种是使用maven插件

    命令行

    protoc --proto_path=$SRC_DIR --plugin=$P_NAME=$P_DIR --grpc-java_out=$DST_DIR --java_out=$DST_DIR person.proto
    

    --proto_path(也可以简写为-I)表示查找proto文件的路径,
    --plugin表示要添加的执行插件,格式为name(插件名称)=dir(路径),
    --grpc-java_out表示生成服务接口的路径,
    --java_out表示生成数据类型的路径,
    实际命令为

    .\protoc.exe --proto_path=D:\java\code_resp\github_resp\source_code\src\main\java\com\imooc\sourcecode\java\google\grpc\test1 --plugin=protoc-gen-grpc-java=D:\java\software\protoc-3.20.0-win64\bin\protoc-gen-grpc-java-1.4.0-windows-x86_64.exe --grpc-java_out=D:\java\code_resp\github_resp\source_code\src\main\java --java_out=D:\java\code_resp\github_resp\source_code\src\main\java person.proto
    

    --grpc-java_out和--java_out值不要包含具体的包路径,proto文件中已经配置了。

    maven插件

    <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:1.4.0:exe:${os.detected.classifier}</pluginArtifact>
            <clearOutputDirectory>false</clearOutputDirectory>
            <protoSourceRoot>${basedir}/src/main/java/com/imooc/sourcecode/java/google/grpc/test1</protoSourceRoot>
            <outputDirectory>${basedir}/src/main/java</outputDirectory>
          </configuration>
          <executions>
            <execution>
              <goals>
                <goal>compile</goal>
                <goal>compile-custom</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
    

    参考官方文档,自定义了三个属性,
    clearOutputDirectory表示代码生成前是否清除输出目录,默认为true。
    protoSourceRoot属性表示查找proto文件的路径,默认路径为${basedir}/src/main/proto。
    outputDirectory属性表示生成文件的路径,不需要包含具体的包路径,proto文件中已经配置了,默认路径为${project.build.directory}/generated-sources/protobuf/java。
    插件的更多属性可以查看Maven Protocol Buffers Plugin

    protobuf:compile任务会生成HelloProto.java文件,protobuf:compile-custom任务会生成GreeterGrpc.java文件,插件底层也是通过命令行方式来实现的。

    服务器端代码

    import com.imooc.sourcecode.java.google.grpc.test1.HelloProto.HelloReply;
    import com.imooc.sourcecode.java.google.grpc.test1.HelloProto.HelloRequest;
    import io.grpc.Server;
    import io.grpc.ServerBuilder;
    import io.grpc.stub.StreamObserver;
    import java.io.IOException;
    import java.util.concurrent.TimeUnit;
    import java.util.logging.Logger;
    
    /**
     * Server that manages startup/shutdown of a {@code Greeter} server.
     */
    public class HelloWorldServer {
    
      private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
    
      private Server server;
    
      private void start() throws IOException {
        /* The port on which the server should run */
        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() {
            // Use stderr here since the logger may have been reset by its JVM shutdown hook.
            System.err.println("*** shutting down gRPC server since JVM is shutting down");
            try {
              HelloWorldServer.this.stop();
            } catch (InterruptedException e) {
              e.printStackTrace(System.err);
            }
            System.err.println("*** server shut down");
          }
        });
      }
    
      private void stop() throws InterruptedException {
        if (server != null) {
          server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
        }
      }
    
      /**
       * Await termination on the main thread since the grpc library uses daemon threads.
       */
      private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
          server.awaitTermination();
        }
      }
    
      /**
       * Main launches the server from the command line.
       */
      public static void main(String[] args) throws IOException, InterruptedException {
        final HelloWorldServer server = new HelloWorldServer();
        server.start();
        server.blockUntilShutdown();
      }
    
      static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
    
        @Override
        public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
          HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
          responseObserver.onNext(reply);
          responseObserver.onCompleted();
        }
    
        @Override
        public void sayHelloAgain(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
          HelloReply reply = HelloReply.newBuilder().setMessage("Hello again " + req.getName()).build();
          responseObserver.onNext(reply);
          responseObserver.onCompleted();
        }
      }
    }
    

    核心在于GreeterImpl实现类,处理客户端的请求。

    客户端代码

    import com.imooc.sourcecode.java.google.grpc.test1.HelloProto.HelloReply;
    import com.imooc.sourcecode.java.google.grpc.test1.HelloProto.HelloRequest;
    import io.grpc.Channel;
    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;
    
    /**
     * A simple client that requests a greeting from the {@link HelloWorldServer}.
     */
    public class HelloWorldClient {
    
      private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
    
      private final GreeterGrpc.GreeterBlockingStub blockingStub;
    
      /**
       * Construct client for accessing HelloWorld server using the existing channel.
       */
      public HelloWorldClient(Channel channel) {
        // 'channel' here is a Channel, not a ManagedChannel, so it is not this code's responsibility to
        // shut it down.
    
        // Passing Channels to code makes code easier to test and makes it easier to reuse Channels.
        blockingStub = GreeterGrpc.newBlockingStub(channel);
      }
    
      /**
       * Say hello to server.
       */
      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());
      }
    
      /**
       * Greet server. If provided, the first element of {@code args} is the name to use in the
       * greeting. The second argument is the target server.
       */
      public static void main(String[] args) throws Exception {
        String user = "world";
        // Access a service running on the local machine on port 50051
        String target = "localhost:50051";
        // Allow passing in the user and target strings as command line arguments
        if (args.length > 0) {
          if ("--help".equals(args[0])) {
            System.err.println("Usage: [name [target]]");
            System.err.println("");
            System.err.println("  name    The name you wish to be greeted by. Defaults to " + user);
            System.err.println("  target  The server to connect to. Defaults to " + target);
            System.exit(1);
          }
          user = args[0];
        }
        if (args.length > 1) {
          target = args[1];
        }
    
        // Create a communication channel to the server, known as a Channel. Channels are thread-safe
        // and reusable. It is common to create channels at the beginning of your application and reuse
        // them until the application shuts down.
        ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
            // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
            // needing certificates.
            .usePlaintext()
            .build();
        try {
          HelloWorldClient client = new HelloWorldClient(channel);
          client.greet(user);
        } finally {
          // ManagedChannels use resources like threads and TCP connections. To prevent leaking these
          // resources the channel should be shut down when it will no longer be used. If it may be used
          // again leave it running.
          channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }
      }
    }
    

    客户端和服务器端代码都来自grpc-java

    编写Python服务器端和客户端代码

    安装依赖

    pip install grpcio
    pip install grpcio-tools
    

    PyCharm中可以这样安装

    根据proto文件创建Python数据类型和接口代码

    python -m grpc_tools.protoc -I=$SRC_DIR --python_out=$DST_DIR --grpc_python_out=$DST_DIR person.proto
    

    -m表示以脚本的方式运行python模块。
    -I表示查找proto文件的路径,
    --grpc_python_out表示生成服务接口代码的路径,
    --python_out表示生成数据类型代码的路径,
    实际命令为

    .\python.exe -m grpc_tools.protoc -I=D:\java\code_resp\PycharmProjects\test_protobuf\grpc --python_out=D:\java\code_resp\PycharmProjects\test_protobuf\grpc --grpc_python_out=D:\java\code_resp\PycharmProjects\test_protobuf\grpc person.proto
    

    服务器端代码

    from concurrent import futures
    import logging
    
    import grpc
    import person_pb2
    import person_pb2_grpc
    
    
    class Greeter(person_pb2_grpc.GreeterServicer):
    
        def SayHello(self, request, context):
            return person_pb2.HelloReply(message='Hello, %s!' % request.name)
    
    
    def serve():
        server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
        person_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
        server.add_insecure_port('[::]:50051')
        server.start()
        server.wait_for_termination()
    
    
    if __name__ == '__main__':
        logging.basicConfig()
        serve()
    

    核心在于Greeter类,处理来自客户端的请求。

    客户端代码

    from __future__ import print_function
    
    import logging
    
    import grpc
    import person_pb2
    import person_pb2_grpc
    
    
    def run():
        # NOTE(gRPC Python Team): .close() is possible on a channel and should be
        # used in circumstances in which the with statement does not fit the needs
        # of the code.
        with grpc.insecure_channel('localhost:50051') as channel:
            stub = person_pb2_grpc.GreeterStub(channel)
            response = stub.SayHello(person_pb2.HelloRequest(name='you'))
        print("Greeter client received: " + response.message)
    
    
    if __name__ == '__main__':
        logging.basicConfig()
        run()
    

    客户端和服务器端代码都来自grpc

    总结

    通过上述测试,我们可以发现,gRPC可以跨语言通信,如Java作为服务器端,Python作为客户端。更多gRPC相关用法,请查看官方文档

    参考

    gRPC-官方文档
    如何给老婆解释什么是RPC
    使用maven插件生成grpc所需要的Java代码
    java protobuf 生成grpc 代码
    gRPC详解

  • 相关阅读:
    CoreJava Reading Note(9:Collection)
    CoreJava Reading Note(8:Generic programming)
    Algorithms 4th Reading Note(3:Find)
    CoreJava Reading Note(7:Exception,Assertions,Logging)
    Algorithms 4th Reading Note(1:Foundation)
    CoreJava Reading Note(6:Interface,lambda and Inner Class)
    Algorithms 4th Reading Note(2:Sort)
    CoreJava Reading Note(5:Inheritance)
    SpringMVC spring-servlet.xml配置
    MySQL 数据库事物隔离级别的设置
  • 原文地址:https://www.cnblogs.com/strongmore/p/16131477.html
Copyright © 2020-2023  润新知