一、信道
在 BRPC 中,信道(Channel) 是客户端与服务端通信的核心组件,负责管理网络连接、协议解析、请求发送 / 响应接收等底层逻辑,是隐藏网络细节、让开发者像调用本地函数一样发起远程调用的关键。
简单说:信道封装了所有网络通信细节,客户端只需通过信道发起调用,无需关心 "数据如何发送到服务端""如何处理网络异常" 等问题。
1. brpc::ChannelOptions:信道的 "控制面板"
配置决定了信道的行为(如超时时间、重试次数等),常用参数如下:
| 参数名 | 含义 | 默认值 | 示例场景 |
|---|---|---|---|
| connect_timeout_ms | 建立 TCP 连接的超时时间(毫秒) | 200 | 设为 -1 表示永不超时(不推荐) |
| timeout_ms | RPC 调用总超时时间(从发请求到收响应,毫秒) | 500 | 长耗时调用可设为 3000(3 秒) |
| max_retry | 调用失败时的最大重试次数 | 3 | 非核心接口可设为 0 禁用重试 |
| protocol | 通信协议 | (如 baidu_std、http、redis) | baidu_std |
| load_balancer | 负载均衡策略(如 round_robin 轮询、consistent_hash 一致性哈希) | round_robin | 分布式服务选一致性哈希保证会话粘滞 |
| max_pending_tasks | 信道的最大待处理任务数(超过则拒绝新请求) | 100000 | 保护客户端不被过多请求压垮 |
2. brpc::Channel:信道实例
通过 Init 方法初始化后,即可用于发起 RPC 调用,核心方法如下:
| 方法 | 作用 |
|---|---|
| Init(server_addr, &options) | 初始化信道,连接到服务端(server_addr 为服务端地址,如 ip:port) |
| CallMethod(...) | 底层调用方法(通常通过 Stub 类间接使用,无需手动调 |
二、对信道封装
cpp
#pragma once
#include <brpc/channel.h>
#include <string>
#include <vector>
#include <unordered_map>
#include <mutex>
#include "logger.hpp"//自己封装的日志类
namespace bite_im{
// 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) {
//创捷新的brpc信道智能指针
auto channel = std::make_shared<brpc::Channel>();
brpc::ChannelOptions options; //信道配置选项
options.connect_timeout_ms = -1;//连接超时:-1表示不超时
options.timeout_ms = -1; //调用超时:-1表示不超时
options.max_retry = 3; //最大重试次数: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);
}
//服务下线了一个节点,将信道添加到管理容器中
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(Round-Robin,轮询)策略,获取一个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; //当前轮转下标计数器,用于RR策略
std::string _service_name; //服务名称
std::vector<ChannelPtr> _channels;//存储当前服务对应的信道的集合
//主机地址与信息映射关系(用于快速查找和删除下线节点的信道)
std::unordered_map<std::string, ChannelPtr> _hosts;
};
// 2. 总体的服务信道管理类:管理多个服务的信道,是上层对外的主要接口
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();
}
//调用对应服务的信道管理类的choose方法(轮询选择)
return sit->second->choose();
}
//声明需要关注的服务:只有声明关注的服务,才会进行上下线管理
void declared(const std::string &service_name) {
std::unique_lock<std::mutex> lock(_mutex);
_follow_service.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_service.find(service_name);
if (fit == _follow_service.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;
}
//调用ServiceChannel的append方法添加新节点
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_service.find(service_name);
if (fit == _follow_service.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;
}
//调用ServiceChannel的remove方法删除节点
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_service;
//服务名称到其信道管理实例的映射:管理多个服务的信道
std::unordered_map<std::string, ServiceChannel::ptr> _services;
};
}
1. 核心设计思想
- ServiceChannel 类:
负责管理同一服务的多个节点信道。每个服务可能部署在多个节点(如分布式系统中的多实例),该类通过 append/remove 维护节点的上下线,并提供基于轮询(RR)策略的 choose 方法,从可用节点中选择一个信道用于 RPC 调用。 - ServiceManager 类:
作为上层管理接口,负责管理多个不同服务的信道。通过 declared 方法指定需要关注的服务,再通过 onServiceOnline/onServiceOffline 响应服务节点的上下线事件,内部会为每个服务创建对应的 ServiceChannel 实例进行具体管理,最终通过 choose 方法间接获取指定服务的可用信道。
2. 服务信道管理类(ServiceChannel)
定位:管理单个服务的多个节点信道,是信道操作的最小单元;
核心功能:
| 方法 | 作用 |
|---|---|
| append(host) | 服务新增节点时,初始化该节点的 BRPC Channel 并加入管理(含连接配置,如不超时、重试 3 次、baidu_std协议); |
| remove(host) | 服务下线节点时,从信道集合中删除对应 Channel(通过主机地址快速查找并清理); |
| choose() | 基于 RR(轮询)策略选择一个可用 Channel(通过自增索引取模实现,保证节点负载均衡); |
成员变量:
- 线程安全:_mutex 互斥锁,保证多线程下信道增删、选择的安全性;
- 数据存储:_hosts(主机地址 - Channel 映射,用于快速删除)、_channels(Channel 列表,用于轮询);
- 状态记录:_index(轮询索引计数器)、_service_name(绑定的服务名称)。
3. 全局服务管理类(ServiceManager)
定位:管理多个服务的信道实例,是上层业务调用的统一入口;
核心功能:
| 方法 | 作用 |
|---|---|
| declared(service_name) | 声明业务关心的服务(仅声明过的服务才会被管理,过滤无关服务); |
| choose(service_name) | 为指定服务选择一个可用 Channel(调用对应 ServiceChannel 的 choose 方法); |
| onServiceOnline(...) | 服务节点上线回调:解析服务名称,检查是否为关注服务,若已关注则创建 / 复用 ServiceChannel 并添加节点; |
| onServiceOffline(...) | 服务节点下线回调:解析服务名称,检查是否为关注服务,若已关注则调用 ServiceChannel 移除节点; |
关键辅助:
- getServiceName(service_instance):从服务实例名(格式如 "服务名 / 实例名")中提取服务名称,支持按服务维度管理;
- 数据存储:_follow_services(关注的服务集合)、_services(服务名称 -ServiceChannel 映射,管理多服务信道)。
4. 对客户端进行改造
cpp
#include <brpc/channel.h>
#include <thread>
#include "main.pb.h"
#include "../../common/channel.hpp"
int main() {
auto service_mgr = std::make_shared<bite_im::ServiceManager>();
service_mgr->declared("/service");
service_mgr->onServiceOnline("127.0.0.1:8080", "/service");
while (1)
{
// 3. 通过Rpc信道管理对象,获取提供Echo服务的信道
auto channel = service_mgr->choose("/service");
if (!channel)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
// 4. 发起EchoRpc调用
example::EchoService_Stub stub(channel.get());
// 构造请求消息
example::EchoRequest req;
req.set_message("hello~world~!");
// 异步调用Echo方法
brpc::Controller *cntl = new brpc::Controller(); // 控制调用过程(如超时,错误)
example::EchoResponse *rsp = new example::EchoResponse(); // 接收响应
stub.Echo(cntl, &req, rsp, nullptr); // 同步调用
if (cntl->Failed())
{
std::cout << "Rpc调用失败:" << cntl->ErrorText() << std::endl;
delete cntl;
delete rsp;
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
std::cout << "收到响应: " << rsp->message() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
service_mgr->onServiceOffline("127.0.0.1:8080", "/service");
}
return 0;
}