本文目录导读:

PHP API 熔断的实现,通常不是 PHP 内建的标准功能,而是需要通过设计模式或借助第三方库来实现,其核心思想是:当 API 调用某个下游服务(如数据库、外部 API)的失败率达到一定阈值时,系统自动停止对它的调用,直接返回预设的错误或降级响应,经过一段时间后再尝试恢复。
以下是几种主流的 PHP 熔断实现方案,从简单到复杂:
手动实现(基于文件或内存的计数器)
这是最基础的方式,适合单机、小型项目。
原理: 记录最近 N 秒内的请求成功/失败次数,达到阈值则打开熔断器。
<?php
class CircuitBreaker {
private string $serviceName;
private int $failureThreshold; // 失败次数阈值
private int $successThreshold; // 半开状态下成功次数阈值
private int $timeout; // 熔断后等待恢复的时间(秒)
private string $stateFile; // 存储状态的文件路径
public function __construct(string $serviceName) {
$this->serviceName = $serviceName;
// 配置参数
$this->failureThreshold = 3;
$this->successThreshold = 2;
$this->timeout = 30;
$this->stateFile = "/tmp/circuit_breaker_{$serviceName}.json";
}
public function call(callable $apiCall) {
$state = $this->getState();
if ($state['status'] === 'open') {
// 熔断打开:先不调用,直接返回错误
if (time() - $state['last_failure_time'] < $this->timeout) {
throw new \RuntimeException("Service {$this->serviceName} is currently unavailable (circuit open)");
}
// 等待超时,进入半开状态
$state['status'] = 'half_open';
$this->saveState($state);
}
try {
$result = call_user_func($apiCall);
// 调用成功:处理状态
$state['success_count']++;
$state['failure_count'] = 0;
if ($state['status'] === 'half_open' && $state['success_count'] >= $this->successThreshold) {
// 半开状态下连续成功达到阈值,关闭熔断器
$state['status'] = 'closed';
$state['success_count'] = 0;
}
$this->saveState($state);
return $result;
} catch (\Exception $e) {
// 调用失败
$state['failure_count']++;
$state['success_count'] = 0;
if ($state['failure_count'] >= $this->failureThreshold) {
$state['status'] = 'open';
$state['last_failure_time'] = time();
}
$this->saveState($state);
throw $e;
}
}
private function getState(): array {
if (!file_exists($this->stateFile)) {
return ['status' => 'closed', 'failure_count' => 0, 'success_count' => 0, 'last_failure_time' => 0];
}
return json_decode(file_get_contents($this->stateFile), true);
}
private function saveState(array $state): void {
file_put_contents($this->stateFile, json_encode($state), LOCK_EX);
}
}
// 使用示例
$cb = new CircuitBreaker('user_api');
try {
$data = $cb->call(function() {
// 这里调用实际的 API
return file_get_contents('https://api.example.com/users');
});
} catch (\RuntimeException $e) {
// 熔断降级:返回缓存或默认值
echo "Service unavailable, returning cached data...";
}
优点: 无外部依赖,容易理解。
缺点: 非线程安全(多个 PHP-FPM 进程同时写入文件可能冲突);无法跨机器共享状态。
使用 Redis 实现分布式熔断
适合多服务器、集群环境,利用 Redis 的原子计数和过期时间实现精确控制。
<?php
class RedisCircuitBreaker {
private \Redis $redis;
private string $keyPrefix;
private int $failureWindow; // 统计窗口(秒)
private int $failureThreshold; // 窗口内失败次数阈值
private int $halfOpenTimeout; // 半开状态等待时间
public function __construct(\Redis $redis) {
$this->redis = $redis;
$this->keyPrefix = 'cb:service:';
$this->failureWindow = 10; // 最近10秒
$this->failureThreshold = 5; // 失败5次
$this->halfOpenTimeout = 30; // 熔断30秒后尝试恢复
}
public function call(string $service, callable $apiCall) {
$statusKey = $this->keyPrefix . $service . ':status';
// 1. 检查状态
$status = $this->redis->get($statusKey);
if ($status === 'open') {
// 检查是否超过超时时间,进入半开状态
$lastFailure = $this->redis->get($statusKey . ':last_failure');
if (time() - $lastFailure < $this->halfOpenTimeout) {
throw new \RuntimeException("Service {$service} is circuit open");
}
$this->redis->set($statusKey, 'half_open');
}
// 2. 执行调用
try {
$result = call_user_func($apiCall);
// 成功处理
if ($status === 'half_open') {
$this->redis->set($statusKey, 'closed');
}
// 重置失败计数器(可选)
$this->redis->del($this->keyPrefix . $service . ':failures');
return $result;
} catch (\Exception $e) {
// 3. 记录失败
$failureKey = $this->keyPrefix . $service . ':failures';
$failureCount = $this->redis->incr($failureKey);
$this->redis->expire($failureKey, $this->failureWindow);
if ($failureCount >= $this->failureThreshold && ($status !== 'open')) {
$this->redis->set($statusKey, 'open');
$this->redis->set($statusKey . ':last_failure', time());
$this->redis->expire($statusKey, $this->halfOpenTimeout + 10);
}
throw $e;
}
}
}
优点: 真正的分布式,数据一致性好,性能高。
缺点: 需要 Redis 服务。
使用第三方库:Packagist 上的熔断器组件
推荐使用成熟的开源方案,joshbrw/laravel-circuit-breaker 或 acker/whelton-circuit-breaker,以最流行的组合 Laravel + Redis 为例:
安装:
composer require joshbrw/laravel-circuit-breaker
配置: 在 config/circuit-breaker.php 中配置熔断策略(阈值、超时时间等)。
使用:
use Joshbrw\LaravelCircuitBreaker\CircuitBreaker;
use Joshbrw\LaravelCircuitBreaker\Exceptions\CircuitBreakerOpenException;
try {
$users = CircuitBreaker::run('user_api', function () {
return Http::get('https://users.example.com')->json();
});
} catch (CircuitBreakerOpenException $e) {
// 熔断触发,返回降级数据
$users = Cache::get('user_cache_fallback');
}
优点: 配置简单,与框架深度集成,自带监控事件。
缺点: 有外部依赖,通常服务于特定框架(如 Laravel)。
使用 API 网关 + 微服务模式
如果项目采用微服务架构,熔断通常不在 PHP 代码层实现,而在网关层处理(如 Nginx + Lua、Kong、Spring Cloud Gateway)。
- Nginx 熔断: 通过
upstream的max_fails和fail_timeout指令实现简单的连接级熔断。 - API 网关(如 Kong + Circuit Breaker 插件): 可以按服务、路由配置熔断策略。
优点: 无侵入,集中管理。
缺点: 需要额外基础设施。
最佳实践建议
- 对于 PHP 单体或简单项目: 采用方案 2(Redis),平衡了可靠性、性能与实现成本。
- 对于使用 Laravel/Symfony 等框架的项目: 选择方案 3(第三方库),利用框架的 Cache 系统(可切换 Redis/数据库)。
- 无论是哪种方案,都需要配合:
- 降级策略:熔断后返回缓存数据、默认值或友好错误信息。
- 监控和告警:记录熔断器状态变化到日志/监控系统(如 Prometheus)。
- 错误判断需精准:只对可重试的临时错误(如网络超时、HTTP 503)进行熔断,业务错误(如 400 Bad Request)不应触发熔断。
| 实现方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 文件/内存计数器 | 单机测试、低并发 | 无依赖,直观 | 非线程安全,不跨节点 |
| Redis 计数器 | 多服务器、高并发 | 分布式,精确,性能好 | 需要维护 Redis |
| 第三方库 | 标准框架项目 | 配置简单,集成好 | 特定框架依赖 |
| API 网关 | 微服务、高流量 | 无入侵,集中控制 | 需要额外运维 |
对于一个生产级别的 PHP 项目,最推荐的是 Redis + 熔断器库 的组合,它既保证了熔断逻辑的健壮性,又避免了重复造轮子。