gRPC 和 protobuf 开发框架

image

gRPC就是(Google Remote process calling)谷歌开发的远程过程调用的缩写,是服务端比较常见的一种技术,对于本地过程调用,就是完成某个功能任务,在本机上执行代码就可以完成这个功能;远程过程调用就是,里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务,当然采用到的数据通信服务协议就是protobuf。

gRPC

gRPC与其他的RPC系统框架类似,都是定义一个服务,指定它能够被远程各种类型服务端所调用的方法,在服务端实现这个接口,并启动一个服务器,来处理客户端的调用。在客户端同样拥有一个和服务端相同的方法存根。

gRPC 特性

  • HTTP2

    HTTP2(超文本传输协议第二版),HTTP的基本语义是不变的,比如方法语义(GET/PUST/PUT/DELETE),状态码(200/404/500等)。HTTP2与HTTP1的区别推荐文章

  • 接口定义语言使用protobuf

    ProtoBuf是由Google开发的一种数据序列化协议(类似于XML、JSON),ProtoBuf能够将数据进行序列化,并广泛应用在数据存储、通信协议等。压缩和传输效率高,语法简单,表达力强

  • 支持多语言

    gRPC支持多种语言,并能够基于语言自动生成客户端和服务端功能库,grpc-java已经支持Android开发。

gRPC通信方式

  • 一个请求对象对应一个返回对象
  • 一个请求对象,服务端可以传回多个结果对象
  • 客户端传入多个请求对象,服务端返回一个响应结果
  • 可以传入多个对象,返回多个响应对象

gRPC优缺点

  • 优点

    • protobuf序列化成二进制消息,性能好/效率高(空间和时间效率都表现不错)
    • proto文件生成客户端和服务端目标代码,Ez
    • 支持向前兼容(新加字段采用默认值)和向后兼容(忽略新加字段),简化升级
    • 序列化反序列化直接对应程序中的数据类,不需要解析后在进行其他处理
  • 缺点

    • gRPC 没有提供提供“服务发现”、“负载均衡”、“连接池”等机制
    • protobuf二进制可读性差

protobuf

gRPC使用ProtoBuf定义服务,我们可以在.proto文件中定义服务并使用任何支持它的语言去生成客户端和服务器。

protobuf的安装

  1. 最新的protobufGithub下载并且解压。
  2. 进入解压的目录,设置编译目录(这个/usr/local/protobuf就是安装目录); ./configure –prefix=/usr/local/protobuf
  3. 安装,分别执行make和make install
  4. 配置环境变量编辑.bash_profile,添加配置文件

    1
    2
    export PROTOBUF=/usr/local/protobuf
    export PATH=$PROTOBUF/bin:$PATH
  5. source .bash_profile使配置文件生效

  6. protoc –version检查是否安装成功

protobuf用法

编写.proto文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.rocka";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package com.rocka;

service Greeter{
//定义一个方法,输入的是
rpc SayHello(HelloRequest) returns(HelloReply){}
}

message HelloRequest{
string name = 1;
}

message HelloReply{
string message = 1;
}

执行命令生成代码protoc –java_out=./java/com.rocka ./proto/helloworld.proto

数据结构

.protobuf 说明 Java 类型
double double
float float
int32 int
int64 long
uint32 int[1]
uint64 long[1]
sint32 int
sint64 long
fixed32 int[1]
fixed64 long[1]
sfixed32 int
sfixed64 long
bool boolean
string String
bytes ByteString

protobuf优缺点

  • 优点

    • 性能好/效率高
    • 有代码生成机制
    • 多语言支持
    • 向前向后兼容
  • 缺点

    • 二进制格式导致可读性差
    • 缺乏自描述

grpc-java-master

  1. 下载grpc-java-master
  2. 进入compiler目录,执行编译../gradlew java_pluginExecutable,编译成功会在目标路径下生成grpc-java-master/compiler/build/exe/java_plugin/protoc-gen-grpc-java.exe文件。
  3. 生成通信代码的命令行。

    1
    2
    protoc --plugin=protoc-gen-grpc-java=build/binaries/java_pluginExecutable/protoc-gen-grpc-java \
    --grpc-java_out="$OUTPUT_FILE" --proto_path="$DIR_OF_PROTO_FILE" "$PROTO_FILE"

    我本地生成的命令行为:

    1
    protoc --java_out=./src/main/java --plugin=protoc-gen-grpc-java=/Users/Rocka/Desktop/rocka_workspace/stack_workspace/protobuf_grpc/grpc-java-master/compiler/build/exe/java_plugin/protoc-gen-grpc-java --grpc-java_out=./src/main/java/ ./src/main/proto/helloworld.proto

Tips:

  1. mac 下安装会遇到找不到一些头文件,需要链接头文件。下面的usr/local/protobuf就是protobuf的安装路径。

    1
    export CXXFLAGS="-I/usr/local/protobuf/include" LDFLAGS="-L/usr/local/protobuf/lib"
  2. 生成通信代码的命令行。使用了protoc-gen-grpc-java,就是为了多生成一个GreeterGrpc。

  3. 我在执行编译的时候提示有时候版本不一致,或者其他找不到文件的错误,比如本地我装的是3.6.1protobuf的,但是的是找不到3.5.1的某个文件,我又重新装了3.5.1的protobuf

gRPC x protobuf Demo

下面的栗子就是一个简单的交互,其实主要是配置的问题,以及生成代码的问题。网上po的很多代码都不全,而且没有调试成功过,我这个是跑成功过的,下面是使用到的maven仓库依赖。

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
30
31
32
33
34
35
36
37
38
39
<properties>
<grpc.version>1.16.1</grpc.version><!-- CURRENT_GRPC_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>io.grpc</groupId>
<artifactId>grpc-testing</artifactId>
<version>${grpc.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
</dependencies>

服务端代码,一定要加上server.awaitTermination()

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class TestServer {

private int port = 8883;
private Server server;

public static void main(String[] args) throws IOException, InterruptedException {
final TestServer testServer = new TestServer();
testServer.start();
testServer.blockUntilShtudown();
}

private void start() throws IOException {
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.build()
.start();
System.out.println("Sever started , listening on " + port);
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");
TestServer.this.stop();
System.err.println("*** server shut down");
}
});
}

private void stop() {
if (server != null) {
server.shutdown();
}
}

private void blockUntilShtudown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}


private class GreeterImpl extends GreeterGrpc.GreeterImplBase {
public AtomicInteger count = new AtomicInteger(0);

@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
System.out.println("SayHello。。");
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + request.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
System.out.println("TT:" + count.incrementAndGet() + Thread.currentThread().getName());
}
}

}
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
30
31
32
33
34
35
36
37
38
39
40
41
42
public class TestClient {

private final ManagedChannel channel;

private final GreeterGrpc.GreeterBlockingStub blockingStub;

public TestClient(String host, int port) {
channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext(true).build();
blockingStub = GreeterGrpc.newBlockingStub(channel);
}

public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}

public void greet(String word){
HelloRequest request = HelloRequest.newBuilder().setName(word).build();
HelloReply response;

try{
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e){
e.printStackTrace();
return;
}

System.out.println(response.getMessage());
}

public static void main(String[] args) throws Exception{
TestClient client = new TestClient("localhost", 8883);
try {
String user = "Rocka";
if(args.length > 0){
user = args[0];
}
client.greet(user);
}finally {
client.shutdown();
}
}
}

运行结果:

image

image

项目地址

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器