GRPC是基于protocol buffers3.0协议的.
本文将向您介绍gRPC和protocol buffers。 gRPC可以使用protocol buffers作为其IDL(接口描述)和其底层消息交换格式。 如果您是gRPC和protocol buffers的新手,请继续阅读! 如果您只是想首先看到gRPC,请参阅我们的快速入门。
指南
在gRPC中,客户端应用程序可以直接在不同机器上的服务器应用程序上调用方法,就像它是本地对象一样,使您更容易创建分布式应用程序和服务。 与许多RPC系统一样,gRPC基于定义服务的思想,指定可以使用其参数和返回类型远程调用的方法。 在服务器端,服务器实现此接口并运行gRPC服务器来处理客户端调用。 在客户端,客户端有一个stub(简称为一些语言的客户端),提供与服务器相同的方法。
gRPC客户端和服务器可以在各种环境中运行和交互,从Google内部的服务器到您自己的应用,并且可以使用任何gRPC支持的语言编写。 所以,例如,您可以轻松地创建一个Java开发的服务,使用Go,Python或Ruby中的客户端。In addition, the latest Google APIs will have gRPC versions of their interfaces, letting you easily build Google functionality(功能) into your applications.
Protocol buffer 版本
虽然Protocol buffer已经可用于开源用户一段时间,但我们的示例使用了proto3的Protocol buffer的新风格,它具有略微简化的语法,一些有用的新功能,并支持更多的语言。这是目前可用于Java,C ++,Python,Objective-C,C#,lite-runtime(Android Java),Ruby和JavaScript的Protocol buffer Github repo,以及来自golang/protobuf Github的Go语言自动生成,还有更多的语言在开发中。您可以在proto3语言指南和每种支持的语言的参考文档(如果可用)查看更多内容,并且可以在发行说明中查看与当前默认版本的主要区别。更多的proto3文档即将推出。
一般来说,虽然您可以使用proto2(当前的默认Protocol buffer版本),但我们建议您使用proto3协议为基础的gRPC,因为它可以让您使用全系列的gRPC支持的语言,并避免与proto2客户端与proto3服务器的兼容性问题,反之亦然。
注:因为proto2只提供了消息定义,而没有提供服务接口的定义。
服务定义
像许多RPC系统一样,gRPC基于定义服务的思想,指定可以使用其参数和返回类型远程调用的方法。 默认情况下,gRPC使用 protocol buffers作为接口定义语言(IDL),用于描述有效负载消息的服务接口和结构。
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
pf3.0出现了接口定义。
gRPC 定义了四种类型的服务接口:
- 一元RPC,客户端向服务器发送请求并获得响应,就像正常的函数调用一样。
rpc SayHello(HelloRequest) returns (HelloResponse){
}
- 服务器流RPC,客户端发送一个对象服务器端返回一个Stream(流式消息)
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}
- 客户端流式RPC,客户端发送一个Stream(流式消息)服务端返回一个对象。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
- 双向流RPC
其中双方使用读写流发送消息序列。 两个流独立运行,所以客户端和服务器可以按照他们喜欢的顺序进行读取和写入:例如,服务器可能在写入响应之前等待接收所有客户端消息,或者可以交替地读取消息然后写入消息, 或读取和写入的其他组合。 每个流中消息的顺序被保留。
类似于WebSocket(长连接),客户端可以向服务端请求消息,服务器端也可以向客户端请求消息)。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}
使用api
从.proto文件中的服务定义开始,gRPC提供了生成客户机和服务器端代码的protocol buffer编译器插件。 gRPC用户通常在客户端调用这些API,并在服务器端实现相应的API。
- 在服务器端,服务器实现服务声明的方法,并运行gRPC服务器来处理客户端调用。 gRPC基础设施解码传入请求,执行服务方法和编码服务响应。
- 在客户端,客户端具有称为stub的本地方法定义(对于一些语言,首选项是客户端),其实现与服务相同的方法。 然后,客户端可以直接在本地对象上调用这些方法,将调用的参数包含在适当的protocol buffer消息类型中 - gRPC在将请求发送到服务器并返回服务器的protocol buffer响应之后。
同步和异步
同步RPC调用阻塞,直到响应从服务器返回是与RPC渴望的过程调用最接近(差不多这个意思)。 另一方面,网络本质上是异步的,并且在许多情况下,能够在不阻塞当前线程的情况下启动RPC是有用的。
大多数语言中的gRPC编程表面都包含同步和异步语言。 您可以在每种语言的教程和参考文档中找到更多内容(完整的参考文档即将推出)。
RPC生命周期
现在让我们仔细看看当gRPC客户端调用gRPC服务器方法时会发生什么。 我们不会查看实现细节,可以在特定语言页面中了解更多信息。
一元RPC
客户端向服务器发送单个请求并获得单个响应,就像正常的函数调用一样。
一旦客户端调用stub/客户端对象上的方法,就会通知服务器RPC客户端的元数据用于此调用,方法名称和指定的期限(如果配置的话)
然后,服务器可以立即发回自己的初始元数据(必须在任何响应之前发送),或等待客户端的请求消息 - 由应用程序决定谁先执行。
一旦服务器有客户端的请求消息,它就做任何创建和填充其响应所需的工作。 然后将响应(如果成功)返回给客户端以及状态详细信息(状态代码和可选状态消息)以及可选的尾随元数据。
- 状态是ok的,客户端得到响应,客户端完成所有的调用。
服务器流RPC
服务器流RPC与我们的简单示例类似,除了服务器在获取客户端请求消息之后发送回响应流。 在发回所有响应后,服务器端的状态信息(状态码和可选状态消息)和可选的尾随元数据将被发送回完成。 客户端完成直到接收到服务器的所有响应。
客户端流式RPC
客户端流RPC也类似于我们的简单示例,除了客户端将请求流发送到服务器,而不是单个请求。 服务器返回单个响应,通常但不一定在收到所有客户端的请求后返回响应,以及其状态详细信息和可选的尾随元数据。
双向流RPC
在双向流RPC中,调用发起与客户端调用方法然后服务器端接收客户端的元数据,方法名称,和调用期限。 服务器也可以选择发回其初始元数据或等待客户端开始发送请求。
接下来发生的情况取决于应用程序,因为客户端和服务器可以以任何顺序读取和写入 - 流完全独立运行。 因此,例如,服务器可能会等到收到所有客户端的消息后再写入响应,否则服务器和客户端可能会“ping-pong”:服务器获取请求,然后发送回应,然后客户端发送基于响应的另一个请求等等。
截止日期和超时
gRPC允许客户端指定在RPC服务调用终止之前愿意等待RPC完成的时间,否则显示错误DEADLINE_EXCEEDED。 在服务器端,服务器可以查询特定的RPC是否超时,还是剩下多少时间来完成RPC调用。
如何指定期限或超时时间因语言而异 - 例如,并非所有语言都有默认的最后期限,某些语言API的工作时间是截止时间(固定时间点),而某些语言API的工作范围是超时 (一段时间内)。
RPC 终止
在gRPC中,客户端和服务器都相对独立和本地确定本地调用成功,并且其结论可能不匹配另一端。 这意味着,例如,您可以在服务器端成功完成一个RPC返回(“我已发送我的所有回复”),但在客户端失败(“我的截止日期之后回复”)。 服务器也可以在客户端发送所有请求之前决定完成。
取消RPC调用
客户端或服务器可以随时取消RPC。取消立即被终止以便不再进行任何工作。它不是一个“撤消”:在取消之前的修改不会被回滚。
元数据
元数据是关于键值对列表形式的特定RPC调用(例如认证细节)的信息,其中键是字符串,并且值通常是字符串(但可以是二进制数据)。 元数据对gRPC本身是不透明的 - 它允许客户端提供与对服务器的调用相关联的信息,反之亦然。
对元数据的访问取决于依赖的语言。
通道
gRPC通道提供与指定主机和端口上的gRPC服务器的连接,并在创建客户端stub(或仅某些语言的“客户端”)时使用。 客户端可以指定通道参数来修改gRPC的默认行为,例如打开和关闭消息压缩。 通道有状态,包括连接和空闲。 > gRPC如何处理关闭频道与语言有关。 某些语言也允许查询通道状态。
快速入门
下载example
➜ # Clone the repository at the latest release to get the example code:
➜ git clone -b v1.4.0 https://github.com/grpc/grpc-java
➜ # Navigate to the Java examples:
➜ cd grpc-java/examples
-b v1.4.0
表示指定的是1.4.0
这个版本,不指定版本默认的是master
版本。
运行一个grpc的应用
编译服务器端和客户端,这一步需要很长时间,
➜ ./gradlew installDist
运行服务器:
➜ ./build/install/examples/bin/hello-world-server
六月 25, 2017 6:58:58 下午 io.grpc.examples.helloworld.HelloWorldServer start
信息: Server started, listening on 50051
运行客户端:
➜./build/install/examples/bin/hello-world-client
六月 25, 2017 6:59:08 下午 io.grpc.examples.helloworld.HelloWorldClient greet
信息: Will try to greet world ...
六月 25, 2017 6:59:09 下午 io.grpc.examples.helloworld.HelloWorldClient greet
信息: Greeting: Hello world
修改gRPC服务
进入src/main/proto/
目录,修改helloworld.proto
文件,增加一个SayHelloAgain
方法,
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 {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayHelloAgain (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;
}
helloworld.proto
文件中增加了一个SayHelloAgain
方法,入参是HelloRequest
,返回参数是HelloReply
。
修改服务器代码,增加SayHelloAgain
方法的实现
重新编译.prto文件,将会重新生成客户端,服务端代码,也重新生成序列化,我们在服务端去增加刚才helloworld.proto
文件中增加了一个SayHelloAgain
方法的实现:
在src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java
文件增加sayHelloAgain
方法实现:
private class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
@Override
public void sayHelloAgain(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello again " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
...
修改客户端
修改src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java
文件,增加blockingStub.sayHelloAgain
的调用。
public void greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
try {
response = blockingStub.sayHelloAgain(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
}
编译文件,重新执行
➜ ./gradlew installDist
运行服务器:
➜ ./build/install/examples/bin/hello-world-server
运行客户端:
➜ ./build/install/examples/bin/hello-world-client
客户端打印:
➜ ./build/install/examples/bin/hello-world-client
六月 25, 2017 7:43:29 下午 io.grpc.examples.helloworld.HelloWorldClient greet
信息: Will try to greet world ...
六月 25, 2017 7:43:29 下午 io.grpc.examples.helloworld.HelloWorldClient greet
信息: Greeting: Hello world
六月 25, 2017 7:43:30 下午 io.grpc.examples.helloworld.HelloWorldClient greet
信息: Greeting: Hello again world
总结
运行一个简单的gRPC列子,首先你需要学会下面三个步骤:
- 使用一个.proto文件定义服务(定义了消息体和服务接口)
- 使用protocol buffer编译器去编译.proto文件并且去生成客户端和服务端代码(不能使用protoc指令,因为gRPC是使用的基于protocol buffer3.0的,maven和gradle都提供了对应的编译器,而protoc 指令是protocol buffer2.0的指令)。
- 编写自己的客户端和服务端(服务器端额外编写接口实现)。
发现所有的RPC框架的基础流程都是如此。