专栏首页云原生可观测性gRPC 简介并实战——文末附源码

gRPC 简介并实战——文末附源码

1. 介绍

gRPC 是一个高性能的开源 RPC 框架,最初由 Google 开发。

RPC 是什么?在客户端应用里可以像调用本地方法对象一样直接调用另一台不同机器上的服务端应用的方法。同时支持跨语言的异构系统。国内开源的 RPC 框架有阿里Dubbo、蚂蚁金服的 SOFA-RPC、百度 bRPC、新浪 Motan等等。

废话不多说,直接就开始使用 gRPC。文末附源码链接。

2. 概述

本文将使用以下步骤使用 gRPC 创建典型的C/S服务:

  1. 首先在 .proto 文件中定义服务: gRPC 使用 protobuf 作为 IDL,明确定义了参数及类型。
  2. 通过 protobuf 编译器自动生成客户端-服务端通信 Stub 的代码。
  3. 创建服务器端的程序,并对 stub 进行实现。
  4. 创建客户端应用程序,使用生成的 stub 进行 RPC 调用服务端方法。

整个流程如图所示,并描述了不同语言的系统远程调用的方式:

我们先来定义一个简单的 HelloService 服务,它返回问候语和姓名。

3. Maven 依赖

这里添加 grpc-netty , grpc-protobufgrpc-stub 三个依赖:

<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-netty</artifactId>
    <version>1.16.1</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-protobuf</artifactId>
    <version>1.16.1</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-stub</artifactId>
    <version>1.16.1</version>
</dependency>

4.定义 .proto 文件

创建 HelloService.proto 文件,并开始如下定义:

syntax = "proto3"; // 告诉编译器此文件使用什么版本的语法,而且默认情况下,编译器在单个 Java 文件中生成所有 Java 代码。
option java_multiple_files = true; // 可选配置,再次告诉编译器所有内容都将在单个文件中生成。
package org.baeldung.grpc; //最后,我们指定要用于生成的 Java 类的包。


// 定义消息结构体,相当于 Http Request
// 并对每个属性都定义数据类型,需要为每个属性分配一个唯一编号,称为标记。此标记由 protobuf 用于表示属性,而不是使用属性名称。
// 因此,与 JSON 不同,我们每次都会传递属性名称 name,而 protobuf 将使用数字 1 来表示 name。
message HelloRequest {
    string firstName = 1;
    string lastName = 2;
}

// 定义消息结构体,相当于 Http Response
// 这里的响应体定义与 `HelloRequest` 类似。需要注意的是,我们可以在多个消息类型之间使用相同的标记:
message HelloResponse {
    string greeting = 1;
}

// 最后,让我们定义服务(method)。对于我们的 HelloService,我这里定义了一个 hello() 操作:单次接受一个请求并返回一个响应
service HelloService {
    rpc hello(HelloRequest) returns (HelloResponse);
}

另外要指出的是,gRPC 还支持通过将 stream 关键字来标示是否进行流式处理。也就是说,客户端和服务端交互有四种情况,客户端流式/非流式——服务端流式/非流式。

5. 生成代码

现在,我们需要将 HelloService.proto 文件传递 protobuf 编译器 protoc 来生成 Java 文件。有多种方法可以触发此功能。

  • 如果采用 protoc 编译器 的方式,这也是最简单的一种方式:

首先你需要下载编译器:https://developers.google.com/protocol-buffers/docs/downloads,并通过如下命令来将 HelloService.proto 生成 Java 文件:

protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/HelloService.proto
  • 当然,你也可以用 Maven 插件的方式:

gRPC 提供了 protobuf-maven-plugin, 在Maven 中添加如下配置:

<build>
  <extensions>
    <extension>
      <groupId>kr.motd.maven</groupId>
      <artifactId>os-maven-plugin</artifactId>
      <version>1.6.1</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.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>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>compile-custom</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

上面的 os-maven 插件可以生成各种与平台相关的属性。

6. 创建服务端程序

不过无论您使用上面哪种方法生成代码,都将生成以下关键文件:

  • HelloRequest.java 文件, 包含 HelloRequest 请求类型定义
  • HelloResponse.java 文件,它包 HelloResponse 响应类型定义
  • HelloServiceImplBase.java 文件,它包含抽象类 HelloServiceImplBase,它提供了我们在服务接口中定义的所有操作的实现

6.1 重写抽象类 HelloServiceImplBase的实现

抽象类 HelloServiceImplBase 的默认实现是抛出运行时异常 io.grpc.StatusRuntimeException,用于指出该方法未被实现。

让我们来扩展此类并重写服务定义中提到的 hello() 方法:

public class HelloServiceImpl extends HelloServiceImplBase {
 
    @Override
    public void hello(
      HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
 
        String greeting = new StringBuilder()
          .append("Hello, ")
          .append(request.getFirstName())
          .append(" ")
          .append(request.getLastName())
          .toString();
 
        HelloResponse response = HelloResponse.newBuilder()
          .setGreeting(greeting)
          .build();
 
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

如果我们将 hello() 的签名与我们在 HellService.proto 文件中写入的签名进行比较,我们会注意到它不会返回 HelloResponse。相反,它将第二个参数称为 StreamObserver,它是 响应观察者,是服务器调用其响应的回调。正如最开始提到的那样,客户端将获得进行阻塞调用或非阻塞调用(流式)的选项。

gRPC 使用生成器(builder)创建对象。我们使用 HelloResponse.newBuilder() 并设置"hello" 问候语以生成 HelloResponse 对象。我们将此对象设置为响应观察者的 onNext()方法,将其发送到客户端。

最后,我们需要调用 "onCompleted()" 来指定我们已完成对这次 RPC 的处理,否则连接将挂起,客户端将等待更多信息进来。

6.2 运行服务端程序

接下来,我们需要启动 gRPC 服务器来监听传入的请求:

public class GrpcServer {
    public static void main(String[] args) {
        Server server = ServerBuilder
          .forPort(8080)
          .addService(new HelloServiceImpl()).build();
 
        server.start();
        server.awaitTermination();
    }
}

在这里,我们再次使用生成器(ServerBuilder) 在端口 8080 上创建一个 gRPC 服务器,并添加我们定义的 HelloServiceImpl 服务。start() 将启动服务器。在我们的示例中,我们将调用 awaittermination() 以保持服务器在后台保持运行。

创建客户端程序

gRPC 提供了一个通道构造,用于抽象基础详细信息,如连接、连接池、负载平衡等。

public class GrpcClient {
    public static void main(String[] args) {
        // 这里使用 `ManagedChannelBuilder` 创建通道,并指定需要连接的服务器地址和端口。
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
          .usePlaintext() //这里将使用纯文本,无需任何加密
          .build();
 
        /** 接下来,我们需要创建一个 Stub,我们将用它来进行实际的调用远程的 hello() 方法。Stub(存根)是客户端与服务器交互的主要方式。使用自动生成Stub时,Stub 类包含了用于包装通道(channel)的构造函数。
        **/
        HelloServiceGrpc.HelloServiceBlockingStub stub 
          = HelloServiceGrpc.newBlockingStub(channel);
 
        // 是时候进行 hello() RPC 调用了。在这里,我们传递 Hello 请求。我们可以使用newBuilder 来设置 HelloRequest 对象的姓、名属性。并得到从服务器返回的 HelloResponse 对象。
        HelloResponse helloResponse = stub.hello(HelloRequest.newBuilder()
          .setFirstName("test")
          .setLastName("gRPC")
          .build());
 
        channel.shutdown();
    }
}

需要说明的是,上面使用阻塞/同步的存根(newBlockingStub),以便 RPC 调用等待服务器响应,并将返回响应或引发异常。有两种类型的存根由 gRPC 提供,另外一种便于非阻塞/异步调用。

8. 总结

在本文中,介绍了如何使用 gRPC 来简化两个服务之间的通信开发,与此同时,我们可以更加专注地定义服务以及更加专注的实现我们的业务逻辑。

最后,Github 源码链接:https://github.com/JaredTan95/grpc-demo

本文分享自微信公众号 - 一万小时极客(coding-Hub),作者:JaredTan95

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-02-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Kibana:Canvas 大屏幕显示

    Elastic Visulization 提供了强大的可视化工具供我们来展示及分析数据,但是 Elastic Canvas可以给我们提供大屏幕显示的方式,并提供...

    Jared.Tan
  • 2w 字 + 40 张图带你参透并发编程!

    在计算机最早期的时候,没有操作系统,执行程序只需要一种方式,那就是从头到尾依次执行。任何资源都会为这个程序服务,在计算机使用某些资源时,其他资源就会空闲,就会存...

    Jared.Tan
  • ELK 之 Logstash 的安装与导入数据

    上一节主要介绍了数据可视化工具 Kibana 工具的使用,不过并没有过多的介绍怎么大量的导入数据。

    Jared.Tan
  • C++11基础学习系列一

    ---- 概述 C++11标准越来越趋于稳定和成熟,国外c++11如火如荼而国内却依然处于观望期。每当提到C++很多程序员都很抵触,特别是学术界的呼声更高一些。...

    BrianLv
  • 一份为高中生准备的机器学习与人工智能入门指南

    翻译 | AI科技大本营 参与 | 林椿眄 作为一名高中生,我在过去的一年里自学了机器学习与人工智能的相关课程,在这里和大家分享下我自己的学习心得,希望能够对那...

    AI科技大本营
  • 每天玩转3分钟 MyBatis-Plus - 1. 配置环境

    MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

    悟空聊架构
  • Dubbo框架的Hello World

    最近写基于 Dubbo 的 Demo,本来挺简单的一个 Demo 但是整了两个小时,而最后解决的方法是因为包名的问题。可能很多所谓的经验不过就是一个踩坑的经验。

    码农UP2U
  • java练习本(2019-06-25)

    “The dream crossed twilight between birth and dying.”

    微笑的小小刀
  • 线程池 -- 动态链接库

    链接:https://pan.baidu.com/s/1Y0JutBYsMlwmSjoLcTlkSw 提取码:j9hn

    看、未来
  • ​2019 DevOps 技术指南

    我们在推进国内研发团队 DevOps 落地的过程中,发现不少研发组织在积极寻求 DevOps 技能方面的提升。今天翻译的这篇深受欢迎的 DevOps 技术雷达来...

    CODING

扫码关注云+社区

领取腾讯云代金券