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 Type1
double double
float float
int32 int
int64 long
uint32 int2
uint64 long2
sint32 int
sint64 long
fixed32 int2
fixed64 long2
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的案例,上面我们说了有四种接口类型大家可以自己去了解一下其他接口类型的实现方法,代码大体上是差不多的

相关推荐
曾几何时`3 分钟前
Go(二)Goroutine及GMP模型
开发语言·后端·golang
wang09074 分钟前
自己动手写一个spring之IOC_1
java·后端·spring
江湖中的阿龙8 分钟前
Go语言零基础入门教程(一)环境搭建与基础入门
开发语言·后端·golang
集成显卡8 小时前
Rust实战七 |基于带 colored 颜色文字控制台的批量文件删除工具
开发语言·后端·rust
jeffer_liu9 小时前
Spring AI 生产级实战:工具调用
java·人工智能·后端·spring·ai编程
Cosolar10 小时前
AutoGen 精通教程:从零到企业级多 Agent 系统架构师
人工智能·后端·面试
狂炫冰美式11 小时前
你还在古法PPT吗,试试HTML呢?免费编辑导出工具给 xdm 放这了
前端·后端·github
万少12 小时前
未来组织的分水岭不是员工数量,而是人才密度
前端·后端·面试
Honmaple12 小时前
终端 AI 编程的两条路:Pi 极简哲学 vs Oh-My-Pi 全能主义深度对决
后端