熔断状态必须持久化,不能仅存于内存;否则服务重启后状态丢失,易被突发流量击垮。应选redis/etcd等支持原子写、ttl和跨实例同步的存储,避免mysql;加载时需校验有效性并安全兜底,写入仅在三次关键状态跃迁及半开成功时触发。

熔断状态为什么不能只存在内存里
服务重启后 CircuitBreaker 状态全丢,意味着刚启动就可能被突发流量打垮——这不是“保护”,是“裸奔”。内存态熔断只适合单次进程生命周期,持久化不是可选项,是上线前必须闭环的点。
关键矛盾在于:状态写入要快(不能拖慢主调用路径),读取要可靠(重启时能准确还原),且不能因持久化失败导致熔断逻辑异常。
选文件还是数据库存熔断状态
小规模服务、无依赖数据库的场景,直接用本地文件最轻量;有现成 Redis 或 etcd 的,优先走它们——不是因为“高级”,而是因为原子写+过期时间+跨实例同步天然支持。
-
std::Filesystem::write_text()写 json 到/var/run/myapp/cb-state.json可行,但要注意权限、原子性(先写临时文件再 rename)、并发写冲突 - 用
redis-cli或hiredis存cb:service-a的哈希字段,设 TTL 比熔断窗口长 5 秒,避免状态滞留 - 别用 MySQL 存——每次调用都触发一次事务太重,且故障时反而拖累主链路
初始化时如何安全加载熔断状态
重启后从持久层读状态不是“load once”就完事。得处理:文件不存在、JSON 解析失败、字段缺失、过期时间已到但状态还在等场景。
立即学习“C++免费学习笔记(深入)”;
- 用
std::optional<circuitstate></circuitstate>封装加载结果,空值时走默认关闭态,不 panic - 检查持久化时间戳,若距当前超 2 倍窗口期,直接忽略该状态(防磁盘残留旧数据)
- 加载后立即调用
cb->transition_to_closed()或cb->transition_to_open(),而不是直接赋值内部字段——确保状态机合法性
示例片段:
auto loaded = load_from_redis("cb:auth-service");<br>if (loaded.has_value() && !loaded->is_expired()) {<br> cb->restore_state(loaded.value());<br>} else {<br> cb->transition_to_closed(); // 安全兜底<br>}
写入时机卡在哪几个关键点
不是所有状态变更都要立刻落盘——高频写会成为瓶颈,但漏写又会导致状态丢失。真正需要持久化的只有三次跃迁:半开 → 开、关闭 → 开、开 → 关闭。
- 在
on_failure()触发熔断时写,不在每个请求计数器更新时写 - 用异步线程 + 队列缓冲写操作,但注意:若进程 crash 前队列未刷出,接受少量状态丢失(比阻塞主流程更合理)
- 写失败不抛异常,只记日志;后续调用仍按内存态执行,避免因存储故障引发雪崩
最容易被忽略的是:半开状态下,即使成功调用一次,也不代表可以跳过持久化——如果这次成功后立即崩溃,下次启动会误判为“一直半开”,实际应退回到关闭态。所以 on_success_in_half_open() 也得触发一次写入。