gRPC 的一些实践

gRPC 是啥

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

gRPC 的特点:

  • 简单的服务定义:使用 Protocol Buffers(做序列化的工具)来定义服务
  • 跨语言跨平台:可以自动生成不同语言对应的 Client Stubs 和 Server Stubs。如下图所示,服务端可以用 C++ 来实现,但是客户端来说,可以提供 Ruby 的版本和 Java 的版本。
    跨语言跨平台
  • 入手简单,并且可扩展
  • 双向数据流

基本思想

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

  • 通过一种方式来定义服务 Service 及这个服务下面包含的方法,同时定义这些方法需要的参数类型和返回值类型,这些方法就是远程调用的对象
  • 在服务提供者这里,需要实现上面定义好的接口,运行 gRPC Server 来处理调用请求
  • 在服务调用者这里,通过使用 Client Stub 来调用上面定义好的方法

关于 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 的比较:

  • are simpler 更简单
  • are 3 to 10 times smaller 更节省空间
  • are 20 to 100 times faster 更快
  • are less ambiguous
  • generate data access classes that are easier to use programmatically

gRPC 与 Protocol Buffers

gRPC 默认使用 Protocol Buffers 来:

  • 作为接口定义语言(Interface Definition Language)来定义服务 Service
  • 定义传输过程中的数据结构

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

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

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

gRPC 的基本概念

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

  • 一元 RPC(Unary RPCs),客户端发送简单请求,得到简单响应,类似于一个函数调用:
rpc SayHello(HelloRequest) returns (HelloResponse){
}
  • 服务端流式 RPC(Server streaming RPCs),客户端发送请求,得到一个流 Stream,然后从 Stream 读取内容:
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}
  • 客户端流式 RPC(Client streaming RPCs),客户端通过流 Stream 来写入内容,然后发送给服务端,最后得到响应:
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
  • 双向流式 RPC(Bidirectional streaming RPCs),上面两种方式的结合体:
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}

关于同步调用 Vs 异步调用

  • 同步 RPC 会阻塞客户端的当前线程,直到获得了响应
  • 异步 RPC 不会阻塞客户端的当前线程

关于 gRPC 的超时 Timeout

  • 客户端可以定超时 Timeout。
  • 服务端可以知道某一个 RPC Call 是否已超时。

关于 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

  • java_outer_classname = "HelloWorldProto" 编译后会产生 HelloWorldProto.class

  • message HelloRequest 编译后会产生 HelloRequest.class 及对应的几个内部类,代表了 gRPC 请求的实体。可以通过它们来构造请求并获取请求中的内容。

    HelloRequest.class 及对应的几个内部类

  • message HelloReply 编译后会产生 HelloReply.class 及对应的几个内部类,代表了 gRPC 响应的实体。可以通过它们来构造响应并获取响应中的内容。

    `HelloReply.class 及对应的几个内部类

  • service Greeter 编译后会产生 GreeterRrpc.class 及对应的几个内部类,代表了服务的实体

    • 注意:这个 GreeterRrpc 类及对应的几个内部类在下面都会用到,作为 Client Stubs 和 Server Stubs
    • GreeterGrpc.GreeterImplBase 里面有 sayHello() 方法,可以作为 Server Stubs
    • GreeterGrpc.GreeterBlockingStub 里面有 sayHello() 方法,可以作为 Client Stubs
      GreeterRrpc.class 及对应的几个内部类

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

  • 服务端通过重载 GreeterGrpc.GreeterImplBase 里的 sayHello() 方法来提供服务的具体实现
  • 通过 ServerBuilder 来启动 gRPC server 来处理请求
  • gRPC 框架负责帮我们解码请求(将请求内容转换为 HelloRequest 类),执行服务方法,编码响应(将 HelloReply 类转换成 Protocol Buffers 对应的格式)
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。可以看出:

  • 通过指定主机和端口来创建 gRPC channel 管道,代表对 gRPC server 的连接
  • 客户端通过 GreeterGrpc.newBlockingStub(channel) 来得到客户端调用的 Stub,然后再调用具体的方法
  • gRPC 框架负责帮我们编码请求,发送请求,得到响应,解码相应
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();
    }
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • GRPC是基于protocol buffers3.0协议的. 本文将向您介绍gRPC和protocol buffe...
    二月_春风阅读 17,919评论 2 28
  • gRPC是由Google主导开发的RPC框架,使用HTTP/2协议并用ProtoBuf作为序列化工具。其客户端提供...
    CZ_Golang阅读 82,052评论 9 71
  • 一.Grpc简介 一个2016年才由google正式发布的的RPC框架,基于http2,protobuf协议 官网...
    我也是玄冲阅读 8,577评论 0 2
  • 1)简介 gRPC负载平衡的主要实现机制是外部负载平衡,即通过外部负载平衡器来向客户端提供更新后的服务器列表。 g...
    Jay_Guo阅读 13,060评论 6 22
  • JACK,手划过这个名字时,妮妮才发现,心里还是会隐隐作痛。 在那次培训会前,妮妮看见满满当当的人却没几个认识的时...
    妮妮魔豆阅读 498评论 0 1