熔断状态应使用原子变量存储核心字段并配合轻量锁机制:用std::atomic存is_open,再配含failure_count、last_failure_time的结构体;仅状态翻转时加锁,避免acquire()频繁争抢;采用滑动窗口计数(如60秒内失败≥5次)触发熔断,半开态通过compare_exchange_strong争试探名额,并设超时;仅网络连接失败(非sql错误)更新计数;在连接池create_connection()中埋钩子,结合健康检查补漏已借出连接断连场景。

熔断状态怎么存才不拖慢获取连接
熔断不是靠全局开关硬拦,而是每个数据库实例(或连接池)自己维护状态。用 std::atomic<bool></bool> 存 is_circuit_open 最轻量,读写都无锁;但仅靠布尔值不够——你得知道“失败多少次才开”“开多久才试”,所以还得配一个带时间戳和计数的结构体,比如:
struct CircuitState {
std::atomic<int> failure_count{0};
std::atomic<long long> last_failure_time{0}; // us
std::atomic<bool> is_open{false};
};
别用 std::mutex 包整个状态更新,否则每次 acquire() 都要抢锁,池子一并发就卡住。只在真正要翻转状态(如从半开变开)时加锁。
什么时候触发熔断、什么时候试探恢复
典型策略是“滑动窗口失败计数 + 指数退避试探”。不是累计总失败,而是在最近 60 秒内失败 ≥ 5 次就开熔断;开之后等 30 秒进半开态,只允许 1 个请求试探,成功则重置,失败则延长等待时间(比如翻倍到 60 秒)。
-
acquire()前先查is_open.load(),为true就直接抛异常或返回空指针,不碰底层连接队列 - 半开状态下,用
compare_exchange_strong争一个“试探名额”,没抢到就继续等待 - 试探请求必须设超时(比如
connect_timeout=2s),否则卡死会拖垮整个半开机制
连接获取失败时怎么更新熔断计数
只对明确的连接建立失败(如 mysql_real_connect 返回 nullptr、libpq 的 PQstatus(conn) == CONNECTION_BAD)计数,不能把 SQL 执行失败或超时也塞进去——那是业务层该管的。
立即学习“C++免费学习笔记(深入)”;
- 网络类错误:如
ECONNREFUSED、ETIMEDOUT、ENETUNREACH必须计数 - 认证失败(
Access denied)不算,大概率是配置错,反复重试没意义 - 更新
failure_count和last_failure_time要用fetch_add和store,避免竞态导致漏记
libpq / mysqlclient 里怎么插熔断逻辑
不能改驱动源码,得在连接池的 create_connection() 里埋钩子。以 libpq 为例:
PGconn* create_connection() {
if (circuit_state.is_open.load()) return nullptr;
auto conn = PQconnectdb(conn_string.c_str());
if (!conn || PQstatus(conn) != CONNECTION_OK) {
circuit_state.failure_count.fetch_add(1);
circuit_state.last_failure_time.store(
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count()
);
maybe_open_circuit(); // 判断是否达到阈值
PQfinish(conn);
return nullptr;
}
return conn;
}
注意 PQconnectdb 本身不超时,得用 PQconnectStart + PQconnectPoll 配合 select() 自己控时,否则一个挂掉的 DB 会让整个线程卡死。
最易被忽略的是:熔断后,已借出的连接如果突然断连,不会自动触发二次熔断——得靠连接归还时的健康检查(比如发个 PING 或 SELECT 1)来补漏。不然熔断开着,旧连接还在脏读,问题更隐蔽。










