GRPC 基础知识
基本介绍
gRPC 是一个高性能、开源和通用的 RPC 框架,面向服务端和移动端,基于 HTTP/2 协议标准而设计,默认使用 ProtoBuf(Protocol Buffers) 序列化协议进行开发,当前支持C、Java、Go等多种语言
gRPC提供了一种简单的方法来精确的定义服务,并且为客户端和服务端自动生成可靠的功能库。与很多RPC系统一样,服务端负责实现定义好的接口并处理客户端的请求,客户端根据接口描述直接本地调用需要的服务而不用去关心具体底层通信细节和调用过程。客户端和服务端可以分别使用gRPC支持的不同语言进行实现
基本通信流程
gRPC
通信的第一步是定义IDL(Interface Definition Language)
即proto
文件- 第二步是编译
proto
文件,得到存根Stub
文件。Stub
中集成了服务调用、数据序列化等底层功能,客户端使用它与服务端进行交互。 - 第三步是服务端实现第一步定义的接口并启动,这些接口的定义也在存根
Stub
文件里面 - 最后一步是客户端借助
Stub
文件调用服务端的函数,虽然客户端调用的函数是有服务端实现的,但是调用起来就像是本地函数一样
多语言 Hello World 案例
准备工作
IDEA 安装 Protobuf Support 或 Protobuf Editor 插件用于识别 proto
文件
Protobuf Support 插件官方已弃用,不支持IDEA19+的版本,由于我的电脑IDEA版本较低,所以用的这个
Protobuf Editor 可以支持最新版本的IDEA,但不支持部分老版本的
准备好 python 环境并安装 grpc 相关插件
pip3 install grpcio
pip3 install grpcio-tools
项目搭建
项目结构如下
grpc-sample
├── src
│ └── main
│ ├── java
│ │ └── io
│ │ └── grpc
│ │ └── sample
│ │ └── helloworld
│ ├── proto
│ ├── python
│ └── resources
└── pom.xml
创建一个 Maven 工程,配置 pom 文件,导入 gRPC 的依赖和编译插件
<properties>
<grpc-version>1.20.0</grpc-version>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-core</artifactId>
<version>${grpc-version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>${grpc-version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc-version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc-version}</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<!-- protobuf插件 -->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.9.1:exe:${os.detected.classifier}</pluginArtifact>
<!-- proto文件的所在路径 -->
<protoSourceRoot>src/main/proto</protoSourceRoot>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
在 main/proto 目录下创建 helloworld.proto 文件,定义服务和序列化数据结构
// 指定使用proto3的语法,如果不指定的话,编译器会使用proto2去编译
syntax = "proto3";
// option选项: 影响 特定环境下 的处理方式
// 不同的类会分散为多个java文件(默认false代表所有内容集中在同一个java文件)
option java_multiple_files = true;
// 指定生成的类应该放在什么Java包名下(默认采用包名)
option java_package = "io.grpc.sample.helloworld";
// 指定生成的java文件的类名(默认根据文件名采用驼峰式生成)
option java_outer_classname = "HelloWorldProto";
// 声明包名, 防止项目间命名冲突
package helloworld;
// 定义服务接口
service Greeter {
// 定义一个简单rpc方法 sayHello(除此之外还有3种流式rpc方法)
// 接收一个 HelloRequest 消息体, 返回一个 HelloReply 消息体
rpc SayHello (HelloRequest) returns (HelloReply) {
}
}
// 定义请求消息体
message HelloRequest {
// 消息字段
string name = 1;
}
// 定义响应消息体
message HelloReply {
string message = 1;
}
使用 protobuf-maven-plugin 插件编译 proto 文件生成 Java 文件
前面在 pom
文件中加入了 protobuf
插件后,可以在 maven projects
视图界面看到以下几个编译指令
Plugins
└── protobuf
├── protobuf:compile # 编译消息对象, 生成java文件
├── protobuf:compile-cpp # 编译消息对象, 生成c文件
├── protobuf:compile-custom # 依赖消息对象, 生成接口服务文件
├── protobuf:compile-javanano # 编译消息对象, 生成javanano文件
└── protobuf:compile-python # 编译消息对象, 生成python文件
我们在使用时,只需要执行以下两个指令
protobuf:compile
默认在target/generated-sources/protobuf/java
目录下生成消息文件protobuf:compile-custom
默认在target/generated-sources/protobuf/grpc-java
目录下生成接口服务文件
执行完成后会生成如下文件
target/generated-sources/
├── annotations
└── protobuf
├── grpc-java
│ └── io/grpc/sample/helloworld
│ └── GreeterGrpc.java
└── java
└── io/grpc/sample/helloworld
├── HelloReply.java
├── HelloReplyOrBuilder.java
├── HelloRequest.java
├── HelloRequestOrBuilder.java
└── HelloWorldProto.java
使用 grpcio-tools 工具编译 proto 文件生成 Python 文件
# 进入proto目录
cd proto
# 编译proto文件并将对应的python文件输出到python目录
python -m grpc_tools.protoc --python_out=../python --grpc_python_out=../python -I. helloworld.proto
执行完成后会在 python
目录下生成如下文件
python
├── helloworld_pb2.py # 消息文件
└── helloworld_pb2_grpc.py # 接口服务文件
创建 Java 服务端
- 首通过监听端口创建
server
实例 - 然后重载
GreeterGrpc.GreeterImplBase
中定义的sayHello
方法来提供服务的具体实现,并将该服务注册到server
实例中 - 最后再启动
server
服务
package io.grpc.sample.helloworld;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
* HelloWorld 服务端
* Server that manages startup/shutdown of a {@code Greeter} server.
*/
public class HelloWorldServer {
private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
private Server server;
private void start() throws IOException {
int port = 50051;
// 创建server对象, 监听50051端口
server = ServerBuilder.forPort(port)
// 注册服务
.addService(new GreeterImpl())
.build()
.start();
logger.info("Server started, listening on " + port);
// 添加JVM停止时的回调函数
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
try {
HelloWorldServer.this.stop();
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
System.err.println("*** server shut down");
}
});
}
private void stop() throws InterruptedException {
if (server != null) {
// 等待所有提交的任务执行结束关闭服务
server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
}
}
/**
* Await termination on the main thread since the grpc library uses daemon threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
/**
* Main launches the server from the command line.
*/
public static void main(String[] args) throws IOException, InterruptedException {
final HelloWorldServer server = new HelloWorldServer();
// 启动服务
server.start();
// 阻塞当前主线程
server.blockUntilShutdown();
}
// 扩展grpc自动生成的服务接口抽象, 实现业务功能
static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
// 构建响应消息, 从请求消息中获取姓名并在前面拼接上"Hello "
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
// 在流关闭或抛出异常前可以调用多次
responseObserver.onNext