提供 Nshead 服务

qianmoQqianmoQ· 更新于 2026-10-05· 阅读 20 分钟· 0 次阅读

登录后可跨设备保存划线和私人笔记登录

提供 Nshead 服务

学习如何提供 nshead 服务。

ub是百度内广泛使用的老RPC框架,在迁移ub服务时不可避免地需要访问ub-server或被ub-client访问。ub使用的协议种类很多,但都以nshead作为二进制包的头部,这类服务在brpc中统称为"nshead service"。

nshead后大都使用mcpack/compack作为序列化格式,注意这不是"协议"。"协议"除了序列化格式,还涉及到各种特殊字段的定义,一种序列化格式可能会衍生出很多协议。ub没有定义标准协议,所以即使都使用mcpack或compack,产品线的通信协议也是五花八门,无法互通。鉴于此,我们提供了一套接口,让用户能够灵活的处理自己产品线的协议,同时享受brpc提供的builtin services等一系列框架福利。

使用ubrpc的服务

ubrpc协议的基本形式是nshead+compack或mcpack2,但compack或mcpack2中包含一些RPC过程需要的特殊字段。

在brpc r31687之后,用protobuf写的服务可以通过mcpack2pb被ubrpc client访问,步骤如下:

把idl文件转化为proto文件

使用脚本idl2proto把idl文件自动转化为proto文件,下面是转化后的proto文件。

// Converted from echo.idl by brpc/tools/idl2proto
import "idl_options.proto";
option (idl_support) = true;
option cc_generic_services = true;
message EchoRequest {
  required string message = 1;
}
message EchoResponse {
  required string message = 1;
}

// 对于有多个参数的idl方法,需要定义一个包含所有request或response的消息,作为对应方法的参数。
message MultiRequests {
  required EchoRequest req1 = 1;
  required EchoRequest req2 = 2;
}
message MultiResponses {
  required EchoRequest res1 = 1;
  required EchoRequest res2 = 2;
}

service EchoService {
  // 对应idl中的void Echo(EchoRequest req, out EchoResponse res);
  rpc Echo(EchoRequest) returns (EchoResponse);

  // 对应idl中的EchoWithMultiArgs(EchoRequest req1, EchoRequest req2, out EchoResponse res1, out EchoResponse res2);
  rpc EchoWithMultiArgs(MultiRequests) returns (MultiResponses);
}

原先的 echo.idl 文件如下:

struct EchoRequest {
    string message;
};

struct EchoResponse {
    string message;
};

service EchoService {
    void Echo(EchoRequest req, out EchoResponse res);
    uint32_t EchoWithMultiArgs(EchoRequest req1, EchoRequest req2, out EchoResponse res1, out EchoResponse res2);
};

以插件方式运行protoc

BRPC_PATH代表brpc产出的路径(包含bin include等目录),PROTOBUF_INCLUDE_PATH代表protobuf的包含路径。注意–mcpack_out要和–cpp_out一致。

protoc --plugin=protoc-gen-mcpack=$BRPC_PATH/bin/protoc-gen-mcpack --cpp_out=. --mcpack_out=. --proto_path=$BRPC_PATH/include --proto_path=PROTOBUF_INCLUDE_PATH

实现生成的 Service 基类

class EchoServiceImpl : public EchoService {
public:

    ...
    // 对应idl中的void Echo(EchoRequest req, out EchoResponse res);
    virtual void Echo(google::protobuf::RpcController* cntl_base,
                      const EchoRequest* request,
                      EchoResponse* response,
                      google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);

        // 填充response。
        response->set_message(request->message());

        // 对应的idl方法没有返回值,不需要像下面方法中那样set_idl_result()。
        // 可以看到这个方法和其他protobuf服务没有差别,所以这个服务也可以被ubrpc之外的协议访问。
    }

    virtual void EchoWithMultiArgs(google::protobuf::RpcController* cntl_base,
                                   const MultiRequests* request,
                                   MultiResponses* response,
                                   google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);

        // 填充response。response是我们定义的包含所有idl response的消息。
        response->mutable_res1()->set_message(request->req1().message());
        response->mutable_res2()->set_message(request->req2().message());

        // 告诉RPC有多个request和response。
        cntl->set_idl_names(brpc::idl_multi_req_multi_res);

        // 对应idl方法的返回值。
        cntl->set_idl_result(17);
    }
};

设置ServerOptions.nshead_service

#include <brpc/ubrpc2pb_protocol.h>
...
brpc::ServerOptions option;
option.nshead_service = new brpc::policy::UbrpcCompackAdaptor; // mcpack2用UbrpcMcpack2Adaptor

例子见example/echo_c++_ubrpc_compack。

使用nshead+blob的服务

NsheadService是brpc中所有处理nshead打头协议的基类,实现好的NsheadService实例必须赋值给ServerOptions.nshead_service才能发挥作用。如果不赋值,默认为NULL,代表不支持任何nshead开头的协议,当这个server被nshead开头的数据包访问时会报错。显然,一个Server只能处理一种以nshead开头的协议。

NsheadService的接口如下,基本上用户只需要实现ProcessNsheadRequest这个函数。

// 代表一个nshead请求或回复。
struct NsheadMessage {
    nshead_t head;
    butil::IOBuf body;
};

// 实现这个类并赋值给ServerOptions.nshead_service来让brpc处理nshead请求。
class NsheadService : public Describable {
public:
    NsheadService();
    NsheadService(const NsheadServiceOptions&);
    virtual ~NsheadService();

    // 实现这个方法来处理nshead请求。注意这个方法可能在调用时controller->Failed()已经为true了。
    // 原因可能是Server.Stop()被调用正在退出(错误码是brpc::ELOGOFF)
    // 或触发了ServerOptions.max_concurrency(错误码是brpc::ELIMIT)
    // 在这种情况下,这个方法应该通过返回一个代表错误的response让客户端知道这些错误。
    // Parameters:
    //   server      The server receiving the request.
    //   controller  Contexts of the request.
    //   request     The nshead request received.
    //   response    The nshead response that you should fill in.
    //   done        You must call done->Run() to end the processing, brpc::ClosureGuard is preferred.
    virtual void ProcessNsheadRequest(const Server& server,
                                      Controller* controller,
                                      const NsheadMessage& request,
                                      NsheadMessage* response,
                                      NsheadClosure* done) = 0;
};

完整的示例位于example/nshead_extension_c++。

使用 nshead+mcpack/compack/idl 的服务

idl 是 mcpack/compack 的前端,用户只要在 idl 文件中描述 schema,就可以生成一些 C++ 结构体,这些结构体可以打包为 mcpack/compack。如果你的服务仍在大量地使用 idl 生成的结构体,且短期内难以修改,同时想要使用 brpc 提升性能和开发效率的话,可以实现NsheadService,其接口接受 nshead + 二进制包作为 request,用户填写自己的处理逻辑,最后的 response 也是 nshead + 二进制包。流程与 protobuf 方法保持一致,但过程中不涉及任何 protobuf 的序列化和反序列化,用户可以自由地解析 nshead 之后的二进制包,包括用 idl 加载 mcpack/compack 数据包。

不过,你应当充分意识到这么改造的坏处:

这个服务继续使用 mcpack/compack 作为序列化格式,相比 protobuf 会占用成倍的带宽和打包时间。

为了解决这个问题,我们提供了mcpack2pb,允许把 protobuf 作为 mcpack/compack 的前端。你只要写一份 proto 文件,就可以同时解析 mcpack/compack 和 protobuf 格式的请求。使用这种方式,用 idl 描述的服务可以平滑地改造为用 proto 文件描述,而不用修改上游 client(仍然使用 mcpack/compack)。你产品线的服务可以逐个地从 mcpack/compack/idl 切换为 protobuf,从而享受到性能提升、带宽节省、全新开发体验等好处。你可以自行在 NsheadService 中使用 src/mcpack2pb,也可以联系我们,获得更高质量的协议支持。

使用 nshead+protobuf 的服务

如果你的协议已经使用了 nshead + protobuf,或者你想把你的协议适配为 protobuf 格式,那可以使用另一种模式:实现NsheadPbServiceAdaptor(NsheadService 的子类)。

工作步骤:

  • 调用 ParseNsheadMeta() 解析 nshead 头部,用户必须在回调中告知 RPC 应调用哪个 pb 方法。
  • 调用 ParseRequestFromIOBuf() 将 nshead 头部之后的 body 转换为 pb 请求,然后调用该 pb 方法。
  • 当用户调用 server 的 done 以结束 RPC 时,SerializeResponseToIOBuf() 会被调用,将 pb 响应转换为二进制数据,这些数据会被追加在 nshead 头部之后并发送回客户端。

这样做的好处是,这个服务还可以被其他使用 protobuf 的协议访问,比如 baidu_std、hulu_pbrpc、sofa_pbrpc 协议等等。NsheadPbServiceAdaptor 的主要接口如下。完整的示例位于这里。

class NsheadPbServiceAdaptor : public NsheadService {
public:
    NsheadPbServiceAdaptor() : NsheadService(
        NsheadServiceOptions(false, SendNsheadPbResponseSize)) {}
    virtual ~NsheadPbServiceAdaptor() {}

    // Fetch meta from `nshead_req' into `meta'.
    // Params:
    //   server: where the RPC runs.
    //   nshead_req: the nshead request that server received.
    //   controller: If something goes wrong, call controller->SetFailed()
    //   meta: Set meta information into this structure. `full_method_name'
    //         must be set if controller is not SetFailed()-ed
    // FIXME: server is not needed anymore, controller->server() is same
    virtual void ParseNsheadMeta(const Server& server,
                                 const NsheadMessage& nshead_req,
                                 Controller* controller,
                                 NsheadMeta* meta) const = 0;
    // Transform `nshead_req' to `pb_req'.
    // Params:
    //   meta: was set by ParseNsheadMeta()
    //   nshead_req: the nshead request that server received.
    //   controller: you can set attachment into the controller. If something
    //               goes wrong, call controller->SetFailed()
    //   pb_req: the pb request should be set by your implementation.
    virtual void ParseRequestFromIOBuf(const NsheadMeta& meta,
                                       const NsheadMessage& nshead_req,
                                       Controller* controller,
                                       google::protobuf::Message* pb_req) const = 0;
    // Transform `pb_res' (and controller) to `nshead_res'.
    // Params:
    //   meta: was set by ParseNsheadMeta()
    //   controller: If something goes wrong, call controller->SetFailed()
    //   pb_res: the pb response that returned by pb method. [NOTE] `pb_res'
    //           can be NULL or uninitialized when RPC failed (indicated by
    //           Controller::Failed()), in which case you may put error
    //           information into `nshead_res'.
    //   nshead_res: the nshead response that will be sent back to client.
    virtual void SerializeResponseToIOBuf(const NsheadMeta& meta,
                                          Controller* controller,
                                          const google::protobuf::Message* pb_res,
                                          NsheadMessage* nshead_res) const = 0;
};

最后修改于 2022 年 5 月 17 日:update brpc users page (devlive-community/knowforge#71) (a31ce10d3)

评论

登录后参与评论

正在加载评论…