
📚 系列:C++ 工程实践 第 10 篇 ⏱ 阅读约 13 分钟
📌 一句话总结
上一篇讲 brpc 怎么调通,这一篇讲怎么在业务里用得下去:用四个小类把「节点管理、选路、异步回调、起服务」收口,业务代码只写服务名、节点和业务 lambda。
文章目录
-
- [1. 先看结果:封装后,一次异步调用长什么样](#1. 先看结果:封装后,一次异步调用长什么样)
- [2. 为什么封装:四个类各解决什么](#2. 为什么封装:四个类各解决什么)
- [3. Channels:一个服务的多节点选路](#3. Channels:一个服务的多节点选路)
- [4. SvcChannels:服务名 → Channels](#4. SvcChannels:服务名 → Channels)
- [5. ClosureFactory:把 lambda 变成 brpc Closure](#5. ClosureFactory:把 lambda 变成 brpc Closure)
- [6. RpcServerFactory 与使用样例](#6. RpcServerFactory 与使用样例)
- [7. 踩坑与总结](#7. 踩坑与总结)
1. 先看结果:封装后,一次异步调用长什么样
封装的目标不是「更高级」,而是让业务代码里不再出现 Channel、Controller 的样板。 上一篇的异步调用要自己 new 出 request/response/cntl、写 NewCallback;封装后是这样:
cpp
#include "rpc.h"
#include "cal.pb.h"
#include <iostream>
#include <memory>
int main() {
rpcwrap::SvcChannels scs;
scs.setWatch("cal"); // 关注哪个服务
scs.addNode("cal", "127.0.0.1:9000"); // 节点从配置/服务发现来
auto channel = scs.getNode("cal"); // 轮流拿一个可用节点
if (!channel) { std::cerr << "no node\n"; return -1; }
auto* cntl = new brpc::Controller;
auto* req = new cal::AddReq;
req->set_num1(22);
req->set_num2(333);
auto* rsp = new cal::AddRsp;
auto closure = rpcwrap::ClosureFactory::create([=]() {
std::unique_ptr<brpc::Controller> c(cntl);
std::unique_ptr<cal::AddReq> q(req);
std::unique_ptr<cal::AddRsp> s(rsp);
if (c->Failed()) { std::cerr << c->ErrorText() << "\n"; return; }
std::cout << "22 + 333 = " << s->result() << std::endl;
});
cal::CalService_Stub stub(channel.get());
stub.Add(cntl, req, rsp, closure);
std::cout << "异步请求已发出\n";
std::cin.get(); // 演示:等回调
return 0;
}
和上一篇直接调 brpc 比,差别在「样板去哪了」:
| 事情 | 原生写法 | 封装后 |
|---|---|---|
| 管节点 | 手工 Channel.Init |
SvcChannels.addNode |
| 选节点 | 自己记地址 | getNode 轮询 |
| 异步回调 | NewCallback(函数, 参数...) |
ClosureFactory::create(lambda) |
| 起服务 | AddService/Start 三件套 | RpcServerFactory::create |
| 客户端代码行数 | 约 35 行 | 约 18 行 |
真正省掉的不是行数,而是「出错的地方」:节点地址不再散落在业务里,改配置只改一处;回调原型固定,不会一半写 NewCallback 一半写 lambda;起服务不再复制粘贴三件套,端口和 Options 只在工厂里出现一次。
2. 为什么封装:四个类各解决什么
一个服务可能多节点,多服务要分别管;回调写法反人类;起服务样板重复------四个类正好对应四件事。
#mermaid-svg-zO4lELpa4mdcoNST{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zO4lELpa4mdcoNST .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zO4lELpa4mdcoNST .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zO4lELpa4mdcoNST .error-icon{fill:#552222;}#mermaid-svg-zO4lELpa4mdcoNST .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zO4lELpa4mdcoNST .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zO4lELpa4mdcoNST .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zO4lELpa4mdcoNST .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zO4lELpa4mdcoNST .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zO4lELpa4mdcoNST .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zO4lELpa4mdcoNST .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zO4lELpa4mdcoNST .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zO4lELpa4mdcoNST .marker.cross{stroke:#333333;}#mermaid-svg-zO4lELpa4mdcoNST svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zO4lELpa4mdcoNST p{margin:0;}#mermaid-svg-zO4lELpa4mdcoNST .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zO4lELpa4mdcoNST .cluster-label text{fill:#333;}#mermaid-svg-zO4lELpa4mdcoNST .cluster-label span{color:#333;}#mermaid-svg-zO4lELpa4mdcoNST .cluster-label span p{background-color:transparent;}#mermaid-svg-zO4lELpa4mdcoNST .label text,#mermaid-svg-zO4lELpa4mdcoNST span{fill:#333;color:#333;}#mermaid-svg-zO4lELpa4mdcoNST .node rect,#mermaid-svg-zO4lELpa4mdcoNST .node circle,#mermaid-svg-zO4lELpa4mdcoNST .node ellipse,#mermaid-svg-zO4lELpa4mdcoNST .node polygon,#mermaid-svg-zO4lELpa4mdcoNST .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zO4lELpa4mdcoNST .rough-node .label text,#mermaid-svg-zO4lELpa4mdcoNST .node .label text,#mermaid-svg-zO4lELpa4mdcoNST .image-shape .label,#mermaid-svg-zO4lELpa4mdcoNST .icon-shape .label{text-anchor:middle;}#mermaid-svg-zO4lELpa4mdcoNST .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zO4lELpa4mdcoNST .rough-node .label,#mermaid-svg-zO4lELpa4mdcoNST .node .label,#mermaid-svg-zO4lELpa4mdcoNST .image-shape .label,#mermaid-svg-zO4lELpa4mdcoNST .icon-shape .label{text-align:center;}#mermaid-svg-zO4lELpa4mdcoNST .node.clickable{cursor:pointer;}#mermaid-svg-zO4lELpa4mdcoNST .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zO4lELpa4mdcoNST .arrowheadPath{fill:#333333;}#mermaid-svg-zO4lELpa4mdcoNST .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zO4lELpa4mdcoNST .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zO4lELpa4mdcoNST .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zO4lELpa4mdcoNST .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zO4lELpa4mdcoNST .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zO4lELpa4mdcoNST .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zO4lELpa4mdcoNST .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zO4lELpa4mdcoNST .cluster text{fill:#333;}#mermaid-svg-zO4lELpa4mdcoNST .cluster span{color:#333;}#mermaid-svg-zO4lELpa4mdcoNST div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zO4lELpa4mdcoNST .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zO4lELpa4mdcoNST rect.text{fill:none;stroke-width:0;}#mermaid-svg-zO4lELpa4mdcoNST .icon-shape,#mermaid-svg-zO4lELpa4mdcoNST .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zO4lELpa4mdcoNST .icon-shape p,#mermaid-svg-zO4lELpa4mdcoNST .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zO4lELpa4mdcoNST .icon-shape .label rect,#mermaid-svg-zO4lELpa4mdcoNST .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zO4lELpa4mdcoNST .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zO4lELpa4mdcoNST .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zO4lELpa4mdcoNST :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 业务: setWatch + addNode
SvcChannels.getNode
Channels.select 轮询
Stub 发起调用
ClosureFactory 回调
| 类 | 职责 | 一句话 |
|---|---|---|
Channels |
一个服务的全部节点信道 + 轮询选路 | 「这个服务能连谁」 |
SvcChannels |
服务名 → Channels 的映射 |
「这个服务归谁管」 |
ClosureFactory |
lambda → google::protobuf::Closure |
「回调别写 NewCallback」 |
RpcServerFactory |
一行创建并启动 Server | 「起服务别抄三件套」 |
类之间的关系:
#mermaid-svg-4DLyoSG4ujdolSAM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4DLyoSG4ujdolSAM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4DLyoSG4ujdolSAM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4DLyoSG4ujdolSAM .error-icon{fill:#552222;}#mermaid-svg-4DLyoSG4ujdolSAM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4DLyoSG4ujdolSAM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4DLyoSG4ujdolSAM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4DLyoSG4ujdolSAM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4DLyoSG4ujdolSAM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4DLyoSG4ujdolSAM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4DLyoSG4ujdolSAM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4DLyoSG4ujdolSAM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4DLyoSG4ujdolSAM .marker.cross{stroke:#333333;}#mermaid-svg-4DLyoSG4ujdolSAM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4DLyoSG4ujdolSAM p{margin:0;}#mermaid-svg-4DLyoSG4ujdolSAM g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-4DLyoSG4ujdolSAM g.classGroup text .title{font-weight:bolder;}#mermaid-svg-4DLyoSG4ujdolSAM .cluster-label text{fill:#333;}#mermaid-svg-4DLyoSG4ujdolSAM .cluster-label span{color:#333;}#mermaid-svg-4DLyoSG4ujdolSAM .cluster-label span p{background-color:transparent;}#mermaid-svg-4DLyoSG4ujdolSAM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4DLyoSG4ujdolSAM .cluster text{fill:#333;}#mermaid-svg-4DLyoSG4ujdolSAM .cluster span{color:#333;}#mermaid-svg-4DLyoSG4ujdolSAM .nodeLabel,#mermaid-svg-4DLyoSG4ujdolSAM .edgeLabel{color:#131300;}#mermaid-svg-4DLyoSG4ujdolSAM .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-4DLyoSG4ujdolSAM .label text{fill:#131300;}#mermaid-svg-4DLyoSG4ujdolSAM .labelBkg{background:#ECECFF;}#mermaid-svg-4DLyoSG4ujdolSAM .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-4DLyoSG4ujdolSAM .classTitle{font-weight:bolder;}#mermaid-svg-4DLyoSG4ujdolSAM .node rect,#mermaid-svg-4DLyoSG4ujdolSAM .node circle,#mermaid-svg-4DLyoSG4ujdolSAM .node ellipse,#mermaid-svg-4DLyoSG4ujdolSAM .node polygon,#mermaid-svg-4DLyoSG4ujdolSAM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4DLyoSG4ujdolSAM .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-4DLyoSG4ujdolSAM g.clickable{cursor:pointer;}#mermaid-svg-4DLyoSG4ujdolSAM g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-4DLyoSG4ujdolSAM g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-4DLyoSG4ujdolSAM .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-4DLyoSG4ujdolSAM .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-4DLyoSG4ujdolSAM .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-4DLyoSG4ujdolSAM .dashed-line{stroke-dasharray:3;}#mermaid-svg-4DLyoSG4ujdolSAM .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-4DLyoSG4ujdolSAM #compositionStart,#mermaid-svg-4DLyoSG4ujdolSAM .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4DLyoSG4ujdolSAM #compositionEnd,#mermaid-svg-4DLyoSG4ujdolSAM .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4DLyoSG4ujdolSAM #dependencyStart,#mermaid-svg-4DLyoSG4ujdolSAM .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4DLyoSG4ujdolSAM #dependencyStart,#mermaid-svg-4DLyoSG4ujdolSAM .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4DLyoSG4ujdolSAM #extensionStart,#mermaid-svg-4DLyoSG4ujdolSAM .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4DLyoSG4ujdolSAM #extensionEnd,#mermaid-svg-4DLyoSG4ujdolSAM .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4DLyoSG4ujdolSAM #aggregationStart,#mermaid-svg-4DLyoSG4ujdolSAM .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4DLyoSG4ujdolSAM #aggregationEnd,#mermaid-svg-4DLyoSG4ujdolSAM .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4DLyoSG4ujdolSAM #lollipopStart,#mermaid-svg-4DLyoSG4ujdolSAM .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4DLyoSG4ujdolSAM #lollipopEnd,#mermaid-svg-4DLyoSG4ujdolSAM .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4DLyoSG4ujdolSAM .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-4DLyoSG4ujdolSAM .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4DLyoSG4ujdolSAM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4DLyoSG4ujdolSAM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4DLyoSG4ujdolSAM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} SvcChannels
Channels
Channel
RpcServerFactory
Server
ClosureFactory
Closure
3. Channels:一个服务的多节点选路
每个服务一个 Channels,内部用 vector<地址, Channel> 存节点,select 按下标轮询返回一个可用信道。 完整类:
cpp
#pragma once
#include <brpc/channel.h>
#include <mutex>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace rpcwrap {
using ChannelPtr = std::shared_ptr<brpc::Channel>;
class Channels {
public:
using ptr = std::shared_ptr<Channels>;
void insert(const std::string& addr);
void remove(const std::string& addr);
ChannelPtr select();
std::optional<std::string> selectAddr();
private:
std::mutex mtx_;
uint32_t idx_ = 0; // 轮询下标
std::vector<std::pair<std::string, ChannelPtr>> channels_;
};
} // namespace rpcwrap
关键实现只有两个函数:
cpp
void Channels::insert(const std::string& addr) {
std::unique_lock<std::mutex> lock(mtx_);
auto channel = std::make_shared<brpc::Channel>();
brpc::ChannelOptions options;
options.protocol = "baidu_std";
options.timeout_ms = 30000; // 按业务调,别照抄
if (channel->Init(addr.c_str(), &options) != 0) {
return; // 节点不可用,不加入
}
channels_.emplace_back(addr, channel);
}
ChannelPtr Channels::select() {
std::unique_lock<std::mutex> lock(mtx_);
if (channels_.empty()) return ChannelPtr();
const size_t idx = (idx_++) % channels_.size(); // 简单轮询
return channels_[idx].second;
}
| 设计点 | 为什么这么写 |
|---|---|
vector 而不是 map |
节点数量少,顺序遍历/轮询更直接 |
idx_ 自增取模 |
最简单的负载均衡;要加权/一致性哈希再换策略 |
| 锁保护整个 vector | 服务发现会并发增删节点,选路也在并发读 |
Init 失败直接不加 |
坏节点不进池,调用方自然跳过 |
这里有个取舍:Channel 是「到某个节点的连接」,不是一次请求。所以同一个节点的 Channel 被多次调用复用,底层连接池也由 brpc 管理;这正是不能每个请求都新建 Channel 的原因------那样等于把连接池扔掉,延迟和 fd 都会爆。
ℹ️ 轮询只是起点
这个封装把「选路」抽出来以后,换成随机、加权、一致性哈希都只改 select() 一个函数;接口不变,业务无感。
4. SvcChannels:服务名 → Channels
它维护「服务名 → 该服务的 Channels」映射,并规定只有 setWatch 声明过的服务才会被管理。 这是「先声明再使用」的模式,避免拼错服务名时静默建出空对象。
cpp
class SvcChannels {
public:
void setWatch(const std::string& svc); // 预声明
void addNode(const std::string& svc, const std::string& addr);
void delNode(const std::string& svc, const std::string& addr);
ChannelPtr getNode(const std::string& svc);
std::optional<std::string> getNodeAddr(const std::string& svc);
private:
Channels::ptr channelsOf(const std::string& svc);
std::mutex mtx_;
std::unordered_map<std::string, Channels::ptr> maps_;
};
cpp
void SvcChannels::setWatch(const std::string& svc) {
std::unique_lock<std::mutex> lock(mtx_);
maps_.emplace(svc, std::make_shared<Channels>());
}
void SvcChannels::addNode(const std::string& svc, const std::string& addr) {
auto channels = channelsOf(svc); // 内部只锁很短一段
if (!channels) return; // 没声明过的服务,直接忽略
channels->insert(addr);
}
ChannelPtr SvcChannels::getNode(const std::string& svc) {
auto channels = channelsOf(svc);
return channels ? channels->select() : ChannelPtr();
}
这里有个锁的细节 :channelsOf 只保护「查 map」这一小段,拿到 shared_ptr 后立刻释放锁,后面的 insert/select 由 Channels 自己的锁保护。两层锁各管各的,避免大锁套小锁。
为什么用「预声明」而不是「第一次 addNode 时自动建」?预声明能把配置错误提前暴露:拼错服务名时 addNode 直接忽略并打日志,而不是安静地建出一个空的 Channels、等到调用时才报「没有可用节点」。如果服务列表来自配置中心,启动时逐个 setWatch 一遍即可;懒加载(用到再建)也成立,但要把「建对象」和「加节点」都放进同一个双检锁,复杂度更高,收益不大。
5. ClosureFactory:把 lambda 变成 brpc Closure
brpc 只认 google::protobuf::Closure*,而它的 NewCallback 不接受仿函数(lambda),所以包一层:把 lambda 塞进 shared_ptr 对象,再用静态函数做回调。
cpp
class ClosureFactory {
public:
using callback_t = std::function<void()>;
static google::protobuf::Closure* create(callback_t cb) {
auto obj = std::make_shared<Object>();
obj->callback = std::move(cb);
return brpc::NewCallback(&ClosureFactory::run, obj);
}
private:
struct Object {
callback_t callback;
};
static void run(std::shared_ptr<Object> obj) {
if (obj && obj->callback) obj->callback();
}
};
两个必须知道的约束:
| 约束 | 说明 |
|---|---|
NewCallback 参数不能传引用 |
框架按值保存参数,传引用会悬垂;这里传的是 shared_ptr 副本,安全 |
闭包 Run() 后自删 |
brpc 的 Closure 在执行后自行 delete,不要手动 delete 回调对象 |
| 捕获的堆对象谁释放 | 由 lambda 内部 unique_ptr 接管(见第 1 节),外层不要提前 delete |
为什么把 lambda 装进 shared_ptr 而不是裸指针?NewCallback 会把参数按值保存一份,若传裸指针,谁负责 delete 就说不清了;shared_ptr 的引用计数保证闭包执行完、最后一个持有者析构时才释放,配合 brpc「Run 后自删」刚好闭环。
6. RpcServerFactory 与使用样例
起服务原本要 AddService、ServerOptions、Start 三步,工厂一行搞定;服务对象由服务器接管(SERVER_OWNS_SERVICE)。
cpp
class RpcServerFactory {
public:
static std::shared_ptr<brpc::Server> create(int port,
google::protobuf::Service* svc) {
auto server = std::make_shared<brpc::Server>();
if (server->AddService(svc, brpc::SERVER_OWNS_SERVICE) != 0) {
std::cerr << "AddService failed" << std::endl;
abort();
}
brpc::ServerOptions options;
options.idle_timeout_sec = -1;
if (server->Start(port, &options) != 0) {
std::cerr << "Start failed" << std::endl;
abort();
}
return server;
}
};
服务端业务代码只剩两行:
cpp
int main() {
auto* svc = new CalServiceImpl; // 堆上创建,交给服务器管
auto server = rpcwrap::RpcServerFactory::create(9000, svc);
server->RunUntilAskedToQuit();
return 0;
}
对应的 cal.proto(一个做加法,一个留作 HTTP 回显):
proto
syntax = "proto3";
package cal;
option cc_generic_services = true;
message AddReq {
int32 num1 = 1;
int32 num2 = 2;
}
message AddRsp {
int32 result = 1;
}
message HelloReq {}
message HelloRsp {}
service CalService {
rpc Add(AddReq) returns (AddRsp);
rpc Hello(HelloReq) returns (HelloRsp);
}
服务起来后,客户端跑一次:
text
添加 cal 服务节点 127.0.0.1:9000 管理
异步请求已发出
22 + 333 = 355
✅ 这套封装适合谁
服务数量多、节点会动态上下线的项目最划算:节点变更只动 SvcChannels,业务代码不用改一行;只有一两个固定节点的工具,直接用原生 brpc 更省事。
7. 踩坑与总结
坑集中在三处:回调对象的生命周期、两层锁、节点初始化失败。
| 坑 | 现象 | 修法 |
|---|---|---|
| 手动 delete 回调对象 | 崩溃/双重释放 | brpc 闭包 Run 后自删,只释放 lambda 捕获的 req/rsp/cntl |
NewCallback 传引用参数 |
回调里拿到悬垂引用 | 传值或 shared_ptr,别传 &obj |
节点 Init 失败仍入池 |
调用随机失败 | insert 里判断 Init 返回值,失败不加入 |
| 两层锁顺序不一致 | 偶发死锁 | SvcChannels 锁只管查 map,Channels 锁管节点池,永不在持锁时调用对方 |
SERVER_OWNS_SERVICE 配栈对象 |
二次释放 | 服务对象必须 heap 分配,生命周期交给 Server |
| 服务名拼错 | 节点加了但调不通 | setWatch 预声明,addNode 对未声明的服务直接忽略并打日志 |
| 超时写死 30s | 故障节点拖慢整体 | 按接口定 timeout_ms,配合重试与熔断 |
这套封装不追求「什么都能干」:它把易错的样板收走,把协议和 Stub 留给 brpc 原生。这样出问题时,日志、/status、抓包依然按 brpc 的方式看,排查路径不会被封装层挡住。
📝 小结
① 四层各管一件事:节点池、服务映射、回调包装、服务器创建。
② 封装只包样板,不包协议:proto 和 Stub 还是原生的,出问题照样能查。
③ 最值钱的是选路可替换:改一个 `select()` 就能换负载均衡策略。
📦 项目源码
本文示例所在的 C++ 微服务脚手架(点播系统):https://github.com/syagain-zsx/cpp-microservice-scaffold-vod-system
🔜 下一篇
《CMake 工程化》------把前面这些轮子收进一个可维护的构建体系。关注专栏不迷路。
🔗 参考
1 brpc 官方文档:https://brpc.apache.org/
2 brpc GitHub:https://github.com/apache/brpc
3 上一篇《BRpc 搭建简单 rpc 服务器》