GRPC简介
1. 简介
**gRPC 是最初由 Google 开发的高性能、开源的 RPC 框架。**它有助于消除样板代码,并有助于在数据中心内和跨数据中心连接多语言服务。
2. 概述
该框架基于远程过程调用的客户端-服务器模型。客户端应用程序可以直接调用服务器应用程序上的方法,就好像它是本地对象一样。
本文将使用以下步骤使用 gRPC 创建典型的客户端-服务器应用程序:
- 在*.proto*文件中定义服务
- 使用协议缓冲区编译器生成服务器和客户端代码
- 创建服务器应用程序,实现生成的服务接口并生成 gRPC 服务器
- 创建客户端应用程序,使用生成的存根进行 RPC 调用
让我们定义一个简单的HelloService,它返回问候以换取名字和姓氏。
3. Maven依赖
让我们添加grpc-netty 、grpc-protobuf 和grpc-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*文件中完成的。它们还用于描述有效负载消息的结构。
4.1.基本配置
让我们为示例HelloService创建一个HelloService.proto文件。我们首先添加一些基本的配置细节:
syntax = "proto3";
option java_multiple_files = true;
package com.blogdemo.grpc;
第一行告诉编译器在这个文件中使用了什么语法。默认情况下,编译器在单个 Java 文件中生成所有 Java 代码。第二行覆盖此设置,所有内容都将在单个文件中生成。
最后,我们指定要用于生成的 Java 类的包。
4.2. 定义消息结构
接下来,我们定义消息:
message HelloRequest {
string firstName = 1;
string lastName = 2;
}
这定义了请求有效负载。在这里,进入消息的每个属性都与其类型一起定义。
需要为每个属性分配一个唯一编号,称为标记。协议缓冲区使用此标记来表示属性,而不是使用属性名称。
因此,与 JSON 中我们每次都传递属性名称firstName不同,protocol buffer 将使用数字 1 来表示firstName。响应负载定义类似于请求。
请注意,我们可以在多种消息类型中使用相同的标签:
message HelloResponse {
string greeting = 1;
}
4.3. 定义服务合同
最后,让我们定义服务契约。对于我们的HelloService,我们定义了一个*hello()*操作:
service HelloService {
rpc hello(HelloRequest) returns (HelloResponse);
}
hello()操作接受一元请求并返回一元响应。gRPC 还通过在请求和响应中添加stream关键字来支持流式传输。
5. 生成代码
现在我们将HelloService.proto文件传递给协议缓冲区编译器protoc以生成 Java 文件。有多种触发方式。
5.1. 使用协议缓冲区编译器
首先,我们需要 Protocol Buffer Compiler。我们可以从这里 提供的许多预编译二进制文件中进行选择。
此外,我们需要获取gRPC Java Codegen Plugin 。
最后,我们可以使用以下命令生成代码:
protoc --plugin=protoc-gen-grpc-java=$PATH_TO_PLUGIN -I=$SRC_DIR
--java_out=$DST_DIR --grpc-java_out=$DST_DIR $SRC_DIR/HelloService.proto
5.2. 使用 Maven 插件
作为开发人员,您希望代码生成与您的构建系统紧密集成。gRPC 为 Maven 构建系统提供了一个protobuf-maven-plugin :
<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-plugin 扩展/插件生成各种有用的平台相关项目属性,如*${os.detected.classifier}*
6. 创建服务器
无论您使用哪种方法生成代码,都会生成以下密钥文件:
- HelloRequest.java – 包含HelloRequest类型定义
- HelloResponse.java - 这包含HelleResponse类型定义
- HelloServiceImplBase.java – 这包含抽象类HelloServiceImplBase,它提供了我们在服务接口中定义的所有操作的实现
6.1.覆盖服务基类
抽象类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<HelloResponse>,它是一个响应观察者,是服务器调用其响应的回调。
这样**,客户端可以选择进行阻塞调用或非阻塞调用**。
gRPC 使用构建器来创建对象。我们使用HelloResponse.newBuilder()并设置问候文本来构建一个HelloResponse对象。我们将此对象设置为 responseObserver 的*onNext()*方法以将其发送给客户端。
最后,我们需要调用*onCompleted()*来指定我们已经完成了对 RPC 的处理,否则连接将被挂起,客户端将等待更多信息进来。
6.2. 运行 Grpc 服务器
接下来,我们需要启动 gRPC 服务器来监听传入的请求:
public class GrpcServer {
public static void main(String[] args) {
Server server = ServerBuilder
.forPort(8080)
.addService(new HelloServiceImpl()).build();
server.start();
server.awaitTermination();
}
}
在这里,我们再次使用构建器在端口 8080 上创建一个 gRPC 服务器,并添加我们定义的HelloServiceImpl服务。*start()将启动服务器。在我们的示例中,我们将调用awaitTermination()*以保持服务器在前台运行并阻止提示。
7. 创建客户端
gRPC 提供了一种通道结构,可以抽象出连接、连接池、负载平衡等底层细节。
我们将使用ManagedChannelBuilder创建一个频道。在这里,我们指定服务器地址和端口。
我们将使用没有任何加密的纯文本:
public class GrpcClient {
public static void main(String[] args) {
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
.usePlaintext()
.build();
HelloServiceGrpc.HelloServiceBlockingStub stub
= HelloServiceGrpc.newBlockingStub(channel);
HelloResponse helloResponse = stub.hello(HelloRequest.newBuilder()
.setFirstName("Blogdemo")
.setLastName("gRPC")
.build());
channel.shutdown();
}
}
接下来,我们需要创建一个存根,用于对*hello()*进行实际的远程调用。**存根是客户端与服务器交互的主要方式。**当使用自动生成的存根时,存根类将具有用于包装通道的构造函数。
这里我们使用阻塞/同步存根,以便 RPC 调用等待服务器响应,并且将返回响应或引发异常。gRPC 提供了另外两种类型的存根,它们有助于非阻塞/异步调用。
最后,是时候进行hello() RPC 调用了。这里我们传递了HelloRequest。我们可以使用自动生成的设置器来设置HelloRequest对象的firstName、lastName属性。
我们取回从服务器返回的HelloResponse对象。