本篇将介绍RPC原理,以及如何基于bprc编写一个简单的Add服务。
PRC原理
RPC(Remote procedure call)的原理可以简述为下图,Client调用Server上的进程时,Client的调用进程将被挂起,而Server上的被调用进程开始执行,调用方将参数传到被调用过程,得到回传结果。

RPC通常借助stub来实现,stub是一组RPC机制的操作原语,这些原语构成了RPC的实现细节。

- Client调用本地stub中的一个过程,开始远程过程调用请求
- 这个stub过程把有关的参数组装成一个信息包或一组信息包,形成一条消息。运行此执行过程的远程场点的
IP地址
和执行该过程的进程ID号
也包含在这条消息中。 - stub把这条消息发送给对应的RPC runtime(RPC运行库)子程序,由这个子程序将详细发送到远程场点。
- Server端的RPC runtime子程序在接收到这条消息时,引用与被调用者对应的stub中的一个子程序,并让它来处理消息。
- 与被调用者对应的stub中的这个子程序卸载信息,解析出相关参数,并用本地调用方式执行所指定的过程。
- 返回调用结果,调用者对应的stub子程序执行return语句返回到Client,整个RPC过程结束。
brpc
brpc是百度开源的工业级RPC框架,支持多种传输协议,目前只开源了C++版本。
在bprc中,参数传递使用protobuf协议,避免了指针类参数等问题。
如何使用brpc
下面将以Add Service为例,来展示如何使用brpc。
Client的调用进程调用Server中的Add过程后被挂起,直到收到调用结构,参数为一组整数。Server计算这组整数的和,并回传调用结果。
编译brpc
根据快速文档编译brpc,以macOS用户为例:
brew install openssl git gnu-getopt coreutils
brew install gflags protobuf leveldb
git clone https://github.com/apache/incubator-brpc
cd incubator-brpc
sh config_brpc.sh --headers=/usr/local/include --libs=/usr/local/lib --cc=clang --cxx=clang++
make -j10
定义Service
brpc中将远程过程称为Service,采用protobuf协议。
这里Request参数为Client调用远程过程的参数(一组整数),Response参数为Server过程的调用结果(一组整数的和),AddService即是所定义的远程过程。
通过protoc --cpp_out=. --proto_path=. add.proto
命令即可生成对应的头文件add.pb.h
,包含AddRequest、AddResponse,以及AddService_Stub等相关的定义。
syntax="proto2";
option cc_generic_services = true;
message AddRequest {
repeated int32 adds = 1;
};
message AddResponse {
required int32 result = 1;
};
service AddService {
rpc Add(AddRequest) returns (AddResponse);
};
Client端
#include <brpc/channel.h>
#include "add.pb.h"
int main(int argc, char* argv[]) {
brpc::Channel channel;
// Initialize the channel
brpc::ChannelOptions options;
options.protocol = "baidu_std";
options.connection_type = "";
options.timeout_ms = 100;
options.max_retry = 3;
std::string endpoints = "0.0.0.0:8000";
std::string load_balancer = ""; // single server
if (channel.Init(endpoints.c_str(), load_balancer.c_str(), &options) != 0) {
std::cerr << "Fail to initialize channel" << std::endl;
return -1;
}
// Create stub
AddService_Stub stub(&channel);
AddRequest request;
AddResponse response;
brpc::Controller cntl;
cntl.request_attachment().append("");
// Pack params
for (int i = 0; i < 100; ++i) {
request.add_adds(i);
}
// Start Add service
stub.Add(&cntl, &request, &response, NULL);
if (!cntl.Failed()) {
std::cout << "get: " << response.result() << std::endl;
} else {
std::cerr << "get: failed" << std::endl;
}
return 0;
}
在bprc中没有Client对应的实体,取而代之的是brpc::Channel
,代表和一台或一组服务器的交互通道。在实践中中Client和Channel的角色基本没有差别,可以将Channel视作Client。Channel的创建和初始化并不是线程安全的,初始化结束后可以被多线程共用。
Client端将相关参数打包好后,通过调用本地stub中的Add过程,来执行Server端的AddService。
tub.Add(&cntl, &request, &response, NULL)
的最后一个参数表示本次调用为同步调用。
Server端
#include <brpc/server.h>
#include "add.pb.h"
// implementation of AddService
class AddServiceImpl : public AddService {
public:
AddServiceImpl() {};
virtual ~AddServiceImpl() {};
virtual void Add(google::protobuf::RpcController* cntl_base,
const AddRequest* request,
AddResponse* response,
google::protobuf::Closure* done) {
brpc::ClosureGuard done_guard(done);
brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
std::cout << "request form [" << cntl->remote_side()
<< "] to [" << cntl->local_side() << "]: ";
int result = 0;
// Parse request and calculate call result
for (int i = 0; i < request->adds_size(); ++i) {
std::cout << request->adds(i) << ", ";
result += request->adds(i);
}
std::cout << std::endl;
// Pack result
response->set_result(result);
}
};
int main(int argc, char* argv[]) {
brpc::Server server;
AddServiceImpl add_service_impl;
// Add the service into server.
if (server.AddService(&add_service_impl,
brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
std::cerr << "Fail to add service" << std::endl;
return -1;
}
// Start the server.
brpc::ServerOptions options;
options.idle_timeout_sec = -1;
if (server.Start(8000, &options) != 0) {
std::cerr << "Fail to start EchoServer" << std::endl;
return -1;
}
server.RunUntilAskedToQuit();
return 0;
}
Server端添加Add Service后启动Server,等待被调用。当接收到Client端stub发来的消息时,解析request参数,执行相关过程,最后将结果打包回传。
运行结果
Server端:
» ./server
I0412 15:31:48 775 src/brpc/server.cpp:1045] Server[AddServiceImpl] is serving on port=8000.
I0412 15:31:48 775 src/brpc/server.cpp:1048] Check out http://barrierys-MBP.local:8000 in web browser.
request form [127.0.0.1:51830] to [127.0.0.1:8000]: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
Client端:
» ./client
get: 4950