由于最近换了一份工作,新公司使用 RPC 框架使用的是 Google 开源的 RPC 框架 grpc。对于 grpc 之前只是听说过,在真实的项目当中并没有使用过。为了能够更好的使用 grpc (当遇到问题,能够快速发现并解决问题),所以准备写一个系列来研究 grpc 。
gRPC是一个现代的开源高性能远程过程调用(RPC)框架,可以在任何环境中运行。它可以有效地连接数据中心内和跨数据中心的服务,支持负载均衡、跟踪、健康检查和身份验证。它也适用于分布式计算的最后一英里,将设备、移动应用程序和浏览器连接到后端服务。
主要使用场景:
- 在微服务风格的体系结构中有效地连接多语言服务
- 连接移动设备、浏览器客户端到后端服务
- 生成高效的客户端库
核心功能
- 11种语言的常用客户端库
- 高效的在线服务和简单的服务定义框架
- 基于http/2传输的双向流
- 可插拔认证、跟踪、负载均衡和健康检查
1、概述
在gRPC中,客户端应用程序可以直接调用不同机器上的服务器应用程序上的方法,就像它是本地对象一样,这使得创建分布式应用程序和服务更加容易。与许多RPC系统一样,gRPC基于定义服务的思想,指定可以通过参数和返回类型远程调用的方法。在服务器端,服务器实现这个接口,并运行一个gRPC服务器来处理客户端调用。在客户端,客户端有一个 stub (在某些语言中称为客户端),它提供与服务器相同的方法。
gRPC 客户机和服务器可以在各种环境中彼此运行和通信。从谷歌内部的服务器到您自己的桌面,并且可以用 gRPC 支持的任何语言编写。例如,你可以很容易地用Java 创建一个 gRPC 服务器,用Go、Python 或 Ruby 创建客户端。此外,最新的谷歌 api 将拥有其接口的gRPC版本,可以很轻松地将谷歌功能构建到您的应用程序中。
2、Protocol Buffers
默认情况下,gRPC 使用 Protocol Buffers ,这是谷歌成熟的用于序列化结构化数据的开源机制(尽管它也可以用于其他数据格式,如JSON)。下面是它如何工作的一个快速介绍。如果您已经熟悉了协议缓冲区,可以直接跳到下一节。
使用协议缓冲区时,第一步是为你想要在原型文件中序列化的数据定义结构:这是一个扩展名为.proto的 普通文本文件。协议缓冲区数据的结构是消息,其中每个消息是一个小的信息逻辑记录,包含一系列名为字段的 名称-值 对。下面是一个简单的例子:
message Person {
string name = 1;
int32 id = 2;
bool has_ponycopter = 3;
}
然后,一旦指定了数据结构,就可以使用 protocol buffer 编译器协议从原型定义中以你的编程语言生成数据访问类。它们为每个字段提供了简单的访问器,比如name() 和 set_name() ,以及将整个结构序列化/解析为原始字节的方法。例如,如果您选择的语言是 c++ ,在上面的示例中运行编译器将生成一个名为 Person 的类。然后,您可以在应用程序中使用这个类来填充、序列化和检索 Person 的protocol buffer 消息。
你在 .proto 文件中定义 gRPC 服务,使用 RPC 方法参数和返回类型指定为协议缓冲区消息:
// The greeter service definition.
service Greeter {
// Sends a greeting
rpc SayHello (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;
}
gRPC使用带有特殊gRPC插件的protoc从你的原型文件中生成代码:你会生成gRPC客户机和服务器代码,以及用于填充、序列化和检索你的消息类型的 protocol buffer 代码。
如果想要了解更多关于 protocol buffers 的信息,包括如何在你的编码语言中使用 gRPC 插件安装 protoc。可以查看 protocol buffers documentation.
3、核心组件
在高层次抽象上,grpc 有三个不同的层:Stub 、Channel 和 Transport 。
3.1 Stub
Stub 层是向大多数开发人员公开的,并提供类型安全的绑定到您正在适应的任何数据模型/IDL/接口。gRPC 附带了一个 protocol-buffers 编译器的插件,它可以从 .proto 文件生成Stub 接口,并且绑定到其他数据模型/IDL很容易。
3.2 Channel
Channel 是传输处理之上的一个抽象,它适合于拦截/修饰,并且比 Stub 层向应用程序暴露更多的行为。应用程序框架可以很容易地使用这一层来处理横切问题,如日志记录、监视、认证等。
3.3 Transport
Transport 完成了将字节从线路上移除的繁重工作。它的接口是抽象的,刚好允许插入不同的实现。注意,Transport API被认为是gRPC内部的,它的API保证比 io.grpc 包下的核心API要弱。
gRPC有三种传输实现:
- 基于
Netty 的传输是基于 Netty 的主要传输实现。它对客户机和服务器都适用。 - 基于
OkHttp 的传输是一种基于 OkHttp 的轻量级传输。它主要用于 Android 和客户端。 进程内传输 适用于服务器和客户端处于同一进程中的情况。它对测试很有用,同时对生产使用也很安全。
4、Hello World
4.1 安装插件 Protobuf Support
4.2 创建项目
创建一个 maven 项目,包结构如下: 注意:proto 目录需要与 src/main/java 目录平级。
4.3 定义 hello.proto 文件
syntax = "proto3";
option java_multiple_files = true;
option java_package = "cn.carl.grpc.demo.proto";
option java_outer_classname = "HelloServiceProto";
package cn.carl.grpc.demo;
// 定义服务
service HelloService {
// 注意 : 这里是 returns 不是 return
rpc sayHello (HelloRequest) returns (HelloResponse) {
}
}
// 定义消息类型
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
4.4 编译项目
使用命令行 mvn clean install 对项目进行编译,在 target 目录会生成 grpc 文件。也就是开发人员关心的 Stub 文件。
4.5 暴露 GRPC 服务
继承 HelloServiceGrpc.HelloServiceImplBase ,并且把服务添加到 grpc 中绑定端口的 io.grpc.internal.ServerImpl 当中,然后启动暴露服务。
GrpcServer.java
public class GrpcServer {
private final int port = 50051;
private io.grpc.Server server;
private void start() throws Exception {
server = ServerBuilder.forPort(port).addService(new HelloServiceImpl()).build().start();
System.out.println("服务开始启动-------");
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println("------shutting down gRPC server since JVM is shutting down-------");
GrpcServer.this.stop();
System.err.println("------server shut down------");
}
});
}
private void stop() {
if (server != null) {
server.shutdown();
}
}
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
private class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
HelloResponse build = HelloResponse.newBuilder().setMessage(request.getName()).build();
responseObserver.onNext(build);
responseObserver.onCompleted();
}
}
public static void main(String[] args) throws Exception {
final GrpcServer server = new GrpcServer();
server.start();
server.blockUntilShutdown();
}
}
服务启动如下:
4.6 客户端远程调用
使用 ManagedChannelBuilder 绑定远程服务器,然后使用 .proto 生成的 stub 类进行远程调用。
GrpcClient.java
public class GrpcClient {
private final ManagedChannel channel;
private final HelloServiceGrpc.HelloServiceBlockingStub blockingStub;
private static final String host = "127.0.0.1";
private static final int ip = 50051;
public GrpcClient(String host, int port) {
channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
blockingStub = HelloServiceGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public void testGrpc(String name) {
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloResponse response = blockingStub.sayHello(request);
System.out.println(response.getMessage());
}
public static void main(String[] args) {
GrpcClient client = new GrpcClient(host, ip);
for (int i = 0; i <= 5; i++) {
client.testGrpc("<<<<<result>>>>>:" + i);
}
}
}
调用结果如下: 服务端实现服务所需要的接口,并且启动服务接受请求。客户端连接上服务端会有一个stub,然后拿着 stub 和请求参数,去请求某个服务下的某个方法。
相对于服务端实现接口时是继承了 HelloServiceGrpc.HelloServiceImplBase 这个抽象类,而这个类是我们用 proto 工具生成的,并非基于反射实现的。
参考文章
- https://www.grpc.io/about/
- https://www.grpc.io/docs/languages/
- https://www.grpc.io/docs/what-is-grpc/introduction/
- https://blog.csdn.net/weixin_43770545/article/details/90786544
|