【问题标题】:How can i write my own RPC Implementation for Protocol Buffers utilizing ZeroMQ我如何使用 ZeroMQ 为协议缓冲区编写自己的 RPC 实现
【发布时间】:2020-04-24 06:45:46
【问题描述】:

他们说,根据“Defining Services”下的 Google Protocol Buffers 文档,

还可以在您自己的 RPC 实现中使用协议缓冲区。

据我了解,Protocol Buffers 本身并没有实现 RPC。相反,它们提供了一系列必须由用户实现的抽象接口(这就是我!)。所以我想实现这些抽象接口,利用 ZeroMQ 进行网络通信。

我正在尝试使用 ZeroMQ 创建一个 RPC 实现,因为我正在处理的项目已经实现了 ZeroMQ 用于基本消息传递(因此我使用 gRPC,正如文档所建议的那样) .

彻底阅读了proto文档后,我发现我必须自己实现抽象接口RpcChannelRpcController

我已经构建了一个最小化的示例,说明我目前的 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 - 基于this

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,因为在我看到的示例中(参见第三个代码块 here),它们传递了一个字符串,该字符串具有要绑定到的端口(即 MyRpcChannel channel("rpc:hostname:1234/myservice");
  • 我关心我的 RPCController 实现,它似乎太简单了。应该更多的去这里吗?
  • 如何实现 RPCChannel,看起来和 SearchServiceImpl 很像。这些类中的 1 个虚函数具有非常相似的方法签名,只是它是通用的。

以下是我遇到的其他一些 Stack Overflow 问题,其中包含有关该主题的一些有用信息:

  1. Protobuf-Net: implementing server, rpc controller and rpc channel - 这是我找到 RPCController 实现示例的地方。
  2. Using Protocol Buffers for implementing RPC in ZeroMQ - 这个答案很有趣,因为在最佳答案中,似乎他们建议不要对 .proto 文件使用内置 RPC 格式的 Protobufs。
    • 我还在名为 libpbrpc 的存储库中的 this 文件中注意到了同样的概念,这似乎是示例代码的好来源
  3. 我可以/我应该使用现有的实现吗,例如RPCZ

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

【问题讨论】:

  • 同样的问题,你最后做了什么?

标签: c++ protocol-buffers zeromq rpc


【解决方案1】:
  • ZeroMQ 为基于可包含任何数据的消息的网络通信提供低级 API。
  • ProtoBuffers 是一个将结构化数据编码为压缩二进制数据并解码此类数据的库。
  • gRPC 是一个 RPC 框架,它为基于网络通信的 RPC 服务生成代码,其函数将数据作为 ProtoBuffers 数据进行交换。

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

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

所以选项是:

  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 的服务和客户端中如何使用/处理订阅(请参阅 How to design publish-subscribe pattern properly in 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,在运行时必须检查参数的数量和类型。

【讨论】:

  • 您好 Flaviu,感谢您抽出宝贵的时间写出所有这些内容。这里有很多很好的信息。我有点坚持使用 ZeroMQ 和协议缓冲区而不是 gRPC,因为我们的项目已经使用 ZeroMQ,他们不想实现另一个网络。所以我一直在尝试探索如何正确集成 Protocol Buffers 提供的 RPC 抽象接口,以及如何用 ZeroMQ 实现它们。
  • 您好,欢迎您。因此,您希望在 ZeroMQ 之上拥有一个 RPC 服务。为此,必须定义两种理论协议,一种用于指定 RPC 内容(函数 ID、参数数量、可能发生的错误),另一种用于指定 RPC 函数交换的消息(参数)。请查看 XML-RPC (en.wikipedia.org/wiki/XML-RPC) 和 JSON-RPC (en.wikipedia.org/wiki/JSON-RPC)。基于这样的 RPC 定义,您可以编写一个涵盖 RPC 和消息内容的 .proto 文件 - 我想这是困难的部分。然后生成 ProtoBuffers 代码,然后集成到 ZeroMQ 项目中。
  • 感谢您的回复。据我了解,我认为我可以利用 Protobufs 实现相同的功能,而不是使用 Xml-RPC 或 Json RPC。阅读他们的"Services" documentation 听起来就是这样,这就是我想要完成的。
  • "或者必须定义 2 个理论协议,一个用于指定 RPC 内容(函数 ID、参数数量、可能发生的错误),一个用于指定 RPC 函数交换的消息(参数) 。”由此看来,听起来这个问题可以使用 Protobuf 模式来解决,但我可能是错的。再次感谢您抽出宝贵时间回复,非常感谢您提供的信息。
  • 很高兴听到这个消息。关于性能,将 ids(枚举)用于函数而不是字符串会更快。当需要与枚举关联的文本时,我使用了github.com/cflaviu/decl_enum。也可以使用 FlatBuffers (google.github.io/flatbuffers) 代替 ProtoBuffers。它们与 ProtoBuffers 非常相似。 FlatBuffers 不压缩数据,因此数据的发送/接收速度更快。
猜你喜欢
  • 1970-01-01
  • 2011-11-15
  • 1970-01-01
  • 2013-11-19
  • 2010-11-29
  • 1970-01-01
  • 1970-01-01
  • 2014-10-03
  • 2012-11-25
相关资源
最近更新 更多