gRPC(Java) 基础教程

gRPC

什么是gRPC?

  • gRPC是由Google公司开源的高性能RPC框架。
  • gRPC支持多语言

gRPC原生使用C、Java、Go进行了三种实现,而C语言实现的版本进行封装后又支持了C++、C#、Node...

  • gRPC支持多平台

支持的平台包括:Linux、Android、IOS、MacOS、Windows

  • gRPC的消息协议使用Google自家开源的Protocol Buffers协议机制(proto3)
  • gRPC的传输使用HTTP/2标准,支持双向流和连接多路复用

使用方法

  1. 使用Protocol Buffers (proto3) 的 IDL 接口定义语言定义接口服务,编写在文本文件(以.proto为后缀名)中。
  2. 使用 protobuf 编译器生成服务器和客户端使用的 stub 代码
  3. 编写补充服务器和客户端逻辑代码

HTTP/2

gRPC的传输是基于HTTP/2标准协议实现的,前面提到的支持双向流和多路复用,实际就是HTTP/2的特性。而且gRPC有四种接口类型,也是依赖HTTP/2协议建立起来的,所以我们有必要先来了解一下HTTP/2协议。

HTTP/1.x

HTTP/1.x 是一个文本协议,可读性很好,但是效率不高

HTTP/1.x 的解析很复杂,

HTTP/1.x 交互模式是一个连接每次只能一问一答,也就是请求之后必须要等到响应才能进行下一次请求

HTTP/1.x 没有服务器推送功能,只能采取轮询的方式。

HTTP/2

HTTP/2 协议是一个二进制协议,这也就意味着它的可读性为0,但是效率高

这里有三个比较重要的概念,分别是数据流、消息和帧

  • 数据流 Stream: 已建立的连接内的双向字节流,可以承载一条或多条消息。
  • 消息 Message: 与逻辑请求或响应消息对应的完整一系列帧
  • 帧 Frame: HTTP/2 通信的最小单位,每个帧都包含帧头,至少也会标识出当前帧所属的数据流

这里就不展开说了,可以自己找资料看一下

gRPC接口类型

gRPC有四种接口类型:

  • Unary RPC (一元RPC) 一个请求一个响应
  • Server Streaming RPC (服务端流式RPC) 一个请求多个响应
  • Client Streaming RPC (客户端流式RPC) 多个请求一个响应
  • Bidrectional Streaming RPC (双向流式RPC) 多个请求多个响应

这些都是数据流 Stream,我们所说的接口类型是指进行一次gRPC调用的数据通讯流程(或数据流Stream的生命周期)

Protocol Buffers

Protocol Buffers 是一种与语言无关,平台无关的可扩展机制,用于序列化结构化数据。使用 Protocol Buffers 可以一次定义结构化的数据,然后可以使用特殊生成的源代码轻松地在各种数据流中使用各种编程语言编写和读取结构化数据

文档结构

Protocol Buffers 文档的版本声明

protobuf 复制代码
syntax = "proto3";

Protocol Buffers 可以声明 package,来防止命名冲突。

使用的时候通过 包名.xxx 使用

protobuf 复制代码
package foo.bar;

中可以导入其他文件消息等

protobuf 复制代码
import "myproject/other_protos.proto";

注释

arduino 复制代码
// 单行注释
/* 多行注释 */

与Java相关的语法

protobuf 复制代码
    // protobuf 生成的代码是一个源文件还是多个源文件
    option java_multiple_files = false;
    // 指定 protobuf 生成的类,放置在哪个包中
    option java_package = "com.basepackage";
    // 外层类的名字
    option java_outer_classname = "xxx.java";

数据类型

  1. 基本数据类型
Proto Type Java/Kotlin Type[1]
double double
float float
int32 int
int64 long
uint32 int[2]
uint64 long[2]
sint32 int
sint64 long
fixed32 int[2]
fixed64 long[2]
sfixed32 int
sfixed64 long
bool boolean
string String
bytes ByteString
  1. 枚举

在 proto3 中,枚举定义中定义的第一个值必须 具有 零,枚举值不能重复,枚举值不能重复,除非使用 option allow_alias = true;来开启别名

枚举器常量必须在 32 位整数的范围内。

protobuf 复制代码
    enum Corpus {
      option allow_alias = true;
      CORPUS_UNSPECIFIED = 0;
      CORPUS_UNIVERSAL = 1;
      CORPUS_WEB = 2;
      CORPUS_IMAGES = 3;
      CORPUS_LOCAL = 4;
      CORPUS_NEWS = 5;
      CORPUS_PRODUCTS = 6;
      CORPUS_VIDEO = 7;
    }
  1. 消息

Protocol Buffers 使用 message 定义消息数据,在 Protocol Buffers 中使用的数据都是通过 message 消息数据封装基本数据类型或其他消息数据

编号不能使用 [19000-19999],这个范围是Protocol Buffers预留的

消息可以嵌套,也可以在消息内部声明消息

protobuf 复制代码
    message SearchRequest {
      string query = 1;// 等号后面是字段的编号
      int32 page_number = 2;
      int32 results_per_page = 3;
      repeated message = 4;
    }
  • singular : 修饰 message 中的字段,这个字段的值只能是0个或一个(默认关键字)
  • repeated : 修饰 message 中的字段,这个字段的值是多个,等价于 java 中的 List 集合

消息中可以指定保留字段

保留变量不被使用

这些字段的编号、标识符被预留下来,不能直接使用

protobuf 复制代码
    message Foo {
        reserved 2,15,9 to 11;
        reserved "foo","bar";
    }
  1. 映射

如果要创建关联映射作为数据定义的一部分, Protocol Buffers 提供了一个方便的快捷语法:

ini 复制代码
map<key_type, value_type> map_field = N;
  1. oneof

其中一个,选择其中一种字段作为类型

protobuf 复制代码
    message SampleMessage {
      oneof test_oneof {
        string name = 4;
        SubMessage sub_message = 9;
      }
    }

定义服务

可以定义多个方法,定义的方法都是以 rpc 开头,前面有提到四种接口类型,其中一元rpc就是不在请求消息和响应消息加stream,其他的就是加上对应的stream

protobuf 复制代码
    service SearchService {
      rpc Search(SearchRequest) returns (SearchResponse){}
    }
    service SearchService {
      rpc 方法名([stream] 请求消息类型) returns ([stream] 响应消息类型){}
    }

安装编译器

直接去官网下载

Releases · protocolbuffers/protobuf

我的版本是 29.0

下载解压到你的目录即可

然后把bin目录添加到path环境变量

通过protoc --version验证是否成功

项目开发

创建api模块

编写.proto文件

执行 protoc --java_out=/xxx/xxx /xxx/xx.proto 命令第一个参数是生成代码的文件目录,后面的是proto文件位置

或者使用 maven 插件编译,一下是需要导入的依赖和插件

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>1.71.0</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.71.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.71.0</version>
        </dependency>
        <dependency> <!-- necessary for Java 9+ -->
            <groupId>org.apache.tomcat</groupId>
            <artifactId>annotations-api</artifactId>
            <version>6.0.53</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.7.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.25.5:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.71.0:exe:${os.detected.classifier}</pluginArtifact>
                        生成代码的目录-->
                    <outputDirectory>${basedir}/src/main/java</outputDirectory>
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

分别执行这两个命令就可以自动生成出来代码

HelloProto:对应数据

HelloProtoGrpc:对应服务

创建service模块

导入上面创建的api模块的依赖

xml 复制代码
    <dependency>
        <groupId>com.huang.grpc</groupId>
        <artifactId>grpc-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

创建一个类实现我们定义的服务接口

java 复制代码
    // 集成服务实现  xxxxImplBase
    public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase{
        /**
         * 实现我们定义的服务接口
         * @param request 客户端发过来的请求参数
         * @param responseObserver 返回值
         */
        @Override
        public void sayHello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {
            // 接受客户端的请求参数并打印
            System.out.println("receive request: " + request.getName());
            // 封装响应参数
            HelloProto.HelloResponse response = HelloProto.HelloResponse.newBuilder()
                    .setResult("hello " + request.getName())//调用对应的set方法设置参数
                    .build();
            // 发送响应
            responseObserver.onNext(response);
            // 通知客户端发送完成
            responseObserver.onCompleted();
        }
    }

创建服务端

java 复制代码
    public class GrpcService {
        public static void main(String[] args) {
            ServerBuilder<?> serverBuilder = ServerBuilder.forPort(9000);
            // 添加服务
            serverBuilder.addService(new HelloServiceImpl());
            // 创建服务对象
            Server server = serverBuilder.build();
            try {
                // 启动服务
                server.start();
                // 阻塞主线程
                server.awaitTermination();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

客户端

创建客户端模块

导入上面创建的api模块

xml 复制代码
    <dependency>
        <groupId>com.huang.grpc</groupId>
        <artifactId>grpc-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

编写客户端程序

我这里为了看起来简洁没有关闭资源,你可以自己在finally的代码块中关闭资源channel.shutdown()

java 复制代码
    public class GrpcClient {
        public static void main(String[] args) {
            // 创建通信管道
            ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build();
            // 获得代理对象 stub
            HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel);
            // 准备参数
            HelloProto.HelloRequest request = HelloProto.HelloRequest.newBuilder()
                    .setName("huang")
                    .build();
            // 发送请求
            HelloProto.HelloResponse helloResponse = stub.sayHello(request);
            // 答应获取的结果
            System.out.println(helloResponse.getResult());
        }
    }

这里是给出的一元RPC的案例,上面我们说了有四种接口类型大家可以自己去了解一下其他接口类型的实现方法,代码大体上是差不多的

相关推荐
晚椰子树34 分钟前
Scala的数据类型
开发语言·后端·scala
安然无虞1 小时前
31天Python入门——第13天:文件操作
开发语言·后端·爬虫·python
Bruce_Liuxiaowei7 小时前
基于Flask的智能天气助手系统设计
后端·python·flask
时光不负追梦人9 小时前
谈谈对spring IOC的理解,原理和实现
java·后端·spring
Asthenia041210 小时前
面试场景题:设计微信朋友圈后端-从接口到数据库的实现
后端
南屿欣风10 小时前
Go 语言中使用 Swagger 生成 API 文档及常见问题解决
开发语言·后端·golang
Asthenia041210 小时前
场景题:设计微信的双向好友添加/建群逻辑
后端
Asthenia041210 小时前
外键是个啥?为什么我要加它?——从面试复盘聊聊数据库设计
后端
程序饲养员11 小时前
Kafka新版本重大更新,发布4.0!!
后端·kafka·消息队列