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

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

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

it's also possible to use protocol buffers with your own RPC implementation.

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

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

在通读proto文档后,我发现我必须为自己的实现实现抽象接口RpcChannel and 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 - 基于 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;
};

到目前为止,我对我的示例有疑问:

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

  1. - 这是我找到 RPCController 实现示例的地方。
  2. - 这个答案很有趣,因为在最上面的答案中,他们似乎建议不要对 .proto 文件使用内置 RPC 格式的 Protobufs。
    • 我在 this file, in a repository called libpbrpc 中也注意到了同样的概念,这似乎是示例代码的一个很好的来源
  3. 我可以 I/Should 使用现有的实现,例如 RPCZ 吗?

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

  • 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 的服务和客户端的订阅方式 used/handled 等特定功能(参见

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

如果其他人将开发客户端,第二个选项将允许 easier/faster 了解所提供服务的功能。

为了在 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,在 运行 时必须检查参数的数量和类型。