目录
PyBreaker
PyBreaker 是 Python 的 熔断器模式 实现,用于保护分布式系统免受级联故障的影响。当下游服务持续失败时,熔断器"跳闸"快速失败,避免资源耗尽;当服务恢复时自动"闭合"恢复正常调用。
核心特性
- 状态机管理:自动维护 CLOSED(闭合)、OPEN(打开)、HALF_OPEN(半开)三种状态
- 可配置阈值:支持自定义失败次数、超时时间、异常类型等触发条件
- 线程安全:基于锁机制确保并发环境下的状态一致性
- 多种通知机制:提供监听器接口,可监听熔断器状态变化
- 灵活集成:支持装饰器语法或上下文管理器两种使用方式
工作原理
熔断器状态机
初始状态
失败数达到阈值
(fail_max)
超时时间过后
(reset_timeout)
调用成功
调用失败
成功调用重置计数
CLOSED
OPEN
HALF_OPEN
正常状态:请求正常通过
失败计数器递增
熔断状态:直接抛出异常
不发起实际调用
等待恢复时间
探测状态:允许一次探测请求
成功则闭合,失败则继续打开
核心参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
fail_max |
int | 5 | 触发熔断的连续失败次数阈值 |
reset_timeout |
int | 60 | OPEN 状态转为 HALF_OPEN 的等待时间(秒) |
exception |
Exception | Exception | 计入失败统计的异常类型或元组 |
fallback |
callable | None | 熔断打开时的降级函数 |
fail_max 决定熔断敏感度,reset_timeout 决定恢复速度,两者需要根据业务特性平衡配置。
快速上手
安装
bash
pip install pybreaker
示例
python
from pybreaker import CircuitBreaker, CircuitBreakerError
# 创建熔断器实例
# fail_max=2: 连续失败 2 次后触发熔断
# reset_timeout=10: 熔断后 10 秒进入半开状态
breaker = CircuitBreaker(fail_max=2, reset_timeout=10)
@breaker
def unreliable_service():
import random
if random.random() < 0.8: # 80% 概率失败
raise ConnectionError("服务不可用")
return "服务响应成功"
# 使用示例
for i in range(5):
try:
result = unreliable_service()
print(f"第 {i+1} 次调用: {result}")
except CircuitBreakerError:
print(f"第 {i+1} 次调用: 熔断器已打开,快速失败")
except ConnectionError as e:
print(f"第 {i+1} 次调用: 业务异常 - {e}")
# 预期输出:
# 第 1 次调用: 业务异常 - 服务不可用
# 第 2 次调用: 业务异常 - 服务不可用
# 第 3 次调用: 熔断器已打开,快速失败 ← 达到 fail_max,熔断
# 第 4 次调用: 熔断器已打开,快速失败
# 第 5 次调用: 熔断器已打开,快速失败
常见使用场景
1. 基础装饰器用法
python
from pybreaker import CircuitBreaker
# 创建熔断器
# fail_max: 允许的最大连续失败次数
# reset_timeout: 从 OPEN 转为 HALF_OPEN 的等待时间(秒)
api_breaker = CircuitBreaker(
fail_max=5, # 连续失败 5 次触发熔断
reset_timeout=60 # 熔断 60 秒后尝试恢复
)
@api_breaker
def call_external_api():
# 被保护的函数
# 连续失败 5 次后,后续调用直接抛出 CircuitBreakerError
# 60 秒后进入半开状态,允许一次探测请求
return requests.get("https://external-api.com/data")
2. 指定异常类型
python
from pybreaker import CircuitBreaker
# exception: 指定哪些异常计入失败统计
# 可以传入单个异常类或异常元组
# 默认为 Exception(捕获所有异常)
network_breaker = CircuitBreaker(
fail_max=3,
reset_timeout=30,
exception=(ConnectionError, TimeoutError) # 仅这两种异常触发计数
)
@network_breaker
def fetch_with_timeout():
# ConnectionError 或 TimeoutError 会递增失败计数
# 其他异常(如 ValueError)不会触发熔断
...
3. 带降级函数(Fallback)
python
from pybreaker import CircuitBreaker
def fallback_function(*args, **kwargs):
"""
降级函数:熔断打开时被调用的备用逻辑
接收与原函数相同的参数
"""
return {"status": "fallback", "data": "缓存数据"}
api_breaker = CircuitBreaker(
fail_max=3,
reset_timeout=60,
fallback=fallback_function # 设置降级函数
)
@api_breaker
def call_primary_service(user_id):
# 正常逻辑:调用主服务
return database.query_user(user_id)
# 使用示例:当熔断打开时,自动返回降级结果
result = call_primary_service(123)
# 如果熔断器已打开,返回 {"status": "fallback", "data": "缓存数据"}
4. 上下文管理器用法
python
from pybreaker import CircuitBreaker
breaker = CircuitBreaker(fail_max=3, reset_timeout=30)
# 临时禁用熔断器进行紧急调用
with breaker.allow():
# allow(): 临时禁用熔断器,强制执行
# 无论熔断器状态如何,都会执行函数
# 适用于紧急修复或维护场景
result = risky_operation()
# with 块结束后,熔断器恢复原状态
5. 状态监听器
python
from pybreaker import CircuitBreaker, CircuitBreakerListener
class MyListener(CircuitBreakerListener):
"""
自定义监听器:监听熔断器状态变化
需继承 CircuitBreakerListener 并实现感兴趣的方法
"""
def before_call(self, cb, func, *args, **kwargs):
"""
每次函数调用前触发
cb: CircuitBreaker 实例
func: 被调用的函数
"""
print(f"[{cb.name}] 准备调用 {func.__name__}")
def success(self, cb, func, *args, **kwargs):
"""调用成功时触发"""
print(f"[{cb.name}] 调用成功")
def failure(self, cb, exc, *args, **kwargs):
"""
调用失败时触发
exc: 捕获的异常对象
"""
print(f"[{cb.name}] 调用失败: {exc}")
def open(self, cb):
"""熔断器从 CLOSED/OPEN 变为 OPEN 时触发"""
print(f"[{cb.name}] ⚠️ 熔断器已打开")
def half_open(self, cb):
"""熔断器从 OPEN 变为 HALF_OPEN 时触发"""
print(f"[{cb.name}] 🔍 熔断器进入半开状态(探测中)")
def close(self, cb):
"""熔断器从 HALF_OPEN 变为 CLOSED 时触发"""
print(f"[{cb.name}] ✅ 熔断器已闭合,服务恢复")
# 创建带监听器的熔断器
api_breaker = CircuitBreaker(
fail_max=3,
reset_timeout=10,
listeners=[MyListener()] # 可传入多个监听器
)
@api_breaker
def external_api():
# 当状态变化时,会自动调用监听器的对应方法
...
6. 多个独立熔断器
python
from pybreaker import CircuitBreaker
# 为不同服务创建独立的熔断器
# 每个熔断器维护独立的状态和计数器
user_service_breaker = CircuitBreaker(
name="user-service", # name: 熔断器名称,用于日志和监听器
fail_max=5,
reset_timeout=60
)
payment_service_breaker = CircuitBreaker(
name="payment-service",
fail_max=2, # 支付服务更敏感,2 次失败即熔断
reset_timeout=120 # 支付服务恢复时间更长
)
@user_service_breaker
def get_user(user_id):
...
@payment_service_breaker
def process_payment(amount):
...
# 两个熔断器完全独立,互不影响
完整生产级示例
python
from pybreaker import CircuitBreaker, CircuitBreakerError, CircuitBreakerListener
import logging
import time
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class LoggingListener(CircuitBreakerListener):
"""生产环境日志监听器"""
def failure(self, cb, exc, *args, **kwargs):
logger.warning(f"[{cb.name}] 调用失败: {exc}")
def open(self, cb):
logger.error(f"[{cb.name}] 🔥 熔断器打开,服务可能故障")
def close(self, cb):
logger.info(f"[{cb.name}] ✅ 熔断器闭合,服务已恢复")
# 创建熔断器
service_breaker = CircuitBreaker(
name="api-service",
fail_max=5, # 连续失败 5 次触发熔断
reset_timeout=30, # 30 秒后尝试恢复
exception=(ConnectionError, TimeoutError), # 仅网络异常触发
listeners=[LoggingListener()]
)
def get_fallback_data(*args, **kwargs):
"""降级函数:返回缓存或默认数据"""
logger.info("执行降级逻辑")
return {"data": "cached", "source": "fallback"}
@service_breaker
def call_external_service(endpoint):
"""
调用外部服务
连续失败 5 次 → 熔断打开 → 返回降级数据
30 秒后 → 半开状态 → 探测请求 → 成功则闭合
"""
# 实际调用逻辑
response = requests.get(f"https://api.example.com/{endpoint}", timeout=5)
response.raise_for_status()
return response.json()
# 使用示例
try:
result = call_external_service("users")
print("成功:", result)
except CircuitBreakerError:
# 熔断器打开时的处理(已设置 fallback 则不会进入这里)
print("服务暂时不可用,请稍后重试")
except Exception as e:
# 其他业务异常
print(f"业务错误: {e}")
# 查看熔断器当前状态
print(f"当前状态: {service_breaker.current_state}") # 'closed', 'open', 或 'half_open'
print(f"失败计数: {service_breaker.fail_counter}") # 当前连续失败次数
状态查询与监控
python
from pybreaker import CircuitBreaker
breaker = CircuitBreaker(fail_max=3, reset_timeout=60)
# 状态查询属性
breaker.current_state # 当前状态: 'closed', 'open', 'half_open'
breaker.fail_counter # 当前连续失败次数
breaker.open_until # 熔断器打开 until 的时间戳(None 表示未打开)
breaker.closed_until # 半开状态的截止时间戳
# 状态判断方法
breaker.opened() # 熔断器是否打开
breaker.closed() # 熔断器是否闭合
# 手动控制(慎用)
breaker._open() # 强制打开熔断器
breaker._close() # 强制闭合熔断器
breaker._half_open() # 强制进入半开状态
优缺点对比
| 维度 | 优势 (Pros) | 局限 (Cons) |
|---|---|---|
| 可靠性 | 快速失败避免雪崩,保护上游服务 | 误判可能导致正常服务被熔断 |
| 资源控制 | 减少对故障服务的无效请求 | 需要合理配置阈值,否则影响用户体验 |
| 自动恢复 | 无需人工干预自动探测恢复 | 恢复速度受 reset_timeout 限制 |
| 可观测性 | 提供状态和计数器,易于监控 | 需要自行集成到监控系统 |
| 灵活性 | 支持多熔断器、降级、监听器 | 不支持动态调整参数(需重启) |
避坑指南与最佳实践
常见陷阱
| 问题 | 说明 | 解决方案 |
|---|---|---|
| 阈值设置不当 | fail_max 太低易误熔断,太高则反应迟钝 |
根据服务 QPS 和可接受失败率计算,通常 5-10 次 |
| 恢复时间过长 | reset_timeout 太大会延迟恢复探测 |
设置为服务正常重启时间的 2-3 倍 |
| 所有服务共用 | 多个服务共用一个熔断器会相互影响 | 每个下游服务使用独立熔断器实例 |
| 忽略降级逻辑 | 熔断打开后无降级,用户看到错误 | 始终配置 fallback 函数 |
| 异常类型过宽 | exception=Exception 捕获太多 |
只捕获网络相关异常如 ConnectionError |
最佳实践
-
阈值配置 :
fail_max = QPS × 可容忍故障时长- 例如:QPS=100,可容忍 2 秒故障 →
fail_max=200
- 例如:QPS=100,可容忍 2 秒故障 →
-
恢复时间 :
reset_timeout应大于下游服务重启时间- 通常设置 30-60 秒,避免频繁探测
-
异常范围:只捕获网络超时类异常
pythonexception=(ConnectionError, TimeoutError, requests.Timeout) -
降级策略:根据业务优先级选择降级方案
- 核心业务:返回缓存数据
- 非核心业务:返回默认值或空结果
- 写操作:记录日志异步重试
-
监控告警:监听熔断器状态变化
pythonclass AlertListener(CircuitBreakerListener): def open(self, cb): send_alert(f"{cb.name} 熔断器打开!")
与 Tenacity 组合使用
python
from pybreaker import CircuitBreaker
from tenacity import retry, stop_after_attempt, wait_exponential
# 先熔断,后重试
# 熔断器保护:服务整体故障时快速失败
# 重试机制:服务偶发故障时自动重试
api_breaker = CircuitBreaker(fail_max=5, reset_timeout=60)
@api_breaker
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=1, max=5)
)
def call_api():
# 执行逻辑:
# 1. 熔断器关闭时:允许进入,失败则重试(最多 3 次)
# 2. 熔断器打开时:直接抛出 CircuitBreakerError
return requests.get("https://api.example.com")
常见问题
Q: 熔断器和 [[Tenacity]] 重试有什么区别?
A: 熔断器是系统级保护 ,防止级联故障;重试是调用级恢复,处理临时故障。两者可组合使用:熔断器决定是否允许调用,重试决定调用失败后是否重试。
Q: 如何知道熔断器被打开了?
A: 通过 CircuitBreakerListener.open() 监听,或检查 breaker.current_state == 'open',建议集成到监控系统。
Q: 半开状态下的成功/失败如何定义?
A: 任何一次成功调用都会闭合熔断器,任何一次失败都会重新打开。一次探测足够判断服务是否恢复。
Q: 能否动态调整 fail_max 和 reset_timeout?
A: PyBreaker 不支持运行时修改参数。如需动态调整,可使用配置中心重新创建熔断器实例。