一键搞懂grpc,Java实现对应的服务

发布于:2024-05-23 ⋅ 阅读:(156) ⋅ 点赞:(0)

1. RPC 概述

服务间通讯HTTP 肯定不失为一种有效的方法,但此处要提起的却是另一种方法:RPC。

RPC 的英文全称是 Remote Procedure Call,即远程过程调用,简单点说,就是服务器A可以像函数调用一样调用服务器B上的方法。

其原理大致如下:

其组成结构大致如下:

可以看出,RPC 底层依然使用 TCP 等运输层协议进行网络通信,它跟 HTTP 较为相似,但比 HTTP 更便捷的是,它还封装了序列化与反序列化的功能。

或许,说到此处,还会有人疑惑:其实 HTTP 外加一层序列化与反序列化的封装后,也能实现 RPC 的功能,为何还需要使用 RPC 呢?

其实,HTTP 与 RPC 各有千秋。甚至在 gRPC 中,其底层就是 HTTP2 协议。所以,并没有孰优孰劣,只是可能 RPC 更加适用于服务与服务之间的通信,不需要 HTTP1 协议中多余的头部,序列化与反序列化的功能也更加完善,而且封装程度高、即拿即用

注意:另外,换一种说法,RPC 与 HTTP 并不在同一层面上,并不能直接比较。毕竟 HTTP 是一个应用层协议,而 RPC 更像一种通信机制、一种框架,它内部可能使用自己编写的应用层协议,也可能就是 HTTP 协议。文中的 HTTP 与 RPC 比较,可以看成是 Restful API 与 RPC 的比较

2、生成交互文件

2.1 命令方式

获取protoc软件。用于处理proto文件的工具软件,对proto文件生成消息对象和序列化及反序列化的Java实体类。下载地址:Central Repository: com/google/protobuf/protoc

获取protoc-gen-grpc-java插件。用于处理rpc定义的插件,生成针对rpc定义的Java接口。下载地址:Central Repository: io/grpc/protoc-gen-grpc-java

命令:

#单个命令:
protoc.exe --java_out=. proto/hello.proto
protoc --plugin=protoc-gen-grpc-java=./protoc-gen-grpc-java-1.32.1-windows-x86_64.exe --grpc-java_out=.   --proto_path=.   ./proto/hello.proto
# 合并之后命令
protoc --plugin=protoc-gen-grpc-java=./protoc-gen-grpc-java-1.42.1-windows-x86_64.exe --grpc-java_out=.  --java_out=.   --proto_path=.   ./hello.proto

2.2 Maven方式

配置pom.xml

  <build>
    <extensions>
      <extension>
        <groupId>kr.motd.maven</groupId>
        <artifactId>os-maven-plugin</artifactId>
        <version>1.6.2</version>
      </extension>
    </extensions>
    <plugins>
      <plugin>
        <groupId>org.xolstice.maven.plugins</groupId>
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>0.6.1</version>
        <configuration>
          <protocArtifact>com.google.protobuf:protoc:3.18.1:exe:${os.detected.classifier}</protocArtifact>
          <pluginId>grpc-java</pluginId>
          <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.42.1:exe:${os.detected.classifier}</pluginArtifact>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>compile-custom</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      
    </plugins>

刷新pom之后可以看到

或者直接执行命令

mvn protobuf:compile

mvn protobuf:compile-custom

最终归于本质还是maven下载一应的protoc和protoc-gen-grpc-java 到本地进行生成。

在target中就会自动生成了对应的文件,将它们移动到对应的源目录底下即可。

3、代码

3.1 Server

Greeter是上述定义的接口,GreeterGrpc.GreeterImplBase就是我们要继承的父类了,RPC生成的。重写上述接口

返回一个字符串拼接请求的名称,在OnNext中返回消息体,返回onCompleted成功

public class GreeterImpl extends GreeterGrpc.GreeterImplBase {

  @Override
  public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
    System.err.println("receive a request: "+ request.toString());
    HelloReply reply = HelloReply.newBuilder().setMessage("hello," + request.getName()).build();
    responseObserver.onNext(reply);
    responseObserver.onCompleted();
  }
}

服务器启动

public class HelloWorldServer {

  private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
  private static final int port = 50051;
  private Server server;

  private void start() throws IOException {
    // (1)
    server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
        .addService(new GreeterImpl())
        .build()
        .start();
    logger.info("Server started, listening on " + port);
    // (2)
    Runtime.getRuntime().addShutdownHook(
        new Thread(() -> {
          System.out.println("shutting down gRpc server since JVM is shutting down");
          try {
            HelloWorldServer.this.stop();
          } catch (InterruptedException e) {
            e.printStackTrace(System.err);
          }
          System.err.println("shutting down gRpc server since JVM is shutting down");
        })
    );
  }
  public void stop() throws InterruptedException {
    if (server != null) {
      server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
    }
  }

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

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

3.2 Client

client则更为简单,我们说rpc就像调用本地方法一样简单

public class HelloWorldClient {

  private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
  private final GreeterGrpc.GreeterBlockingStub blockingStub;


  private HelloWorldClient(Channel channel) {
    // (2)
    this.blockingStub = GreeterGrpc.newBlockingStub(channel);
  }

  public void greet(String name) {
    logger.info("will try to greet " + name + "...");
    // (3)
    HelloRequest request = HelloRequest.newBuilder().setName(name).build();
    HelloReply helloReply = blockingStub.sayHello(request);
    System.out.println("=== response ===");
    System.out.println(helloReply.toString());
    logger.info("greet finished" + name + "...");
  }

  public static void main(String[] args) throws InterruptedException {
    String host = "localhost";
    int port = 50051;
    // (1)
    ManagedChannel managedChannel =
        Grpc.newChannelBuilderForAddress(host, port, InsecureChannelCredentials.create()).build();
    try {
        // (2)
      HelloWorldClient helloWorldClient = new HelloWorldClient(managedChannel);
      logger.info("hello, I am LagLang");
      helloWorldClient.greet("LagLang");
    } finally {
      managedChannel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
    }
  }
}

从main方法开始看起

(1) ManagedChannel 构造一个和server端连接的通道,指明了host和端口,以及安全协议

(2) 构造 Client,client有一个GreeterBlockingStub,通过chennel来构造,这是Greeter的Service存根,通过存根可以进行rpc方法调用

(3) 构造request,完成实际的rpc调用

4、调试工具

这里就显示本地有的工具ApiPost7

导入protobuf

测试对应的接口

5、总结

GRPC底层使用protobuf做数据载体,使用TCP进行通信

一个完整的远程调用框架,在学习的过程中对代码生成走了一些迷惑