如何使用ZeroMQ为协议缓冲区编写自己的RPC实现

How can i write my own RPC Implementation for Protocol Buffers utilizing ZeroMQ

本文关键字:自己的 RPC 实现 缓冲区 何使用 ZeroMQ 协议      更新时间:2023-10-16

根据"定义服务"下的Google协议缓冲区文档,他们说

还可以将协议缓冲区与您自己的RPC实现一起使用。

据我所知,协议缓冲区并不是以本机方式实现RPC的。相反,它们提供了一系列必须由用户实现的抽象接口(这就是我!)。因此,我想利用ZeroMQ实现这些抽象接口用于网络通信。

我正试图使用ZeroMQ创建一个RPC实现,因为我正在进行的项目已经为基本消息传递实现了ZeroMQ(因此,正如文档所建议的,我而不是使用gRPC)。

在彻底阅读了proto文档后,我发现我必须实现抽象接口RpcChannel和RpcController才能实现自己的实现。

我已经用RPC实现构建了一个我目前所处位置的最小化示例

.proto文件:为简洁起见,省略了SearchRequest和SearchResponse模式

service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}

SearchServiceImpl.h

class SearchServiceImpl : public SearchService {
public:
void Search(google::protobuf::RpcController *controller,
const SearchRequest *request,
SearchResponse *response,
google::protobuf::Closure *done) override {
// Static function that processes the request and gets the result
SearchResponse res = GetSearchResult(request);
// Call the callback function
if (done != NULL) {
done->Run();
}
}
}
};

MyRPCController.h

class MyRPCController : public google::protobuf::RpcController {
public:
MyRPCController();
void Reset() override;
bool Failed() const override;
std::string ErrorText() const override;
void StartCancel() override;
void SetFailed(const std::string &reason) override;
bool IsCanceled() const override;
void NotifyOnCancel(google::protobuf::Closure *callback) override;
private:
bool failed_;
std::string message_;
};

MyRPCController.cpp-基于此

void MyRPCController::Reset() {  failed_ = false; }
bool MyRPCController::Failed() const { return failed_; }
std::string MyRPCController::ErrorText() const { return message_; }
void MyRPCController::StartCancel() { }
void MyRPCController::SetFailed(const std::string &reason) {
failed_ = true;
message_ = reason;
}
bool MyRPCController::IsCanceled() const { return false; }
void MyRPCController::NotifyOnCancel(google::protobuf::Closure *callback) { }
MyRPCController::ChiRpcController() : RpcController() { Reset(); }

MyRpcChannel.h

class MyRPCChannel: public google::protobuf::RpcChannel {
public:
void CallMethod(const google::protobuf::MethodDescriptor *method, google::protobuf::RpcController *controller,
const google::protobuf::Message *request, google::protobuf::Message *response,
google::protobuf::Closure *done) override;
};

到目前为止,我对我的例子有一些问题:

  • 在哪里可以将ZeroMQ放入其中?
    • 它似乎应该进入RPCChannel,因为在我看到的示例中(请参阅此处的第三个代码块),它们传递一个具有要绑定的端口的字符串(即MyRpcChannel channel("rpc:hostname:1234/myservice");)
  • 我关心我的RPCController实现,它似乎太简单了。应该有更多的人来这里吗
  • 我如何实现RPCChannel,它似乎与SearchServiceImpl非常相似。这些类中的1虚拟函数有一个非常相似的方法签名,只是它是泛型的

以下是我遇到的其他一些堆栈溢出问题,其中包含了一些关于该主题的有用信息:

  1. Protobuf-Net:实现服务器、rpc控制器和rpc通道-这是我找到RPCController实现示例的地方
  2. 使用协议缓冲区在ZeroMQ中实现RPC-这个答案很有趣,因为在最重要的答案中,他们似乎建议不要为.proto文件使用RPC格式中内置的Protobufs。
    • 我在这个文件中也注意到了同样的概念,在一个名为libpbrpc的存储库中,它似乎是一个很好的源代码,例如代码
  3. 我可以/应该使用现有的实现(如RPCZ)吗

感谢您的帮助。我希望我提供了足够的信息,并且清楚地知道我在寻找什么。如果有什么不清楚或缺乏信息,请告诉我。我很乐意相应地编辑这个问题。

  • ZeroMQ为基于可以包含任何数据的消息的网络通信提供了低级API
  • ProtoBuffers是一个将结构化数据编码为压缩二进制数据并对这些数据进行解码的库
  • gRPC是一个RPC框架,它为基于网络通信的RPC服务生成代码,该服务具有将数据作为ProtoBuffers数据交换的功能

ZeroMQ和gRPC都以不同的方式提供对网络通信的支持。您必须选择ZeroMQ或gRPC进行网络通信。如果选择ZeroMQ,则可以使用交换二进制结构化数据的ProtoBuffer对消息进行编码。

重点是ProtoBuffers库允许对变体记录(类似于C/C++联合)进行编码和解码,可以完全模拟RPC服务提供的功能,RPC服务具有交换ProtoBuffer消息的功能。

所以选项是:

  1. 将ZeroMQ与发送和接收原语以及ProtoBuffers编码的变体消息一起使用,这些消息可以包含各种子消息,如
union Request
{
byte msgType;
MessageType1 msg1;
MessageType2 msg2;
MessageType3 msg3;
}
union Response
{
byte msgType;
MessageType3 msg1;
MessageType4 msg2;
MessageType5 msg3;
}
send(Request request);
receive(Response response);
  1. 使用gRPC生成具有以下功能的服务
service MyService 
{
rpc function1(MessageType1) returns (Response);
rpc function2(MessageType2) returns (Response);
rpc function3(MessageType3) returns (Response);
rpc functionN(MessageType3) returns (MessageType5);
}

(这里可以使用多种组合)

  1. 只使用单个函数gRPC服务,如
service MyService 
{
rpc function(Request) returns (Response);
}

选项可能取决于

  • 客户端的首选目标:ZeroMQ或基于gRPC的客户端
  • 比较ZeroMQ与基于gRPC的服务的性能原因
  • 特定功能,如在ZeroMQ与基于gRPC的服务和客户端中如何使用/处理订阅(请参阅如何在gRPC中正确设计发布订阅模式?)

对于第一个选项,与第二个选项相比,你必须做很多事情。您必须将发送的消息类型与预期接收的消息类型相匹配。

如果其他人将开发客户端,第二种选择将使您更容易/更快地了解所提供服务的功能。

为了在ZeroMQ上开发RPC服务,我会定义这样的.proto文件,指定函数、参数(所有可能的输入和输出参数)和错误,如下所示:

enum Function 
{
F1 = 0;
F2 = 1;
F3 = 2;
}
enum Error 
{
E1 = 0;
E2 = 1;
E3 = 2;
}
message Request
{ 
required Function function = 1;
repeated Input data = 2;
}
message Response
{ 
required Function function = 1;
required Error error = 2;
repeated Output data = 3;
}
message Input
{ 
optional Input1 data1 = 1;
optional Input2 data2 = 2;
...
optional InputN dataN = n;
}
message Output
{ 
optional Output1 data1 = 1;
optional Output2 data2 = 2;
...
optional OutputN dataN = n;
}
message Message
{
repeated Request requests;
repeated Response responses;
}

根据函数id,在运行时必须检查参数的数量和类型。