目录
gRPC简介
gRPC 是一个高性能、开源、通用的RPC框架,由Google推出,基于 HTTP2 协议标准设计开发,默认采用 protobuf 数据序列化协议,支持多种开发语言。gRPC 提供了一种简单的方法来精确的定义服务,并且为客户端和服务端自动生成可靠的功能库。
在 gRPC 客户端可以直接调用不同服务器上的远程程序,使用姿势看起来就像调用本地程序一样,很容易去构建分布式应用和服务。和很多RPC系统一样,服务端负责实现定义好的接口并处理客户端的请求,客户端根据接口描述直接调用需要的服务。客户端和服务端可以分别使用 gRPC 支持的不同语言实现。
gRPC 支持定义四类服务方法:
- 单项 RPC,即客户端发送一个请求给服务端,从服务端获取一个应答,就像一次普通的函数调用。
rpc SayHello(HelloRequest) returns (HelloResponse){
}
- 服务端流式 RPC,即客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取直到没有更多消息为止。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}
- 客户端流式 RPC,即客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
- 双向流式 RPC,即两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,例如:服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者是读写相结合的其他方式。每个数据流里消息的顺序会被保持。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}
protobuf
何为protobuf
protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据方法,可用于(数据)通信协议、数据存储等。你可以定义数据结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据,你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。其一般可简单类比于XML,这里的类比主要指在数据通信和数据存储应用场景中序列化方面的类比,总结 protobuf 三大特点如下:
- 语言无关、平台无关:即 ProtoBuf 支持 Java、C++、Python 等多种语言,支持多个平台
- 灵活、高效:即比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单
- 扩展性、兼容性好:你可以更新数据结构,而不影响和破坏原有的旧程序
下载配置
本文基于mac环境配置,首先前往如下网址进行下载:https://github.com/protocolbuffers/protobuf/releases
选择java语言的最新版本即可,我这里安装的是3.13.0,下载完成后解压,然后就是需要配置环境
1、首先进到protobuf安装目录,即 protobuf-3.13.0 目录下
2、分别执行如下命令
./configure --prefix=/Users/xxxxx/protoBuf/protobuf-3.13.0
make
make install
注意:--prefix后的目录即你的 protobuf 安装目录,若 make 命令出现如下报错:
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
则先执行如下命令重装 xcode command line,然后再回到上面步骤重新执行make
xcode-select --install
如果还是没有解决问题,执行
sudo xcode-select -switch /
当你将 make install 命令执行完后,接下来就是配置 protoc 的环境变量了:
vi ~/.bash_profile
然后插入如下语句
export PROTBUF=/Users/xujia/xxxxx/protoBuf/protobuf-3.13.0
export PATH=$PROTBUF/bin:$PATH
重新运行配置文件
source ~/.bash_profile
这时使用 protoc --version 执行输出如下版本号说明配置完毕
libprotoc 3.13.0
编译
配置完环境变量后我们需要来验证一下protobuf,首先需要先编写一个.proto文件,这里直接给出一个小demo,方便验证
syntax = "proto3"; // 定义语法类型,通常proto3好于proto2,proto2好于proto1,如果不指定,默认使用proto2,必须位于第一行
package hello; // 定义作用域
option java_package = "grpc.demo";
service Hello { // 定义服务
rpc SayHello (HelloRequest) returns (HelloResponse) {}
}
message HelloRequest { // 定义消息体
string name = 1;
}
message HelloResponse {
string message = 1;
}
然后将该文件放入安装目录中的 examples 文件夹,执行如下命令后就能在当前目录下看到一个 grpc 文件夹,里面就有对应的java文件
protoc ./demo1.proto --java_out=./
简单案例
本案例只演示单项服务方法,.proto 文件则使用上面的小 demo,首先将 .proto 文件移动到项目中,注意 .proto 文件中指定的 java 包路径需要与项目中指定的一致,项目图如下所示:
maven配置
引入 grpc 的jar包依赖,同时还需要引入 protobuf 的插件依赖,以便能 maven 的方式生成 grpc 代码,注意下面的<protoSourceRoot> 标签的路径需要与上面标红的保持一致
<properties>
<grpc.version>1.23.0</grpc.version>
<protobuf.version>3.13.0</protobuf.version>
</properties>
<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>
<build>
<!-- ${os.detected.classifier} 变量由${os.detected.name} 和 ${os.detected.arch} 组成
https://github.com/trustin/os-maven-plugin -->
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
<protoSourceRoot>src/main/proto</protoSourceRoot>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
生成 grpc 相关类
两种方式,一个是在终端使用命令:
mvn protobuf:compile
mvn protobuf:compile-custom
另外一个是通过 maven 的插件点击方式,本质是一样的:
执行完后在 target 目录下可以看到生成的类
编写服务端
package grpc.demo;
import grpc.demo.HelloGrpc;
import grpc.demo.HelloOuterClass.HelloRequest;
import grpc.demo.HelloOuterClass.HelloResponse;
import io.grpc.stub.StreamObserver;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class HelloServer {
private Server server;
private void start() throws IOException {
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new HelloImpl())
.build()
.start();
System.out.println("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// jvm关闭前执行
System.err.println("*** shutting down gRPC server since JVM is shutting down");
try {
HelloServer.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);
}
}
/**
* 阻塞等待主线程终止
* @throws InterruptedException
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
public static void main(String[] args) throws IOException, InterruptedException {
final HelloServer server = new HelloServer();
server.start();
server.blockUntilShutdown();
}
/**
* 服务实现类
*/
private class HelloImpl extends HelloGrpc.HelloImplBase{
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
HelloResponse helloResponse = HelloResponse.newBuilder().setMessage("Hello "+request.getName()+", I'm Java grpc Server").build();
responseObserver.onNext(helloResponse);
responseObserver.onCompleted();
}
}
}
运行服务端后,控制台输出
Server started, listening on 50051
编写客户端
package grpc.demo;
import io.grpc.ManagedChannel;
import grpc.demo.HelloGrpc;
import io.grpc.ManagedChannelBuilder;
import grpc.demo.HelloOuterClass.HelloRequest;
import grpc.demo.HelloOuterClass.HelloResponse;
import java.util.concurrent.TimeUnit;
public class HelloClient {
/**
* 远程连接管理器,管理连接的生命周期
*/
private final ManagedChannel channel;
/**
* 远程服务存根
*/
private final HelloGrpc.HelloBlockingStub blockingStub;
public HelloClient(String host, int port) {
// 初始化连接
channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
// 初始化远程服务存根
blockingStub = HelloGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public String sayHello(String name) {
// 构造服务调用参数对象
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
// 调用远程服务方法
HelloResponse response = blockingStub.sayHello(request);
return response.getMessage();
}
public static void main(String[] args) throws InterruptedException {
HelloClient client = new HelloClient("127.0.0.1", 50051);
String content = client.sayHello("Java client");
System.out.println(content);
client.shutdown();
}
}
运行客户端后,控制台输出
Hello Java client, I'm Java grpc Server
说明此时已从服务端获取响应