brpc 介绍与使用
一、什么是 rpc?
RPC(Remote Procedure Call,远程过程调用) 是一种跨网络的函数调用技术 。它的核心目标是:让一台计算机上的程序,能够像调用本地函数一样,直接调用另一台计算机上的函数,而开发者无需手动编写网络通信的细节代码。
RPC 主要应用在分布式架构中,由于引用分布式架构产生了一系列新的问题,而 RPC 正是解决这些问题的关键。
-
服务发现:RPC 集成服务注册中心(如 etcd),客户端自动从注册中心获取健康的服务实例地址。
-
高可用:只需关注业务逻辑(我该调用哪个函数,传入什么参数,拿到什么结果)。至于网络通信、错误重试等复杂问题,全部由 RPC 框架解决。
-
数据一致性:RPC支持跨语言的高性能序列化协议(如 Protobuf),保证数据格式统一且高效。
RPC 解决的核心问题是让构建分布式系统变得像编写本地程序一样简单。
二、什么是 brpc?
bRPC 是一个由百度开源的高性能 RPC(远程过程调用)框架 ,全称是 better RPC (曾被称为 baidu-rpc)。它用 C++ 编写,旨在帮助开发者快速构建高性能、高可靠 的分布式系统。
三、brpc 安装
先安装依赖
bash
sudo apt-get install -y git g++ make libssl-dev libprotobuf-dev libprotoc-dev protobuf-compiler libleveldb-dev protobuf-compiler-grpc
安装 brpc
bash
git clone https://github.com/apache/brpc.git
cd brpc/
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr .. && cmake --build . -j6
make && sudo make install
四、brpc 常见类与接口
4.1 日志输出类与接口
头文件:#include <butil/logging.h>
brpc 库内部有自己的日志输出模块,主要是禁用日志输出,避免被 brpc 的日志干扰。
cpp
namespace logging {
enum LoggingDestination {
LOG_TO_NONE = 0
};
struct BUTIL_EXPORT LoggingSettings {
LoggingSettings();
LoggingDestination logging_dest;
};
bool InitLogging(const LoggingSettings& settings);
}
关闭日志输出
cpp
#include <butil/logging.h>
logging::LoggingSettings logging_setting;
logging_setting.logging_dest = logging::LOG_TO_NONE;
logging::InitLogging(logging_setting);
4.2 protobuf 类与接口
Closure 是 Protobuf 定义的回调接口,RPC 服务实现必须在处理结束时调用其 Run() 方法,以触发框架完成响应的序列化与网络发送。
brpc::ClosureGuard 是一个基于 RAII 的守卫类,无论函数如何退出,都会自动调用 Closure 的 Run 方法。
RpcController:是 RPC 调用的上下文载体,维护其成功或失败状态
-
服务端:
RpcController的Failed()状态由框架自动维护,服务端只需在需要返回业务错误时才主动调用SetFailed(),其他异常框架会自动填充。 -
客户端:通过
RpcController检查调用结果。
inline Closure* NewCallback(void (*function)()); 只有在 创建异步 RPC 客户端 时才需要 Closure,用于指定 RPC 完成后的回调函数;同步调用传 nullptr 即可。
cpp
namespace google {
namespace protobuf {
class PROTOBUF_EXPORT Closure {
public:
Closure() {}
virtual ~Closure();
virtual void Run() = 0;
};
inline Closure* NewCallback(void (*function)());
class PROTOBUF_EXPORT RpcController {
bool Failed();
std::string ErrorText();
}
}
}
namespace brpc {
class ClosureGuard {
explicit ClosureGuard(google::protobuf::Closure* done);
~ClosureGuard() { if (_done) _done->Run(); }
}
}
4.3 服务端类与接口
头文件:#include <brpc/server.h>
brpc::Server 负责:监听端口、接收请求、调度到对应的 Service、发送响应。
cpp
namespace brpc {
struct ServerOptions {
//⽆数据传输,则指定时间后关闭连接
int idle_timeout_sec; // Default: -1 (长连接)
int num_threads; // Default: #cpu-cores
//....
}
enum ServiceOwnership {
//添加服务失败时,服务器将负责删除服务对象
SERVER_OWNS_SERVICE,
//添加服务失败时,服务器也不会删除服务对象,调用者负责删除服务对象
SERVER_DOESNT_OWN_SERVICE
};
class Server {
public:
// 添加服务
int AddService(google::protobuf::Service* service, ServiceOwnership ownership);
// 启动服务
int Start(int port, const ServerOptions* opt);
int Stop(int closewait_ms/*not used anymore*/);
int Join();
// 阻塞等待直到 ctrl+c 按下,或者 stop 和 join 服务器
void RunUntilAskedToQuit();
}
}
4.4 客户端类与接口
头文件:#include <brpc/channel.h>
brpc::Channel 是 bRPC 客户端的通信通道,负责:管理连接、序列化请求、发送 RPC、接收响应。
创建异步客户端调用时需要 NewCallback 将回调函数包装成 Closure,以便 RPC 完成后自动调用;同步调用传 NULL 即可。
cpp
namespace brpc {
enum ProtocolType : int {
PROTOCOL_UNKNOWN = 0,
PROTOCOL_BAIDU_STD = 1,
PROTOCOL_STREAMING_RPC = 2,
PROTOCOL_HULU_PBRPC = 3,
PROTOCOL_SOFA_PBRPC = 4,
PROTOCOL_RTMP = 5,
PROTOCOL_THRIFT = 6,
PROTOCOL_HTTP = 7,
PROTOCOL_PUBLIC_PBRPC = 8
// ...
}
struct ChannelOptions {
//请求连接超时时间
int32_t connect_timeout_ms; // Default: 200 (milliseconds)
//rpc请求超时时间
int32_t timeout_ms; // Default: 500 (milliseconds)
//最⼤重试次数
int max_retry; // Default: 3
//序列化协议类型 options.protocol = "baidu_std";
AdaptiveProtocolType protocol;
//....
}
class Channel : public ChannelBase {
// 初始化信道,连接目标服务器,成功返回0;
int Init(
const char* server_addr_and_port, //192.168.xx.xx:9000
const ChannelOptions* options
);
// 调用 RPC 方法(由 Stub 内部调用)
void CallMethod(const google::protobuf::MethodDescriptor* method,
google::protobuf::RpcController* controller,
const google::protobuf::Message* request,
google::protobuf::Message* response,
google::protobuf::Closure* done);
};
inline ::google::protobuf::Closure* NewCallback(void (*function)());
class Controller : public google::protobuf::RpcController {
public:
void set_timeout_ms(int64_t timeout_ms);
void set_max_retry(int max_retry);
void Reset();
bool Failed();
std::string ErrorText();
}
}
五、brpc 使用案例
5.1 搭建同步 rpc 客户端与服务器
定义 cal.proto 文件,并编译 cal.proto
proto
syntax = "proto3";
package cal;
option cc_generic_services = true;
message CalculateRequest {
int32 x = 1;
int32 y = 2;
}
message CalculateResponse {
int32 result = 1;
}
service CalculateService {
rpc calculate(CalculateRequest) returns (CalculateResponse);
}
rpc_server.cc
在 RPC 代码中,Stub 和 Impl(或 ServiceImpl)分别代表 RPC 通信的两端:
-
Stub(存根/代理):客户端的代理人。它负责伪装成远程服务,让你在本地像调用普通函数一样调用它,而它内部则默默地帮你把请求打包、发送给服务器,并等待结果返回。
-
Impl / ServiceImpl(服务实现):服务端真正的执行者。它是一个真正的、包含业务逻辑的类。服务器收到请求后,会调用这个类的对应方法来执行计算,并返回结果。
server.AddService(&calculate_service... 这个服务对象就是用于告诉服务器,哪个请求应该用哪个函数进行业务处理。
cpp
#include <butil/logging.h>
#include <brpc/server.h>
#include "cal.pb.h"
#include <iostream>
class CalculateServiceImpl : public cal::CalculateService {
public:
void calculate
(
::google::protobuf::RpcController* controller,
const ::cal::CalculateRequest* request, // 客户端的请求信息
::cal::CalculateResponse* response, // 业务处理完毕后填充响应信息
::google::protobuf::Closure* done // 很多 rpc 框架中都支持异步操作,业务处理的过程并非要放到calculate函数中实现,本次请求的结束与否,不由calculate函数的生命周期决定,只有当 done->Run() 的时候才表示处理完毕
) override {
// 函数需要用户自己实现:业务处理
brpc::ClosureGuard done_guard(done);
response->set_result(request->x() + request->y());
}
};
int main() {
// 0.关闭brpc的日志输出
logging::LoggingSettings logging_setting;
logging_setting.logging_dest = logging::LOG_TO_NONE;
logging::InitLogging(logging_setting);
// 1.实例化计算服务对象
CalculateServiceImpl calculate_service;
// 2.定义服务器配置对象
brpc::ServerOptions options;
options.idle_timeout_sec = -1;
// 3.实例化服务器对象
brpc::Server server;
// 4.向服务器添加服务
int res = server.AddService(&calculate_service , brpc::SERVER_DOESNT_OWN_SERVICE);
if(res == -1) {
std::cout << "addService failed" << std::endl;
return -1;
}
// 5.启动服务器
server.Start(9000 , &options);
// 6.等待服务器停止
server.RunUntilAskedToQuit();
return 0;
}
rpc_client.cc
cpp
#include <brpc/channel.h>
#include "cal.pb.h"
int main(int argc, char *argv[])
{
// 0. 实例化ChannelOptions进行参数配置
brpc::ChannelOptions options;
options.protocol = "baidu_std";
// 1. 实例化Channel信道对象
brpc::Channel chennel;
chennel.Init("172.16.168.129:9000", &options);
// 3. 实例化CalService_stub对象--用于客户端发起rpc远程调用请求。
cal::CalculateService_Stub stub(&chennel);
brpc::Controller* cntl = new brpc::Controller;
cal::CalculateRequest* request = new cal::CalculateRequest;
cal::CalculateResponse* response = new cal::CalculateResponse;
request->set_x(1024);
request->set_y(2048);
/*
void calculate(::PROTOBUF_NAMESPACE_ID::RpcController* controller, // 对于客户端判断 rpc 请求是否成功
const ::cal::CalculateRequest* request, // 客户端请求信息
::cal::CalculateResponse* response, // 服务端的响应信息
::google::protobuf::Closure* done); // 若 done 设置为 nullptr ,则表示本次请求时同步阻塞,否则可以实例化 done 对象,设置回调函数,进行异步非阻塞调用
*/
stub.calculate(cntl, request, response, nullptr);
if (cntl->Failed() == true) {
std::cout << "rpc请求失败: " << cntl->ErrorText() << std::endl;
return -1;
}
std::cout << response->result() << std::endl;
delete cntl;
delete request;
delete response;
getchar();
return 0;
}
Makefile
makefile
.PHONY:all
all: rpc_client rpc_server
rpc_server: rpc_server.cc cal.pb.cc
g++ -std=c++17 -o $@ $^ -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags
rpc_client: rpc_client.cc cal.pb.cc
g++ -std=c++17 -o $@ $^ -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags
%.pb.cc: %.proto
protoc --cpp_out=. $^
.PHONY:clean
clean:
rm -f rpc_server rpc_client
5.2 搭建异步 rpc 客户端与服务器
async_rpc_server.cc
cpp
#include <butil/logging.h>
#include <brpc/server.h>
#include <iostream>
#include <thread>
#include "cal.pb.h"
class CalculateServiceImpl : public cal::CalculateService {
public:
void calculate
(
::google::protobuf::RpcController* controller,
const ::cal::CalculateRequest* request,
::cal::CalculateResponse* response,
::google::protobuf::Closure* done
) override {
// 创建一个线程来完成异步操作
std::thread t([=](){
brpc::ClosureGuard done_guard(done);
response->set_result(request->x() + request->y());
std::this_thread::sleep_for(std::chrono::seconds(3));
});
t.detach();
std::cout << "==================" << std::endl;
}
};
int main() {
// 0.关闭brpc的日志输出
logging::LoggingSettings logging_setting;
logging_setting.logging_dest = logging::LOG_TO_NONE;
logging::InitLogging(logging_setting);
// 1.实例化计算服务对象
CalculateServiceImpl calculate_service;
// 2.定义服务器配置对象
brpc::ServerOptions options;
options.idle_timeout_sec = -1;
// 3.实例化服务器对象
brpc::Server server;
// 4.向服务器添加服务
int res = server.AddService(&calculate_service , brpc::SERVER_DOESNT_OWN_SERVICE);
if(res == -1) {
std::cout << "addService failed" << std::endl;
return -1;
}
// 5.启动服务器
server.Start(9000 , &options);
// 6.等待服务器停止
server.RunUntilAskedToQuit();
return 0;
}
async_rpc_client
cpp
#include <brpc/channel.h>
#include <brpc/callback.h>
#include "cal.pb.h"
void callback(
brpc::Controller* cntl,
cal::CalculateRequest* request,
cal::CalculateResponse* response
) {
std::shared_ptr<brpc::Controller> cntl_guard(cntl);
std::shared_ptr<cal::CalculateRequest> request_guard(request);
std::shared_ptr<cal::CalculateResponse> response_guard(response);
if (cntl_guard->Failed() == true) {
std::cout << "rpc请求失败: " << cntl_guard->ErrorText() << std::endl;
return;
}
std::cout << response_guard->result() << std::endl;
}
int main(int argc, char *argv[])
{
// 0. 实例化ChannelOptions进行参数配置b
brpc::ChannelOptions options;
options.protocol = "baidu_std";
// 1. 实例化Channel信道对象
brpc::Channel chennel;
chennel.Init("172.16.168.129:9000", &options);
// 3. 实例化CalService_stub对象--用于发起rpc请求。
cal::CalculateService_Stub stub(&chennel);
brpc::Controller* cntl = new brpc::Controller;
cntl->set_timeout_ms(4000);
cal::CalculateRequest* request = new cal::CalculateRequest;
cal::CalculateResponse* response = new cal::CalculateResponse;
request->set_x(1024);
request->set_y(2048);
google::protobuf::Closure* closure = brpc::NewCallback(callback , cntl , request , response);
stub.calculate(cntl, request, response, closure);
std::cout << "=========" << std::endl;
getchar();
return 0;
}
Makefile
makefile
.PHONY:all
all: rpc_client rpc_server async_rpc_server async_rpc_client
rpc_server: rpc_server.cc cal.pb.cc
g++ -std=c++17 -o $@ $^ -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags
rpc_client: rpc_client.cc cal.pb.cc
g++ -std=c++17 -o $@ $^ -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags
async_rpc_server: async_rpc_server.cc cal.pb.cc
g++ -std=c++17 -o $@ $^ -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags
async_rpc_client: async_rpc_client.cc cal.pb.cc
g++ -std=c++17 -o $@ $^ -lbrpc -lleveldb -lprotobuf -lpthread -ldl -lssl -lcrypto -lgflags
%.pb.cc: %.proto
protoc --cpp_out=. $^
.PHONY:clean
clean:
rm -f rpc_server rpc_client async_rpc_client async_rpc_server
解决 brpc::NewCallback 不能传递 lambda 表达式
cpp
struct CallbackContainer {
std::function<void()> callback;
};
// 异步函数,引用可能会导致悬空引用问题
void callback(CallbackContainer cc) {
cc.callback();
}
int main() {
//...
CallbackContainer cc;
cc.callback = [=](){
std::shared_ptr<brpc::Controller> cntl_guard(cntl);
std::shared_ptr<cal::CalculateRequest> request_guard(request);
std::shared_ptr<cal::CalculateResponse> response_guard(response);
if (cntl_guard->Failed() == true) {
std::cout << "rpc请求失败: " << cntl_guard->ErrorText() << std::endl;
return;
}
std::cout << response_guard->result() << std::endl;
};
google::protobuf::Closure* closure = brpc::NewCallback(callback , std::move(cc));
return 0;
}
-
值接收 + std::move :会发生移动构造,
std::move(cc)将cc转换为右值,把cc内部的资源转移到NewCallback的参数中。 -
右值引用接收 + std::move :不会发生移动,
std::move只是将左值转换为右值引用类型,并没有真正移动任何东西。