一、简介
gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.
gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。
二、Grpc是什么
在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。
使用protocol buffers
gRPC 默认使用 protocol buffers,这是 Google 开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如 JSON)。正如你将在下方例子里所看到的,你用 proto files 创建 gRPC 服务,用 protocol buffers 消息类型来定义方法参数和返回类型。
三、安装Grpc工具
使用grpc需要针对不同的语言安装相应的工具,进行编译。这里我使用的是Java语言,采用maven插件进行处理,这里就不再多说了。
四、Java使用Grpc入门开发
(一)官网例子
我们可以去grpc-java下载官网例子,里面有个examples,这里面存放了实例代码。
(二)创建maven的Java项目,引入依赖
<properties>
<grpc.version>1.13.1</grpc.version>
<protobuf.version>3.5.1</protobuf.version>
<protoc.version>3.5.1-1</protoc.version>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</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>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>${protobuf.version}</version>
</dependency>
</dependencies>
(三)引入编译proto的插件
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
<executions>
<execution>
<id>enforce</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireUpperBoundDeps/>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
(四)定义服务
在src/main下面创建proto文件夹,在里面编写proto文件定义服务,文件编写方式参考例子中proto文件定义方式,这里定义的proto文件如下:
helloworld.proto(其实也是例子中的)
// Copyright 2015 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
//简单的RPC调用
rpc simpleRpc (HelloRequest) returns (HelloReply) {};
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
(五)通过插件生成proto文件
执行之后会在target/generated-sources/protobuf下面生成对应的文件:
其中java里面的主要是一些Java实体类和构造这些实体类的Builder,而grpc里面z只有一个类GreeterGrpc,这个类里面有很多的内部类,我们需要关注的是下面这些:
- GreeterImplBase:服务接口的抽象实现类,需要服务端去实现
- GreeterBlockingStub:同步的服务调用存根,用于客户端去调用服务使用
- GreeterStub:异步的服务调用存根,用于客户端去调用服务使用
接下来,我们可以分别编写客户端和服务端的代码。在实际开发中,我们应该把上面的(二)、(三)、(四)、(五)这些步骤在服务端项目和客户端项目都执行。注意,这个proto文件直接编写一份然后拷贝即可。
(六)服务端编写接口的实现类
package com.firewolf.grpc;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.stub.StreamObserver;
/**
* 作者:刘兴 时间:2019/5/9
**/
public class GreeterServiceImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void simpleRpc(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
System.out.println(request.getName());
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + request.getName()).build();
responseObserver.onNext(reply); //回写响应
responseObserver.onCompleted(); //触发回写操作
}
}
(七)编写服务启动类
通过编写服务启动类用来启动服务,同时把自己的服务实现添加进去
/*
* Copyright 2015 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.firewolf.grpc;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.jar.JarEntry;
import java.util.logging.Logger;
import javax.sound.midi.Soundbank;
/**
* 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 = ServerBuilder.forPort(port)
.addService(new GreeterServiceImpl()) //添加服务实现
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
HelloWorldServer.this.stop();
System.err.println("*** server shut down");
}
});
}
private void stop() {
if (server != null) {
server.shutdown();
}
}
/**
* 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();
}
}
(八)客户端连接服务并调用服务
/*
* Copyright 2015 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.firewolf.grpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.GreeterGrpc.GreeterStub;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.stub.StreamObserver;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.midi.Soundbank;
/**
* A simple client that requests a greeting from the {@link HelloWorldServer}.
*/
public class HelloWorldClient {
private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
private final GreeterGrpc.GreeterStub asyncStub;
public HelloWorldClient(String host, int port) {
this(ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.build());
}
HelloWorldClient(ManagedChannel channel) {
this.channel = channel;
blockingStub = GreeterGrpc.newBlockingStub(channel);
asyncStub = GreeterGrpc.newStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
//简单的RPC调用
public void greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.simpleRpc(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
}
public static void main(String[] args) throws Exception {
HelloWorldClient client = new HelloWorldClient("localhost", 50051);
try {
String user = "world";
if (args.length > 0) {
user = args[0];
}
client.greet(user);
} finally {
client.shutdown();
}
}
}
(九)测试
分别启动服务端和客户端,分别打印如下效果:
可以看到,服务已经调用成功
五、Grpc交互方式
(一)4中交互方式概述
Grpc允许定义4中类型的service方法,这些都定义在proto文件的接口Greeter里面:
- 简单的RPC。客户端使用存根发送请求到服务器并等待响应返回,就像平常的函数调用一样,如:
rpc simpleRpc (HelloRequest) returns (HelloReply) {};
- 服务器端流式 RPC 。客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。从例子中可以看出,通过在响应类型前插入 stream 关键字,可以指定一个服务器端的流方法,如:
rpc serverStream (HelloRequest) returns (stream HelloReply) {};
- 客户端流式 RPC 。 客户端写入一个消息序列并将其发送到服务器,同样也是使用流。一旦 客户端完成写入消息,它等待服务器完成读取返回它的响应。通过在 请求 类型前指定 stream 关键字来指定一个客户端的流方法,如:
rpc clientStream (stream HelloRequest) returns (HelloReply) {};
- 双向流式 RPC 是双方使用读写流去发送一个消息序列。两个流独立操作,因此客户端和服务器 可以以任意喜欢的顺序读写:比如, 服务器可以在写入响应前等待接收所有的客户端消息,或者可以交替 的读取和写入消息,或者其他读写的组合。 每个流中的消息顺序被预留。你可以通过在请求和响应前加 stream 关键字去制定方法的类型,如:
rpc bothStream (stream HelloRequest) returns (stream HelloReply){};
其中第一种方式在入门案例中已经使用了,后面主要讲述另外三种方式。
为了方便后面讲解,这里把统一的工作先做了:将上述修改后的proto文件重新进行编译,生成相关文件(不会的参看前面的步骤)。
(二)服务端流式RPC
1.服务端接口实现代码
@Override
public void serverStream(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
System.out.println(request.getName());
for (int i = 0; i < 10; i++) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello," + i).build();
responseObserver.onNext(reply);
}
responseObserver.onCompleted();
}
这个代码比较简单,这里有一个参数为服务端端响应观察者StreamObserver<HelloReply>
。就是接受到客户端的请求之后,我们可以通过这个观察者使用onNext()
方法流式写回一些数据,之后通过onCompleted()
方法告诉grpc
2.客户端服务调用
public void getList(String name) {
//同步
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
Iterator<HelloReply> helloReplies = blockingStub.serverStream(request);
while (helloReplies.hasNext()) {
HelloReply reply = helloReplies.next();
System.out.println(reply.getMessage());
}
}
上面是使用的同步阻塞方式调用的,也可以使用异步方式调用,如下:
asyncStub.serverStream(request, new StreamObserver<HelloReply>() {
public void onNext(HelloReply helloReply) {
System.out.println(helloReply.getMessage());
}
public void onError(Throwable throwable) {
throwable.printStackTrace();
}
public void onCompleted() {
}
});
(三)客户端流式
这种模式下,客户端通过流式写数据到服务端,服务端处理完之后,再回写数据到客户端,客户端发送数据后,异步等待服务端返回的数据,进行处理
1. 服务端接口实现
@Override
public StreamObserver<HelloRequest> clientStream(final StreamObserver<HelloReply> responseObserver) {
return new StreamObserver<HelloRequest>() {
public void onNext(HelloRequest helloRequest) {
//读取客户端发送过来的信息
System.out.println(helloRequest.getName());
}
public void onError(Throwable throwable) {
}
public void onCompleted() {
//客户端写完之后(上面的onNext函数全部执行完毕之后)会触发这个函数,这个时候回写信息,客户端会接受到这个信息
responseObserver.onNext(HelloReply.newBuilder().setMessage("处理完毕").build());
responseObserver.onCompleted();
}
};
2. 客户端服务调用
public void sendList() {
//使用SettableFuture控制程序,在获取完服务端返回的数据之前,阻塞客户端,否则应为是异步线程,看不到效果
// 实际开发中不需要
final SettableFuture<Void> finishFuture = SettableFuture.create();
StreamObserver<HelloReply> responseStreamObserver = new StreamObserver<HelloReply>() {
public void onNext(HelloReply helloReply) {
System.out.println(helloReply.getMessage());
}
public void onError(Throwable throwable) {
finishFuture.setException(throwable);
}
public void onCompleted() {
finishFuture.set(null);
}
};
StreamObserver<HelloRequest> requestStreamObserver = asyncStub.clientStream(responseStreamObserver);
for (int i = 0; i < 10; i++) {
requestStreamObserver.onNext(HelloRequest.newBuilder().setName("脏三" + i).build());
}
requestStreamObserver.onCompleted();
try {
finishFuture.get();
System.out.println("获取返回完成");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
(四)双向流式RPC
和我们的客户端流的例子一样,我们拿到和返回一个 StreamObserver 应答观察者,除了这次我们在客户端仍然写入消息到 它们的 消息流时通过我们方法的应答观察者返回值。这里读写的语法和客户端流以及服务器流方法一样。虽然每一端都会按照它们写入的顺序拿到另一端的消息,客户端和服务器都可以任意顺序读写——流的操作是互不依赖的。
1. 服务端接口实现
@Override
public StreamObserver<HelloRequest> bothStream(final StreamObserver<HelloReply> responseObserver) {
return new StreamObserver<HelloRequest>() {
public void onNext(HelloRequest helloRequest) {
System.out.println(helloRequest.getName());
HelloReply helloReply = HelloReply.newBuilder().setMessage("消息回执了").build();
responseObserver.onNext(helloReply);
}
public void onError(Throwable throwable) {
}
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
2. 客户端代码
public void bothStreamTest() {
//使用SettableFuture控制程序,在获取完服务端返回的数据之前,阻塞客户端,否则应为是异步线程,看不到效果
// 实际开发中不需要
final SettableFuture<Integer> settableFuture = SettableFuture.create();
//读取服务端响应回来的数据
StreamObserver<HelloReply> responseStreamObserver = new StreamObserver<HelloReply>() {
public void onNext(HelloReply helloReply) {
System.out.println(helloReply.getMessage());
}
public void onError(Throwable throwable) {
settableFuture.setException(throwable);
}
public void onCompleted() {
settableFuture.set(1); //读取完成,设置读取完成标志
}
};
//用于客户端往服务端写数据
StreamObserver<HelloRequest> requestStreamObserver = asyncStub.bothStream(responseStreamObserver);
for (int i = 0; i < 10; i++) {
HelloRequest helloRequest = HelloRequest.newBuilder().setName("zhangsan" + i).build();
requestStreamObserver.onNext(helloRequest);
}
//写完数据后设置标志
requestStreamObserver.onCompleted();
try {
settableFuture.get();
System.out.println("整个服务请求结束");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
到此,GRPC的使用基本讲解完毕