《对brpc信道的封装》

一、信道

在 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. 核心设计思想

  1. ServiceChannel 类:
    负责管理同一服务的多个节点信道。每个服务可能部署在多个节点(如分布式系统中的多实例),该类通过 append/remove 维护节点的上下线,并提供基于轮询(RR)策略的 choose 方法,从可用节点中选择一个信道用于 RPC 调用。
  2. 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;
}
相关推荐
寻寻觅觅☆10 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
fpcc11 小时前
并行编程实战——CUDA编程的Parallel Task类型
c++·cuda
ceclar12312 小时前
C++使用format
开发语言·c++·算法
lanhuazui1012 小时前
C++ 中什么时候用::(作用域解析运算符)
c++
charlee4412 小时前
从零实现一个生产级 RAG 语义搜索系统:C++ + ONNX + FAISS 实战
c++·faiss·onnx·rag·语义搜索
老约家的可汗13 小时前
初识C++
开发语言·c++
crescent_悦13 小时前
C++:Product of Polynomials
开发语言·c++
小坏坏的大世界14 小时前
CMakeList.txt模板与 Visual Studio IDE 操作对比表
c++·visual studio
乐观勇敢坚强的老彭14 小时前
c++寒假营day03
java·开发语言·c++
愚者游世14 小时前
brace-or-equal initializers(花括号或等号初始化器)各版本异同
开发语言·c++·程序人生·面试·visual studio