高并发系统设计-熔断
一、概述
1.1 什么是熔断
熔断(Circuit Breaker)的核心思想来自电路保险丝:
当电流过大时,保险丝会断开,保护整个电路。
在分布式系统中:
服务A → 服务B → 服务C → 数据库
如果服务C异常:
- 超时
- 报错
- 响应缓慢
那么如果不做控制:
A 等 BB 等 CC 卡住→ 线程全部阻塞→ 请求堆积→ 系统雪崩
熔断的作用就是:
当下游服务异常时,直接停止调用它,快速失败。
1.2 为什么需要熔断
在高并发系统中,最危险的问题不是“服务失败”,而是:
失败被放大并传播
例如:
数据库慢查询
↓
所有请求阻塞
↓
连接池耗尽
↓
Redis压力上升
↓
Gateway超时
↓
全链路崩溃
这就是:
级联故障(Cascade Failure)
1.3 熔断解决的问题
熔断解决三类问题:
1. 防止线程阻塞
避免请求一直等待下游:
请求 → 卡住 → 不释放资源
2.防止错误放大
错误不会扩散到上游系统:
错误服务 → 不再调用
1.4 企业典型场景
场景1:支付服务异常
订单服务 → 支付服务(异常)
不熔断:
→ 一直等待
→ 订单线程堆积
熔断后:
→ 直接返回“支付处理中”
场景2:库存服务不可用
库存服务挂了
熔断:
→ 不再调用库存
→ 返回“库存未知”
场景3:第三方接口超时
例如:
- 短信平台
- 邮件服务
- 地图API
熔断避免拖垮主系统。
二、原理
2.1 熔断的核心思想
熔断本质是一个:
状态机 + 失败统计 + 自动恢复机制
2.2 三种状态模型
熔断器包含三个状态:
+---------+
| CLOSED |
+---------+
|
失败率过高
▼
+---------+
| OPEN |
+---------+
|
冷却时间
▼
+--------------+
| HALF-OPEN |
+--------------+
2.3 状态解释
1.CLOSED(关闭状态)
正常状态:请求正常通过
此时:
- 记录成功/失败次数
- 监控错误率
2.OPEN(熔断状态)
触发条件:失败率 > 阈值
行为:
- 直接拒绝请求
- 不再访问下游
- 快速返回 fallback
3.HALF-OPEN(半开状态)
作用:
用少量请求探测服务是否恢复
如果成功:恢复 CLOSED
如果失败:回到 OPEN
2.4 熔断触发条件
常用触发策略:
1. 失败率
如:失败率 > 50%
2.连续失败次数
如:连续失败 10 次
3. 慢调用
如:响应 > 2s 的请求比例过高
4.超时比例
如:超时率 > 30%
2.5 熔断恢复机制
恢复过程:
OPEN
↓(冷却时间)
HALF-OPEN
↓
成功 → CLOSED
失败 → OPEN
三、实现
PHP 简易熔断器实现
class CircuitBreaker
{
private int $failCount = 0;
private int $successCount = 0;
private int $threshold = 5;
private string $state = 'CLOSED';
private int $openTime = 0;
private int $coolDown = 10;
public function call(callable $callback)
{
if ($this->state === 'OPEN') {
if (time() - $this->openTime < $this->coolDown) {
throw new Exception("Circuit Open");
}
$this->state = 'HALF_OPEN';
}
try {
$result = $callback();
$this->onSuccess();
return $result;
} catch (\Throwable $e) {
$this->onFailure();
throw $e;
}
}
private function onSuccess()
{
$this->failCount = 0;
if ($this->state === 'HALF_OPEN') {
$this->state = 'CLOSED';
}
}
private function onFailure()
{
$this->failCount++;
if ($this->failCount >= $this->threshold) {
$this->state = 'OPEN';
$this->openTime = time();
}
}
}
Redis 分布式熔断状态存储结构示例
circuit:user-service
{
state: OPEN,
fail: 10,
success: 0,
open_time: 123456
}