概览
在gRPC里客户端应用可以像调用本地对象一样直接调用另一台不同机器上的服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多RPC系统类似,gRPC也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包括参数和返回类型) 。在服务器端实现这个接口,并运行一个gRPC服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。
gRPC客户端和服务端可以在多种环境中运行和交互-从google内部的服务器到你自己的笔记本,并且可以用任何gRPC支持的语言来编写。所以,你可以很容易地用Java创建一个gRPC服务端,用Go、Python、Ruby来创建客户端。此外,Google最新API将有gRPC版本的接口,使你容易地将Google的功能集成到你的应用里。
使用protocol buffers
gRPC默认使用protocol buffers,这是Google开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如JSON)。正如你将在下方例子里所看到的,你用proto files创建gRPC服务,用protocol buffers消息类型来定义方法的参数和返回类型。你可以在Protocol Buffers文档中找到更多关于Protocol Buffers的资料。
定义服务
创建我们的例子的第一步是定义服务:一个RPC服务通过参数和返回类型来指定可以远程调用的方法。可以使用Protocol Buffers接口定义语言来定义服务方法。
例如:examples/cpp/helloworld
syntax = "proto3";
option java_package = "io.grpc.examples.helloworld";
option java_multiple_files = true;
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) {}
rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}
rpc SayHelloBidiStream (stream HelloRequest) returns (stream 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;
}
一旦定义好服务,我们可以使用Protocol Buffers编译器protoc
来生成创建应用所需的特定客户端和服务端的代码---你可以生成任意gRPC支持的语言的代码。生成的代码包括客户端的存根和服务器要实现的抽象接口,均包含Greeter
所定义的方法。
以Java为例:
服务端需要实现以下接口
public static interface Greeter {
public void sayHello(Helloworld.HelloRequest request, StreamObserver<Helloworld.HelloReply> responseObserver);
}
而客户端用来与Greeter
服务端进行对话的存根
类。就像你看到的,异步存根也实现了Greeter
接口。
public static class GreeterStub extends AbstractStub<GreeterStub>
implements Greeter {
...
}
如果是C++的话,则需要使用以下步骤:
生成客户端和服务端接口,运行:
$ make helloworld.grpc.pb.cc helloworld.pb.cc
数据序列化
在gRPC中,数据的序列化是一个核心概念,它涉及到将数据结构或对象转换成一种可以在网络中传输或存储的格式(通常是二进制格式) 。这个过程确保了数据在发送方和接收方之间能够准确无误地传递。下面我将详细解释数据的序列化,并给出具体例子。
如果我们要支持不同语言的数据序列化,就需要在不同语言之上搭建一个协议用来屏蔽不同语言、不同机器的实现细节。
所以,想要实现不同语言、不同机器间的交流,为不同语言搭建数据序列化的抽象层是有必要的。
一、什么是数据的序列化
序列化(Serialization) 是指将数据结构或对象状态转换为可以存储或传输的形式的过程。具体来说,就是将数据结构或对象转换成一串字节(即二进制数据流),以便在网络上传输或保存到本地文件中。序列化后的数据保留了原始数据的结构和内容,但转换成了更适合存储或传输的格式。
至于序列化之后产生的格式该怎么被接收方理解,那就必须接收方也会这种序列化方式。
双方都会数据的序列化算法,才能真正理解传输的数据格式。
二、序列化的目的
- 数据交换:序列化后的数据可以在不同的系统或平台之间进行交换,因为数据以通用的二进制格式表示。
- 网络传输:在网络通信中,数据需要被序列化成二进制格式才能在网络上传输。
- 数据存储:序列化后的数据可以保存到文件中,以便在需要时重新加载到内存中。
三、gRPC中的序列化
在gRPC中,通常使用Protocol Buffers(简称Protobuf)作为序列化协议(就是数据序列化的一种实现) 。Protobuf是一种轻便高效的结构化数据存储格式,它基于二进制编码构建,能够减少CPU的复杂解析,从而保障RPC调用的高性能。
四、序列化的例子
假设我们有一个简单的用户信息数据结构,包含用户的ID、姓名和年龄,我们可以使用Protobuf来定义这个数据结构并进行序列化。
1. 定义数据结构(.proto文件)
syntax = "proto3";
message User {
int32 id = 1;
string name = 2;
int32 age = 3;
}
在这个.proto
文件中,我们定义了一个User
消息,它包含三个字段:id
(整型)、name
(字符串)和age
(整型)。
2. 序列化过程
当我们需要将一个User
对象(比如User{id: 1, name: "Alice", age: 30}
)序列化时,Protobuf编译器会根据.proto
文件的定义,将这个对象转换成一串二进制字节。这个过程中,Protobuf会采用高效的编码方式(如Varint、Zigzag等)来压缩数据,以减少序列化后的数据大小。
id = 1, name = 2, age = 3这的数字应该是定义变量的二进制形式的排列顺序,转换成真正的二进制字节序列后,为了高效传输,还需要进行压缩(大家约定好算法)。
3. 反序列化过程
接收方在收到序列化后的二进制数据后,会使用Protobuf编译器生成的反序列化代码,将二进制数据转换回User
对象。这样,接收方就能得到与发送方完全相同的User
对象,并进行后续处理。
接收到序列化后的数据后,将这些二进制的序列化数据转换成本地语言、本地机器所使用的状态,这是通过本地的ProtoBuf来处理的。
五、总结
数据的序列化是将数据结构或对象转换成二进制字节流的过程,它在gRPC等RPC框架中扮演着至关重要的角色。通过序列化,数据可以在不同的系统、平台或语言之间进行交换和传输,从而实现远程通信和数据共享。在gRPC中,Protobuf作为一种高效、轻便的序列化协议,被广泛应用于数据的序列化和反序列化过程中。