目录
一.介绍
brpc 是用 c++语言编写的工业级 RPC 框架,常用于搜索、存储、机器学习、广告、推荐等高性能系统。
你可以使用它:
• 搭建能在一个端口支持多协议的服务, 或访问各种服务
- restful http/https, h2/gRPC。使用 brpc 的 http 实现比 libcurl 方便多了。从其他语言通过 HTTP/h2+json 访问基于 protobuf 的协议。
- redis 和 memcached, 线程安全,比官方 client 更方便。
- rtmp/flv/hls, 可用于搭建流媒体服务。
- 支持 thrift , 线程安全,比官方 client 更方便。
- 各种百度内使用的协议: baidu_std, streaming_rpc, hulu_pbrpc, sofa_pbrpc, nova_pbrpc, public_pbrpc, ubrpc 和使用 nshead 的各种协议。
- 基于工业级的 RAFT 算法实现搭建高可用分布式系统,已在 braft 开源。
• Server 能同步或异步处理请求。
• Client 支持同步、异步、半同步,或使用组合 channels 简化复杂的分库或并发访问。
• 通过 http 界面调试服务, 使用 cpu, heap, contention profilers。
• 获得更好的延时和吞吐。
• 把你组织中使用的协议快速地加入 brpc,或定制各类组件, 包括命名服务 (dns, zk, etcd), 负载均衡 (rr, random, consistent hashing)。
对比其他 rpc 框架
二.安装
先安装依赖
sudo apt-get install -y git g++ make libssl-dev libprotobuf-dev libprotoc-dev protobuf-compiler libleveldb-dev
安装 brpc
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
三.类与接口介绍
日志输出类与接口
包含头文件:
#include <butil/logging.h>
日志输出这里,本质上我们其实用不着 brpc 的日志输出,因此在这里主要介绍如何关闭日志输出。
namespace logging
{
enum LoggingDestination
{
LOG_TO_NONE = 0
};
struct BUTIL_EXPORT LoggingSettings
{
LoggingSettings();
LoggingDestination logging_dest;
};
bool InitLogging(const LoggingSettings& settings);
};
BUTIL_EXPORT是宏(用于符号导出 / 导入等逻辑)。
protobuf类与接口
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
{
struct ServerOptions
{
//无数据传输,则指定时间后关闭连接
int idle_timeout_sec; // Default: -1 (disabled)
int num_threads; // Default: #cpu-cores
//....
};
enum ServiceOwnership
{
//添加服务失败时,服务器将负责删除服务对象
SERVER_OWNS_SERVICE,
//添加服务失败时,服务器也不会删除服务对象
SERVER_DOESNT_OWN_SERVICE
};
class Server
{
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();
};
class ClosureGuard
{
explicit ClosureGuard(google::protobuf::Closure* done);
~ClosureGuard() { if (_done) _done->Run(); }
};
class HttpHeader
{
void set_content_type(const std::string& type)
const std::string* GetHeader(const std::string& key)
void SetHeader(const std::string& key,
const std::string& value);
const URI& uri() const { return _uri; }
HttpMethod method() const { return _method; }
void set_method(const HttpMethod method)
int status_code()
void set_status_code(int status_code);
};
class Controller : public google::protobuf::RpcController
{
void set_timeout_ms(int64_t timeout_ms);
void set_max_retry(int max_retry);
google::protobuf::Message* response();
HttpHeader& http_response();
HttpHeader& http_request();
bool Failed();
std::string ErrorText();
using AfterRpcRespFnType = std::function<
void(Controller* cntl,
const google::protobuf::Message* req,
const google::protobuf::Message* res)>;
void set_after_rpc_resp_fn(AfterRpcRespFnType&& fn)
};
};
客户端类与接口:
namespace brpc
{
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,
const ChannelOptions* options);
};
};
四.使用
同步调用
同步调用是指客户端会阻塞收到 server 端的响应或发生错误。
下面我们以 Echo(输出 hello world)方法为例, 来讲解基础的同步 RPC 请求是如何实现的。
创建 proto 文件 - main.proto:
syntax="proto3";
package example;
//给 Protobuf C++ 代码生成器的 “开关”,决定是否为 service 生成通用服务基类 / 存根代码
option cc_generic_services = true;
//定义Echo方法请求参数结构
message EchoRequest
{
string message = 1;
};
//定义Echo方法响应参数结构
message EchoResponse
{
string message = 1;
};
//定义RPC远端方法
service EchoService
{
rpc Echo(EchoRequest) returns (EchoResponse);
};
服务端源码:server.cc
#include <gflags/gflags.h>
#include <butil/logging.h>
#include <brpc/server.h>
#include "main.pb.h"
//使用gflags定义一些命令行参数
DEFINE_int32(listen_port, 8080, "服务器监听端口");
DEFINE_int32(idle_timeout_s, -1, "空闲连接超时关闭时间:默认-1表示不关闭");
DEFINE_int32(thread_count, 3, "服务器启动线程数量");
namespace example
{
class EchoServiceImpl : public EchoService
{
public:
EchoServiceImpl()
{}
~EchoServiceImpl()
{}
void Echo(google::protobuf::RpcController* controller,
const ::example::EchoRequest* request,
::example::EchoResponse* response,
::google::protobuf::Closure* done)
{
brpc::ClosureGuard rpc_guard(done);
std::cout << "收到消息:" << request->message() << std::endl;
std::string str = request->message() + "--这是响应!!";
response->set_message(str);
}
};
};
int main(int argc, char* argv[])
{
//关闭brpc日志输出
logging::LoggingSettings log_setting;
log_setting.logging_dest = logging::LoggingDestination::LOG_TO_NONE;
logging::InitLogging(log_setting);
//解析命令行参数
google::ParseCommandLineFlags(&argc, &argv, true);
//定义服务器
brpc::Server server;
//创建服务对象
example::EchoServiceImpl echo_service_impl;
//将服务添加到服务器中
if(server.AddService(&echo_service_impl, brpc::SERVER_DOESNT_OWN_SERVICE) != 0)
{
std::cout << "add service failed!\n";
return -1;
}
//开始运作服务器
brpc::ServerOptions options;
options.idle_timeout_sec = FLAGS_idle_timeout_s;
options.num_threads = FLAGS_thread_count;
if(server.Start(FLAGS_listen_port, &options) != 0)
{
std::cout << "failed to start EchoServer";
return -1;
}
//阻塞等待服务端运行
server.RunUntilAskedToQuit();
return 0;
}
客户端源码:client.cc
#include <gflags/gflags.h>
#include <butil/logging.h>
#include <butil/time.h> // ???
#include <brpc/channel.h>
#include <iostream>
#include "main.pb.h"
DEFINE_string(protocol, "baidu_std", "通信协议类型,默认使用brpc自定制协议");
DEFINE_string(server_host, "127.0.0.1:8080", "服务器地址信息");
DEFINE_int32(timeout_ms, 500, "Rpc 请求超时时间-毫秒");
DEFINE_int32(max_retry, 3, "请求重试次数");
int main(int argc, char *argv[])
{
google::ParseCommandLineFlags(&argc, &argv, true);
// 创建通道,可以理解为客户端到服务器的一条通信线路
brpc::Channel channel;
// 初始化通道,NULL 表示使用默认选项
brpc::ChannelOptions options;
options.protocol = FLAGS_protocol;
options.timeout_ms = FLAGS_timeout_ms;
options.max_retry = FLAGS_max_retry;
if (channel.Init(FLAGS_server_host.c_str(), &options) != 0)
{
LOG(ERROR) << "Fail to initialize channel";
return -1;
}
// 通常,我们不应直接调用通道,而是包装它的stub服务,通过 stub 进行rpc调用
example::EchoService_Stub stub(&channel);
while(true)
{
// 创建请求、响应、控制对象
example::EchoRequest request;
example::EchoResponse response;
brpc::Controller cntl;
// 构造请求响应
std::string s;
std::cin >> s;
request.set_message(s);
// 由于“done”(最后一个参数)为 NULL,表示阻塞等待响应
stub.Echo(&cntl, &request, &response, NULL);
if (cntl.Failed())
{
std::cout << "请求失败: " << cntl.ErrorText() << std::endl;
return -1;
}
std::cout << "响应:" << response.message() << std::endl;
}
return 0;
}
编写makefile:
all : server client
server : server.cc main.pb.cc
g++ -std=c++17 $^ -o $@ -lbrpc -lgflags -lssl -lcrypto -lleveldb -lpthread -lprotobuf
client : client.cc main.pb.cc
g++ -std=c++17 $^ -o $@ -lbrpc -lgflags -lssl -lcrypto -lleveldb -lpthread -lprotobuf
%.pb.cc : %.proto
protoc --cpp_out=./ $<
异步调用
异步调用是指客户端注册一个响应处理回调函数, 当调用一个 RPC 接口时立即返回,不会阻塞等待响应, 当 server 端返回响应时会调用传入的回调函数处理响应。
五.封装思想
rpc 调用这里的封装,因为不同的服务调用使用的是不同的 Stub,这个封装起来的意义不大,因此我们只需要封装通信所需的 Channel 管理即可,这样当需要进行什么样的服务调用的时候,只需要通过服务名称获取对应的 channel,然后实例化 Stub 进行调用即可。
• 封装 Channel 的管理,每个不同的服务可能都会有多个主机提供服务,因此一个服务可能会对应多个 Channel,需要将其管理起来,并提供获取指定服务 channel 的接口
- 进行 rpc 调用时,获取 channel,目前以 RR 轮转的策略选择 channel
• 提供进行服务声明的接口,因为在整个系统中,提供的服务有很多,但是当前可能并不一定会用到所有的服务,因此通过声明来告诉模块哪些服务是自己关心的,需要建立连接管理起来,没有添加声明的服务即使上线也不需要进行连接的建立。
• 提供服务上线时的处理接口,也就是新增一个指定服务的 channel。
• 提供服务下线时的处理接口,也就是删除指定服务下的指定 channel。
封装代码:
#pragma once
#include <brpc/channel.h>
#include <string>
#include <vector>
#include <unordered_map>
#include <mutex>
#include "logger.hpp"
namespace fish
{
// 1. 封装单个服务的信道管理类:
class ServiceChannel
{
public:
using ptr = std::shared_ptr<ServiceChannel>;
using ChannelPtr = std::shared_ptr<brpc::Channel>;
ServiceChannel(const std::string &name) : _service_name(name), _index(0) {}
// 服务上线了一个节点,则调用append新增信道
void append(const std::string &host)
{
ChannelPtr channel = std::make_shared<brpc::Channel>();
brpc::ChannelOptions options;
options.connect_timeout_ms = -1;
options.timeout_ms = -1;
options.max_retry = 3;
options.protocol = "baidu_std";
int ret = channel->Init(host.c_str(), &options);
if (ret == -1)
{
LOG_ERROR("初始化{}-{}信道失败!", _service_name, host);
return;
}
std::unique_lock<std::mutex> lock(_mutex);
_hosts.insert(std::make_pair(host, channel));
_channels.push_back(channel);
}
// 服务下线了一个节点,则调用remove释放信道
void remove(const std::string &host)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _hosts.find(host);
if (it == _hosts.end())
{
LOG_WARN("{}-{}节点删除信道时,没有找到信道信息!", _service_name, host);
return;
}
for (auto vit = _channels.begin(); vit != _channels.end(); ++vit)
{
if (*vit == it->second)
{
_channels.erase(vit);
break;
}
}
_hosts.erase(it);
}
// 通过RR轮转策略,获取一个Channel用于发起对应服务的Rpc调用
ChannelPtr choose()
{
std::unique_lock<std::mutex> lock(_mutex);
if (_channels.size() == 0)
{
LOG_ERROR("当前没有能够提供 {} 服务的节点!", _service_name);
return ChannelPtr();
}
int32_t idx = _index++ % _channels.size();
return _channels[idx];
}
private:
std::mutex _mutex;
int32_t _index; // 当前轮转下标计数器
std::string _service_name; // 服务名称
std::vector<ChannelPtr> _channels; // 当前服务对应的信道集合
std::unordered_map<std::string, ChannelPtr> _hosts; // 主机地址与信道映射关系
};
// 总体的服务信道管理类
class ServiceManager
{
public:
using ptr = std::shared_ptr<ServiceManager>;
ServiceManager() {}
// 获取指定服务的节点信道
ServiceChannel::ChannelPtr choose(const std::string &service_name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto sit = _services.find(service_name);
if (sit == _services.end())
{
LOG_ERROR("当前没有能够提供 {} 服务的节点!", service_name);
return ServiceChannel::ChannelPtr();
}
return sit->second->choose();
}
// 先声明,我关注哪些服务的上下线,不关心的就不需要管理了
void declared(const std::string &service_name)
{
std::unique_lock<std::mutex> lock(_mutex);
_follow_services.insert(service_name);
}
// 服务上线时调用的回调接口,将服务节点管理起来
void onServiceOnline(const std::string &service_instance, const std::string &host)
{
std::string service_name = getServiceName(service_instance);
ServiceChannel::ptr service;
{
std::unique_lock<std::mutex> lock(_mutex);
auto fit = _follow_services.find(service_name);
if (fit == _follow_services.end())
{
LOG_DEBUG("{}-{} 服务上线了,但是当前并不关心!", service_name, host);
return;
}
// 先获取管理对象,没有则创建,有则添加节点
auto sit = _services.find(service_name);
if (sit == _services.end())
{
service = std::make_shared<ServiceChannel>(service_name);
_services.insert(std::make_pair(service_name, service));
}
else
{
service = sit->second;
}
}
if (!service)
{
LOG_ERROR("新增 {} 服务管理节点失败!", service_name);
return;
}
service->append(host);
LOG_DEBUG("{}-{} 服务上线新节点,进行添加管理!", service_name, host);
}
// 服务下线时调用的回调接口,从服务信道管理中,删除指定节点信道
void onServiceOffline(const std::string &service_instance, const std::string &host)
{
std::string service_name = getServiceName(service_instance);
ServiceChannel::ptr service;
{
std::unique_lock<std::mutex> lock(_mutex);
auto fit = _follow_services.find(service_name);
if (fit == _follow_services.end())
{
LOG_DEBUG("{}-{} 服务下线了,但是当前并不关心!", service_name, host);
return;
}
// 先获取管理对象,没有则创建,有则添加节点
auto sit = _services.find(service_name);
if (sit == _services.end())
{
LOG_WARN("删除{}服务节点时,没有找到管理对象", service_name);
return;
}
service = sit->second;
}
service->remove(host);
LOG_DEBUG("{}-{} 服务下线节点,进行删除管理!", service_name, host);
}
private:
std::string getServiceName(const std::string &service_instance)
{
auto pos = service_instance.find_last_of('/');
if (pos == std::string::npos)
return service_instance;
return service_instance.substr(0, pos);
}
private:
std::mutex _mutex;
std::unordered_set<std::string> _follow_services; // 关心的服务集合
std::unordered_map<std::string, ServiceChannel::ptr> _services; // 服务名和这个服务的管理对象
};
};
六.封装类使用测试
add.proto:
syntax="proto3";
package example;
option cc_generic_services = true;
message AddRequest
{
int64 x = 1;
int64 y = 2;
};
message AddResponse
{
int64 result = 1;
};
service AddService
{
rpc Echo(AddRequest) returns (AddResponse);
};
client.cc:
#include "../../../common/channel.hpp"
#include "../../../common/etcd.hpp"
#include "add.pb.h"
int main(int argc, char *argv[])
{
// 关闭brpc日志输出
logging::LoggingSettings log_setting;
log_setting.logging_dest = logging::LoggingDestination::LOG_TO_NONE;
logging::InitLogging(log_setting);
fish::init_logger(0, "", 0);
fish::ServiceManager sm;
sm.declared("/service/user");
auto put_cb = std::bind(&fish::ServiceManager::onServiceOnline, &sm, std::placeholders::_1, std::placeholders::_2);
auto del_cb = std::bind(&fish::ServiceManager::onServiceOffline, &sm, std::placeholders::_1, std::placeholders::_2);
// fish::Discovery dis("http://127.0.0.1:2379", "/service/user", &fish::ServiceManager::onServiceOnline, &fish::ServiceManager::onServiceOffline);
fish::Discovery dis("http://127.0.0.1:2379", "/service/user", put_cb, del_cb);
auto channel = sm.choose("/service/user");
example::AddService_Stub stub(channel.get());
while (true)
{
brpc::Controller cntl;
example::AddRequest request;
int x, y;
std::cin >> x;
std::cin >> y;
request.set_x(x);
request.set_y(y);
example::AddResponse response;
stub.Echo(&cntl, &request, &response, nullptr);
std::cout << "计算结果是:" << response.result() << std::endl;
}
return 0;
}
server.cc:
#include "../../../common/etcd.hpp"
#include "add.pb.h"
#include <gflags/gflags.h>
#include <butil/logging.h>
#include <brpc/server.h>
DEFINE_int32(port, 8080, "提供服务的端口");
DEFINE_string(host, "127.0.0.1", "服务注册ip");
// 重写rpc调用方法
class AddServiceImp : public example::AddService
{
void Echo(google::protobuf::RpcController *controller,
const ::example::AddRequest *request,
::example::AddResponse *response,
::google::protobuf::Closure *done)
{
brpc::ClosureGuard rpc_guard(done);
response->set_result(request->x() + request->y());
LOG_DEBUG("收到请求,两个数分别是{}和{}", request->x(), request->y());
}
};
int main(int argc, char *argv[])
{
// 关闭brpc日志输出
logging::LoggingSettings log_setting;
log_setting.logging_dest = logging::LoggingDestination::LOG_TO_NONE;
logging::InitLogging(log_setting);
fish::init_logger(0, "", 0);
google::ParseCommandLineFlags(&argc, &argv, true);
fish::Registry etcdReg("127.0.0.1:2379"); // 服务注册对象
etcdReg.registry("/service/user/instance1", "127.0.0.1:8080"); // 注册
// 定义服务器
brpc::Server server;
// 创建服务对象
AddServiceImp add_service_impl;
// 将服务添加到服务器中
if (server.AddService(&add_service_impl, brpc::SERVER_DOESNT_OWN_SERVICE) != 0)
{
std::cout << "add service failed!\n";
return -1;
}
// 开始运作服务器
brpc::ServerOptions options;
options.idle_timeout_sec = -1;
options.num_threads = 1;
if (server.Start(FLAGS_port, &options) != 0)
{
std::cout << "failed to start EchoServer";
return -1;
}
// 阻塞等待服务端运行
server.RunUntilAskedToQuit();
return 0;
}
运行展示:
六.总结
本篇博客是关于brpc的安装与使用,供大家和本人在日常开发中进行参考使用。