前情提要

之前咱们聊过SpringBoot下开启多端口有3个思路,并分析了第一种开启独立management端口的实现细节,今天咱们聊聊开启gRPC端口的实现。


一、gRPC的特别之处

基于SpringBoot的微服务,其通信大多基于HTTP+JSON。前者为通信协议,后者为数据内容格式。更进一步说,这HTTP大多也是HTTP/1.1,随着HTTP协议的发展HTTP/2, HTTP/3也已经出来了。此外, 数据格式也未必都是JSON, XML、Protobuf、Thrift, Avro等结构化格式都是可行的。gRPC使用的默认是HTTP/2+Protobuf,其特别之处主要在HTTP/2与HTTP/1.1的区别。

HTTP/1.1HTTP/2
内容编码文本编码二进制编码, 相同信息比文本编码报文更短
网络请求方式在同一链接上,多个请求依次发送和接收在同一连接上,多个情况可以流水线发送

此外, HTTP/2还新增了header压缩和服务端push等特性。由此可见,gRPC依赖的HTTP/2与HTTP/1的有截然不同的要求, 在实际使用中也要专门处理,其中有独立的HTTP/2相关的协议编解码,应用层的消息编解码等。不过,这些也都被gRPC框架给屏蔽了。关于gRPC本身的实现细节在此不做讨论,我们关注在与SpringBoot的集成。

二、粗暴方案

手动创建相关对象,暴露到Spring容器中。

原始的GrpcObservabilityServer

此处为官网原始的GrpcObservabilityServer

public class GcpObservabilityServer {
  private Server server;

  private void start() throws IOException {
    int port = 50051;
    server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
        .addService(new GreeterImpl())
        .build()
        .start();
    logger.info("Server started, listening on " + port);
  }

  private void stop() throws InterruptedException {
    if (server != null) {
      server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
    }
  }

  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 GcpObservabilityServer server = new GcpObservabilityServer();
    server.start();

    Runtime.getRuntime().addShutdownHook(new Thread() {
      @Override
      public void run() {
        System.err.println("*** shutting down gRPC server since JVM is shutting down");
        try {
          server.stop();
        } catch (InterruptedException e) {
          e.printStackTrace(System.err);
        }
        // Shut down observability
        observability.close();
        System.err.println("*** server shut down");
      }
    });

    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();
    }
  }
}

集成支持

@SpringBootApplication
public class Starter {
    public static void main(String[] args) {
        SpringApplication.run(Starter.class, args);
    }

    @Bean
    public Server newGrpcServer() {
        final GcpObservabilityServer server = new GcpObservabilityServer();
        server.start();
        // 最好再实现DisposableBean,当容器关闭时,关闭掉server实例。此处仅为演示
    }
}

方案评价

这个玩法实在过于暴力:

  1. 配置与代码耦合:端口指定完全可以依赖配置文件;
  2. 纯手工组装(容易出错):GreeterImpl如果依赖容器中的其他类,无法自动组装,同样的问题Server对象的组装也是存在的;
  3. 扩展性上来说,每新增一个grpcService都要记得在server端注册;

三、改进方案

基于grpc-spring-boot-starter,Maven依赖声明如下

<dependency>
    <groupId>io.github.lognet</groupId>
    <artifactId>grpc-spring-boot-starter</artifactId>
    <version>4.2.2</version>
</dependency>

基本原理

  1. 通过SpringBoot的AutoConfiguration机制创建Bean GrpcServerRunner;
  2. GrpcServerRunner implements CommandLineRunner接口, 容器初始化完成后执行;
  3. 扫描容器中所有带@GrpcService注解并且类型为BindableService的Bean, 加入到ServerBuilder中;
  4. 结合最终GrpcServerProperties对象, 构造server并启动;
    到这里整个Application就默认开放两个端口, 默认的HTTP/1.1端口和GRPC使用的HTTP/2.0端口。

改造结果

  1. 实际的service增加注解
// 使当前类被容器自动装配,后续就可以由Spring容器注入依赖对象
@Component
// 被GrpcRunner识别,组装GrpcServer用
@GrpcService
public 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();
    }
  }
  1. 增加必要的配置
grpc:
  port: 8092
  netty-server:
    max-inbound-message-size: 10485760
  1. 保证GreeterImpl会被正确扫描到即可;

  2. 测试用环境说明
    SpringBoot 2.7.2
    Grpc 1.33.0

四、小结

本文介绍了SpringBoot下开启gRPC端口的两种方案,前者仅由容器管理声明周期,后者由框架负责对象的配置,生成和生命周期,开发者可以更多关注自身业务。实际工作中如何处理,还请读者“量体裁衣”,感谢你的阅读。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部