Serve 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文件,下面是转化后的proto文件。
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);
};
BRPC_PATH代表brpc产出的路径(包含bin include等目录),PROTOBUF_INCLUDE_PATH代表protobuf的包含路径。注意–mcpack_out要和–cpp_out一致。
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::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);
}
};
例子见。
使用nshead+blob的服务
是brpc中所有处理nshead打头协议的基类,实现好的NsheadService实例得赋值给ServerOptions.nshead_service才能发挥作用。不赋值的话,默认是NULL,代表不支持任何nshead开头的协议,这个server被nshead开头的数据包访问时会报错。明显地,一个Server只能处理一种以nshead开头的协议。
NsheadService的接口如下,基本上用户只需要实现ProcessNsheadRequest
这个函数。
// 代表一个nshead请求或回复。
struct NsheadMessage {
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在example/nshead_extension_c++。
使用nshead+mcpack/compack/idl的服务
不过,你应当充分意识到这么改造的坏处:
为了解决这个问题,我们提供了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的子类)。
工作步骤:
- Call ParseNsheadMeta() to understand the nshead header, user must tell RPC which pb method to call in the callback.
- Call ParseRequestFromIOBuf() to convert the body after nshead header to pb request, then call the pb method.
- When user calls server’s done to end the RPC, SerializeResponseToIOBuf() is called to convert pb response to binary data that will be appended after nshead header and sent back to client.
Last modified May 16, 2023: