gRPC 的一些实践

2019-01-22  本文已影响86人  专职跑龙套

gRPC 是啥

A high performance, open-source universal RPC framework
一款高性能的开源 RPC 框架。

gRPC 的特点:

基本思想

参考:https://grpc.io/docs/guides/

关于 Protocol Buffers

什么是 Protocol Buffers,请参考:https://developers.google.com/protocol-buffers/docs/overview

Protocol buffers are a flexible, efficient, automated mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.
Protocol buffers 是一个序列化结构化数据的方法,类似于 XML,但是比 XML 更节省空间,更快,更简单。你首先通过一种语法定义好你想要的数据结构,然后可以编译成不同语言对应的源代码(例如 Java 中的实体类),随后你就可以通过这些源代码来进行数据的读写。

Protocol Buffers 与 XML 的比较:

gRPC 与 Protocol Buffers

gRPC 默认使用 Protocol Buffers 来:

我们可以创建一个 .proto 后缀名的文件来表示一个结构化数据,例如:

message Person {
  string name = 1;
  int32 id = 2;
  bool has_ponycopter = 3;
}

随后可以将这个 .proto 文件编译成不同语言对应的源代码,例如编译成 Java 中的 Person.class 类,这个类里提供了 get set 方法。

gRPC 的基本概念

在 gRPC 中可以定义四种类型的服务方法:

rpc SayHello(HelloRequest) returns (HelloResponse){
}
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}

关于同步调用 Vs 异步调用

关于 gRPC 的超时 Timeout

关于 gRPC 调用的中断 termination
服务端和客户端都可以随时中断调用。

关于 gRPC 调用的取消 cancel
服务端和客户端都可以随时取消调用。

Java gRPC 快速入门

参考:https://grpc.io/docs/quickstart/java.html
gRPC Java API 文档:https://grpc.io/grpc-java/javadoc/

// 从 Github 上获取示例代码
git clone -b v1.18.0 https://github.com/grpc/grpc-java

// 进入示例代码目录
cd grpc-java/examples

// 编译服务端和客户端
./gradlew installDist

// 启动服务端
./build/install/examples/bin/hello-world-server

// 启动客户端
./build/install/examples/bin/hello-world-client
启动服务端 启动客户端

我们看看示例中的几个重要文件。
首先是 /examples/src/main/proto/helloworld.proto,在这里我们通过 protocol buffers 来定义了 gRPC 所提供的服务,也就是 Services。可以看出,服务名称为 Greeter,它提供了一个 RPC,名称为 SayHello,其中请求输入为 HelloRequest,包含一个字符串 name,请求响应为 HelloReply,包含一个字符串 message

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (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;
}

随后我们通过 protoc 编译器来进行编译,这里使用的是 proto3 版本。protoc 编译器可以作为插件集成到主流的 Java 构建工具中,例如 Gradle 和 Maven。

这个文件在编译过后,会产生对应的字节码文件,也就是产生了对应的类,位置在 /examples/build/classes/java/main/io/grpc/examples/helloworld

下面来看 RPC 服务是如何被提供的,文件位置 /examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java。可以看出:

package io.grpc.examples.helloworld;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
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");
        HelloWorldServer.this.stop();
        System.err.println("*** server shut down");
      }
    });
  }

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

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

下面来看 RPC 服务是如何被调用的,文件位置 /examples/src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java。可以看出:

package io.grpc.examples.helloworld;

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 ManagedChannel channel;
  private final GreeterGrpc.GreeterBlockingStub blockingStub;

  /** Construct client connecting to HelloWorld server at {@code host:port}. */
  public HelloWorldClient(String host, int port) {
    this(ManagedChannelBuilder.forAddress(host, port)
        // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
        // needing certificates.
        .usePlaintext()
        .build());
  }

  /** Construct client for accessing HelloWorld server using the existing channel. */
  HelloWorldClient(ManagedChannel channel) {
    this.channel = channel;
    blockingStub = GreeterGrpc.newBlockingStub(channel);
  }

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

  /** 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.
   */
  public static void main(String[] args) throws Exception {
    HelloWorldClient client = new HelloWorldClient("localhost", 50051);
    try {
      /* Access a service running on the local machine on port 50051 */
      String user = "world";
      if (args.length > 0) {
        user = args[0]; /* Use the arg as the name to greet if provided */
      }
      client.greet(user);
    } finally {
      client.shutdown();
    }
  }
}
上一篇 下一篇

猜你喜欢

热点阅读