4. 标准熔断器模板架构:模板模式在熔断器中的应用、抽象基类设计、通用接口定义
在驱动开发中,熔断器(Circuit Breaker)的实现需要兼顾状态流转的通用性与具体业务逻辑的可扩展性。模板方法模式(Template Method Pattern)天然适合这种场景:它将熔断器的骨架算法(状态检测、计数、切换)封装在抽象基类中,而将“如何判定失败”“如何执行恢复试探”等可变步骤延迟到子类实现。本节将围绕模板模式在熔断器中的落地,给出抽象基类设计、通用接口定义以及完整的代码骨架。
4.1 模板模式在熔断器中的角色映射
模板模式的核心是“不变部分由父类控制,可变部分由子类填充”。对照熔断器的标准生命周期,映射关系如下表所示:
| 模板模式角色 | 熔断器对应元素 | 职责说明 |
|---|---|---|
| 抽象基类(AbstractClass) | CircuitBreakerTemplate |
定义状态机骨架、计数逻辑、半开定时器、模板方法 call() |
| 具体子类(ConcreteClass) | NetworkBreaker / DiskBreaker |
实现失败判定、健康检查、恢复策略等钩子方法 |
| 模板方法(TemplateMethod) | execute_request() |
固定流程:检查状态 → 执行调用 → 记录结果 → 触发状态转换 |
| 钩子方法(Hook) | is_failure() / on_half_open_check() |
子类可覆盖的扩展点,默认提供通用实现 |
设计要点:模板方法必须声明为
final(或不可重写),防止子类破坏熔断器的核心状态机;钩子方法应提供默认实现,降低子类的实现负担。
4.2 抽象基类设计:CircuitBreakerTemplate
抽象基类封装了熔断器的三态核心状态机(CLOSED / OPEN / HALF_OPEN)以及通用的计数与定时逻辑。以下是完整的抽象基类接口定义:
// circuit_breaker_template.h
#ifndef CIRCUIT_BREAKER_TEMPLATE_H
#define CIRCUIT_BREAKER_TEMPLATE_H
#include <atomic>
#include <chrono>
#include <functional>
#include <mutex>
enum class BreakerState {
CLOSED, // 闭合态:正常转发请求
OPEN, // 断开态:快速失败
HALF_OPEN // 半开态:试探性恢复
};
class CircuitBreakerTemplate {
public:
CircuitBreakerTemplate(int failure_threshold,
int success_threshold_in_half_open,
std::chrono::milliseconds open_timeout)
: failure_threshold_(failure_threshold)
, success_threshold_in_half_open_(success_threshold_in_half_open)
, open_timeout_(open_timeout)
, state_(BreakerState::CLOSED)
, failure_count_(0)
, half_open_success_count_(0) {}
virtual ~CircuitBreakerTemplate() = default;
// 模板方法 —— 不可被子类重写
bool execute_request(std::function<bool()> request) {
std::lock_guard<std::mutex> lock(mutex_);
if (state_ == BreakerState::OPEN) {
if (has_open_timeout_elapsed()) {
transition_to_half_open();
} else {
return false; // 快速失败
}
}
if (state_ == BreakerState::HALF_OPEN) {
return handle_half_open_request(request);
}
// CLOSED 态:正常执行
return handle_closed_request(request);
}
BreakerState get_state() const { return state_; }
protected:
// ---------- 钩子方法(子类可覆盖) ----------
// 判断一次调用是否属于失败
virtual bool is_failure(bool result) const {
return !result; // 默认:返回 false 即为失败
}
// 半开态下的健康检查逻辑(默认直接执行请求)
virtual bool on_half_open_check(std::function<bool()>& request) {
return request();
}
// 状态转换时的通知(默认空实现)
virtual void on_state_changed(BreakerState old_state,
BreakerState new_state) {}
// ---------- 子类可调用的辅助方法 ----------
void reset_failure_count() { failure_count_ = 0; }
void reset_half_open_success_count() { half_open_success_count_ = 0; }
int get_failure_count() const { return failure_count_; }
int get_half_open_success_count() const { return half_open_success_count_; }
private:
// ---------- 私有核心逻辑 ----------
bool handle_closed_request(std::function<bool()>& request) {
bool result = request();
if (is_failure(result)) {
++failure_count_;
if (failure_count_ >= failure_threshold_) {
transition_to_open();
}
} else {
failure_count_ = 0; // 连续成功则重置失败计数
}
return result;
}
bool handle_half_open_request(std::function<bool()>& request) {
bool result = on_half_open_check(request);
if (is_failure(result)) {
transition_to_open(); // 半开态失败 → 回到断开态
return false;
}
++half_open_success_count_;
if (half_open_success_count_ >= success_threshold_in_half_open_) {
transition_to_closed(); // 连续成功达到阈值 → 闭合
}
return result;
}
void transition_to_open() {
BreakerState old = state_;
state_ = BreakerState::OPEN;
open_time_start_ = std::chrono::steady_clock::now();
reset_half_open_success_count();
on_state_changed(old, BreakerState::OPEN);
}
void transition_to_half_open() {
BreakerState old = state_;
state_ = BreakerState::HALF_OPEN;
reset_half_open_success_count();
on_state_changed(old, BreakerState::HALF_OPEN);
}
void transition_to_closed() {
BreakerState old = state_;
state_ = BreakerState::CLOSED;
reset_failure_count();
reset_half_open_success_count();
on_state_changed(old, BreakerState::CLOSED);
}
bool has_open_timeout_elapsed() const {
auto now = std::chrono::steady_clock::now();
return (now - open_time_start_) >= open_timeout_;
}
// ---------- 成员变量 ----------
const int failure_threshold_;
const int success_threshold_in_half_open_;
const std::chrono::milliseconds open_timeout_;
std::atomic<BreakerState> state_;
int failure_count_;
int half_open_success_count_;
std::chrono::steady_clock::time_point open_time_start_;
std::mutex mutex_;
};
#endif // CIRCUIT_BREAKER_TEMPLATE_H
架构要点:抽象基类将状态转换的判定条件(失败阈值、半开成功阈值、超时时间)作为构造函数参数注入,子类只需关注业务相关的钩子方法。所有状态转换路径(CLOSED→OPEN、OPEN→HALF_OPEN、HALF_OPEN→CLOSED/OPEN)均由基类统一管理,避免子类误操作。
4.3 通用接口定义与扩展点
除了抽象基类内部的方法,熔断器模板还需要对外暴露一组通用接口,供驱动框架或上层业务调用。这些接口遵循“最小知识原则”,只暴露必要的操作:
| 接口名称 | 类型 | 说明 |
|---|---|---|
execute_request(callable) |
模板方法(public) | 执行受熔断器保护的调用,自动处理状态机 |
get_state() |
查询接口(public) | 返回当前熔断器状态(CLOSED/OPEN/HALF_OPEN) |
force_open() |
管理接口(public) | 手动强制进入断开态(用于运维或降级场景) |
force_closed() |
管理接口(public) | 手动强制恢复闭合态(重置所有计数) |
is_failure(result) |
钩子方法(protected) | 子类自定义失败判定逻辑 |
on_half_open_check(request) |
钩子方法(protected) | 半开态下的试探策略(如只读查询、健康端点) |
on_state_changed(old, new) |
钩子方法(protected) | 状态变更通知(可用于日志、指标上报) |
避坑提醒:
force_open() 和 force_closed() 必须绕过正常的计数逻辑,直接修改状态并重置内部计数器。实现时需注意与模板方法中的锁机制保持一致,避免竞态条件。
4.4 模板架构的完整状态流转
下图展示了抽象基类中定义的完整状态流转路径,以及每个状态下的核心动作:
4.5 具体子类实现示例
以下是一个针对网络请求熔断的具体子类实现,展示了如何覆盖钩子方法来实现业务定制的失败判定和半开试探:
// network_breaker.h
#include "circuit_breaker_template.h"
#include <string>
class NetworkBreaker : public CircuitBreakerTemplate {
public:
NetworkBreaker(int failure_threshold,
int success_threshold_in_half_open,
std::chrono::milliseconds open_timeout)
: CircuitBreakerTemplate(failure_threshold,
success_threshold_in_half_open,
open_timeout) {}
protected:
// 覆盖失败判定:HTTP 状态码 >= 500 或网络异常视为失败
bool is_failure(bool result) const override {
// 实际场景中 result 可替换为更丰富的返回结构
return !result;
}
// 半开态下只发送轻量级健康探测请求
bool on_half_open_check(std::function<bool()>& request) override {
// 记录半开试探日志
printf("[NetworkBreaker] HALF_OPEN probe started\n");
bool result = request(); // 实际可替换为 /health 端点
printf("[NetworkBreaker] HALF_OPEN probe result: %s\n",
result ? "success" : "failure");
return result;
}
// 状态变更时输出日志
void on_state_changed(BreakerState old_state,
BreakerState new_state) override {
printf("[NetworkBreaker] State: %s → %s\n",
state_to_string(old_state).c_str(),
state_to_string(new_state).c_str());
}
private:
std::string state_to_string(BreakerState s) const {
switch (s) {
case BreakerState::CLOSED: return "CLOSED";
case BreakerState::OPEN: return "OPEN";
case BreakerState::HALF_OPEN: return "HALF_OPEN";
default: return "UNKNOWN";
}
}
};
实践建议:在驱动开发中,建议将熔断器模板作为组合对象嵌入到设备驱动类中,而非使用继承。例如,一个网卡驱动可以包含一个
NetworkBreaker 成员,在发送数据包时调用 execute_request()。这样既复用了模板的状态机,又保持了驱动类的单一职责。
4.6 模板架构的优劣分析
| 维度 | 优势 | 潜在风险 |
|---|---|---|
| 代码复用 | 状态机、计数、定时逻辑全部集中在基类,子类只需写业务差异代码 | 基类过于臃肿时,子类可能被迫依赖不需要的成员 |
| 扩展性 | 钩子方法提供清晰的扩展点,新增熔断策略无需修改基类 | 钩子方法过多会导致“模板方法反模式”,子类需覆盖大量方法 |
| 可测试性 | 基类状态机可独立测试,子类只需测试钩子方法 | 模板方法中的锁和原子操作增加并发测试复杂度 |
| 维护性 | 状态转换路径集中管理,避免子类各自实现导致的状态不一致 | 基类与子类之间的耦合较紧,修改基类可能影响所有子类 |
综合来看,模板模式在熔断器场景中的收益远大于风险。只要控制好基类的职责边界(只做状态机骨架,不做业务假设),并合理设计钩子方法的数量(通常 3~5 个),就能在驱动开发中构建出高内聚、低耦合的标准熔断器模板。