Skald's Pages.

快速上手高性能 RPC 框架 gRPC

Word count: 1.7kReading time: 7 min
2020/10/21 Share

gRPC 简介

gRPC是由Google开发并高性能的开源,基于http2和protobuf 序列化的 RPC 框架, 它具有以下特点

  • 语言中立
    支持C,Java,Go等多种语言来构建RPC服务,这是gRPC被广泛的应用在微服务项目中的重要原因,因为不同的微服务可能用不同的语言构建。
  • 基于HTTP/2协议
    支持双向流,消息头压缩,单TCP的多路复用,服务端推送等,这些特性使得gRPC更加适用于移动场景下的客户端和服务端之间的通信。
  • 基于 IDL 定义服务
    编写.proto文件即可生成特定语言的数据结构、服务端接口和客户端Stub。
  • 支持Protocol Buffer序列化
    Protocol Buffer是由Google开发的一种数据序列化协议(类似于XML、JSON、Hession),平台无关,压缩和传输效率高,语法简单,表达能力强。

gRPC服务的大体架构

gRPC Overview

协议定义

gRPC 中使用 protocol buffers 做接口定义语言(IDL),同时也作为默认的消息交换格式,在设计上,gRPC 是可以支持 JSON 等其他消息格式的。

一个典型的协议proto定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
syntax = "proto3";
option java_package = "com.example.grpc";
option java_multiple_files = true;
option java_outer_classname = "HelloWorldProto";
message Greeting {
string name = 1;
}

message HelloResp {
string reply = 1;
}

service HelloWorld {
rpc sayHello (Greeting) returns (HelloResp);
}
  • java_package表示生成java代码的包名
  • java_multiple_files = true 表示生成多个java文件,若不设置该属性,则只会生成一个java文件
  • java_outer_classname表示包含message描述的java文件的类名
  • message用于定义消息格式,是 protocol buffers 消息定义
  • service 用于定义gRPC服务接口,需要使用插件的方式生成对应的服务代码

引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.30.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.30.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.30.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-core</artifactId>
<version>1.30.1</version>
</dependency>

添加 maven 插件

插件的主要作用是把 .proto 文件转换为 Java 代码。

编译默认的输出位置在打包目录的generated-sources/protobuf/文件夹下,可参考插件官方文档

gRPC 利用proto文件定义了消息交互格式以及服务,插件配置中goalscompile表示编译proto文件为Message对象,即消息交互格式,而compile-custom表示编译.proto文件为gRPC服务对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</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.12.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.30.1:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

maven的配置文件下添加如下配置, 将Message源文件和gRPC源文件输出到不同的目录:

1
2
3
4
5
6
7
8
<properties>
<!-- Message Java 代码 -->
<javaOutputDirectory>${project.basedir}/src/main/java-proto</javaOutputDirectory>
<!-- gRPC 服务 Java 代码 -->
<protocPluginOutputDirectory>
${project.basedir}/src/main/java-grpc
</protocPluginOutputDirectory>
</properties>

需要注意的一点是,gRPC 服务代码依赖Message代码。

运行下面的命令,即可生成代码。

1
mvn clean complie

可以看到生成的代码如下:

source code

服务端代码实现

1. 实现服务端接口

HelloWorldImplBaseThriftIface 类似, gRPC 通过扩展这个类,来实现响应的服务逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class HelloWorldRpcService extends HelloWorldGrpc.HelloWorldImplBase {
@Override
public void sayHello(Greeting request, StreamObserver<HelloResp> responseObserver) {
// 获取请求的数据
String name = request.getName();

// 构造响应
HelloResp resp = HelloResp.newBuilder()
.setReply("Hello " + name)
.build();

// onNext 设置响应
responseObserver.onNext(resp);

// 正常完成
responseObserver.onCompleted();
}
}

2. 创建服务端

我们使用 gRPC 默认的服务来启动即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class GrpcServer {
private Server server;
/**
* @param port 服务端占用的端口
*/
public GrpcServer(int port) {
server = ServerBuilder.forPort(port)
// 将具体实现的服务添加到 gRPC 服务中
.addService(new HelloWorldRpcService())
.build();
}
public void start() throws IOException {
server.start();
}
public void awaitTermination() throws InterruptedException {
server.awaitTermination();
}
public void shutdown() {
server.shutdown();
}
}

3. 服务主程序

设置好端口,启动后,等待服务结束信号。

1
2
3
4
5
6
7
public class MainServer {
public static void main(String[] args) throws IOException, InterruptedException {
GrpcServer server = new GrpcServer(8888);
server.start();
server.awaitTermination();
}
}
  • server.awaitTermination() 调用后才能保证主进程不会退出

客户端实现

1. 创建客户端 Stub 对象

gRPC通过Stub对象与服务端通信,本例需要创建 HelloWorldBlockingStub, 来实现客户端与服务端通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class HelloWorldClient {
private final HelloWorldGrpc.HelloWorldBlockingStub blockingStub;
public HelloWorldClient(String host, int port) {
ManagedChannel managedChannel = ManagedChannelBuilder.forAddress(host, port)
// 使用非安全机制传输
.usePlaintext()
.build();
blockingStub = HelloWorldGrpc.newBlockingStub(managedChannel);
}

public String sayHello(String name) {
Greeting greeting = Greeting.newBuilder()
.setName(name)
.build();
HelloResp resp = blockingStub.sayHello(greeting);

return resp.getReply();
}
}

2. 客户端主程序

1
2
3
4
5
6
7
public class MainClient {
public static void main(String[] args) {
HelloWorldClient client = new HelloWorldClient("localhost", 8888);
String reply = client.sayHello("HanMeiMei");
System.out.println(reply);
}
}

使用命令行的方式生成代码

使用命令行的方式可以批量处理或自定义协议管理器,从而提升对协议维护管理效率。

1. 下载编译好的 protoc 二进制文件

在 gRPC Github 发布页,下载系统对应的二进制版本。

1
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.13.0/protoc-3.13.0-osx-x86_64.zip

解压到 ~/protoc-3.13.0-osx-x86_64

2. 下载编译好的 protoc-java-gen 插件

在 maven repo 发布目录下 找到对应平台的文件下载

1
wget https://repo1.maven.org/maven2/io/grpc/protoc-gen-grpc-java/1.32.2/protoc-gen-grpc-java-1.32.2-osx-x86_64.exe

把此文件放到 ~/protoc-3.13.0-osx-x86_64/plugin 目录下

3. 命令行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# protoc 根路径
PROTOC_ROOT=/usr/local/protoc-3.13.0-osx-x86_64
# java plugin 路径
PROTOC_PLUGIN_JAVA="protoc-gen-grpc-java=$PROTOC_ROOT/plugin/protoc-gen-grpc-java-1.32.2-osx-x86_64.exe"
# proto 协议目录
SRC_PROTO_DIR="proto/"
# 生成代码的 Java 协议目录
DST_JAVA_DIR="gen-java-proto"
# 生成代码的 GRPC 服务目录
DST_GRPC_DIR="gen-java-grpc"
# 主协议文件
MAIN_PROTO_FILE="proto/HelloService.proto"
# 执行命令
protoc --plugin=$PROTOC_PLUGIN_JAVA -I=$SRC_PROTO_DIR --java_out="gen-java-proto" \
--grpc-java_out="gen-java-proto" "proto/HelloService.proto"
CATALOG
  1. 1. gRPC 简介
  2. 2. 协议定义
  3. 3. 引入依赖
  4. 4. 添加 maven 插件
  5. 5. 服务端代码实现
    1. 5.1. 1. 实现服务端接口
    2. 5.2. 2. 创建服务端
    3. 5.3. 3. 服务主程序
  6. 6. 客户端实现
    1. 6.1. 1. 创建客户端 Stub 对象
    2. 6.2. 2. 客户端主程序
  7. 7. 使用命令行的方式生成代码
    1. 7.1. 1. 下载编译好的 protoc 二进制文件
    2. 7.2. 2. 下载编译好的 protoc-java-gen 插件
    3. 7.3. 3. 命令行