本文目录导读:

在 PHP 项目中,服务熔断(Circuit Breaker)的核心目的是防止级联故障——当一个下游服务(如数据库、API、第三方服务)出现故障或延迟时,能快速失败,避免资源耗尽和系统雪崩。
虽然 PHP 不像 Java(Hystrix、Resilience4j)有成熟的官方熔断库,但可以通过中间件 + 状态机手动实现轻量级熔断。
以下是几种后端代码实现方案,从简单到完整:
核心状态机模型(理论基础)
所有熔断器都有三种状态:
- CLOSED(关闭):正常调用,失败次数累积。
- OPEN(打开):直接拒绝请求,快速失败,等待超时时间。
- HALF_OPEN(半开):尝试少量请求,判断服务是否恢复。
状态转换:
CLOSED --(失败达到阈值)--> OPEN
OPEN --(超时时间到)--> HALF_OPEN
HALF_OPEN --(成功)--> CLOSED
HALF_OPEN --(失败)--> OPEN
基于 Redis 的分布式熔断实现(推荐)
适用于多 PHP 进程/多服务器场景,状态共享。
安装依赖:需要 Redis 和 predis/predis 或 phpredis。
核心代码:
<?php
class RedisCircuitBreaker
{
private $redis;
private $name;
private $failureThreshold = 5; // 失败阈值
private $timeout = 10; // 熔断打开时间(秒)
private $halfOpenMaxRequests = 2; // 半开状态允许试探次数
public function __construct(\Redis $redis, string $name)
{
$this->redis = $redis;
$this->name = $name;
}
/**
* 检查是否允许调用
*/
public function isAvailable(): bool
{
$state = $this->getState();
if ($state === 'OPEN') {
// 检查是否超时,超时则进入 HALF_OPEN
$openTime = $this->redis->get("circuit:{$this->name}:open_time");
if ($openTime && (time() - $openTime) >= $this->timeout) {
$this->setState('HALF_OPEN');
// 清除失败计数
$this->redis->del("circuit:{$this->name}:failures");
return true; // 允许一次试探
}
return false; // 熔断中,直接拒绝
}
if ($state === 'HALF_OPEN') {
// 限制半开状态下的最大试探请求数
$count = $this->redis->incr("circuit:{$this->name}:half_open_count");
$this->redis->expire("circuit:{$this->name}:half_open_count", 1); // 1秒过期防止堆积
if ($count > $this->halfOpenMaxRequests) {
return false;
}
return true;
}
// CLOSED 状态:正常放行
return true;
}
/**
* 报告调用成功
*/
public function reportSuccess(): void
{
$state = $this->getState();
if ($state === 'HALF_OPEN') {
// 半开成功 → 关闭熔断器
$this->setState('CLOSED');
$this->redis->del("circuit:{$this->name}:failures");
$this->redis->del("circuit:{$this->name}:open_time");
} else {
// CLOSED 状态成功:重置失败计数(可选)
$this->redis->set("circuit:{$this->name}:failures", 0);
}
}
/**
* 报告调用失败
*/
public function reportFailure(): void
{
$state = $this->getState();
if ($state === 'HALF_OPEN') {
// 半开失败 → 回到 OPEN 状态,重置超时时间
$this->setState('OPEN');
$this->redis->set("circuit:{$this->name}:open_time", time());
return;
}
// CLOSED 状态:增加失败计数
$failures = $this->redis->incr("circuit:{$this->name}:failures");
if ($failures >= $this->failureThreshold) {
$this->setState('OPEN');
$this->redis->set("circuit:{$this->name}:open_time", time());
}
}
private function getState(): string
{
// 内存缓存状态减少 Redis 访问(可选)
return $this->redis->get("circuit:{$this->name}:state") ?: 'CLOSED';
}
private function setState(string $state): void
{
$this->redis->set("circuit:{$this->name}:state", $state);
}
}
使用示例:
$breaker = new RedisCircuitBreaker($redis, 'order_service');
if (!$breaker->isAvailable()) {
// 返回降级响应
return ['error' => '服务繁忙,请稍后重试'];
}
try {
// 真正的远程调用
$result = $httpClient->post(...);
$breaker->reportSuccess();
return $result;
} catch (\Exception $e) {
$breaker->reportFailure();
// 降级逻辑
return fallbackResponse();
}
基于文件的单进程熔断(简单开发环境用)
适合单台服务器或开发调试。
class FileCircuitBreaker
{
private $filePath;
private $failureThreshold = 5;
private $timeout = 30;
public function isAvailable(): bool
{
$data = $this->readState();
if ($data['state'] === 'OPEN') {
if (time() - $data['open_time'] >= $this->timeout) {
$this->writeState(['state' => 'HALF_OPEN', 'failures' => 0, 'open_time' => 0]);
return true;
}
return false;
}
if ($data['state'] === 'HALF_OPEN') {
return true; // 文件锁简单放行所有试探
}
return true;
}
public function reportSuccess(): void
{
$data = $this->readState();
if ($data['state'] === 'HALF_OPEN') {
$this->writeState(['state' => 'CLOSED', 'failures' => 0, 'open_time' => 0]);
} else {
$data['failures'] = 0;
$this->writeState($data);
}
}
public function reportFailure(): void
{
$data = $this->readState();
$data['failures']++;
if ($data['failures'] >= $this->failureThreshold) {
$data['state'] = 'OPEN';
$data['open_time'] = time();
}
$this->writeState($data);
}
private function readState(): array
{
if (!file_exists($this->filePath)) {
return ['state' => 'CLOSED', 'failures' => 0, 'open_time' => 0];
}
return json_decode(file_get_contents($this->filePath), true);
}
private function writeState(array $data): void
{
file_put_contents($this->filePath, json_encode($data), LOCK_EX);
}
}
高级实现:配合 GuzzleHttp 中间件
如果你的远程调用使用 Guzzle,可以将其封装成中间件:
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
$handlerStack = HandlerStack::create();
$breaker = new RedisCircuitBreaker($redis, 'payment_api');
$handlerStack->push(Middleware::retry(function ($retries, $request, $response, $exception) use ($breaker) {
if (!$breaker->isAvailable()) {
// 熔断打开,不再重试
return false;
}
// 失败时记录
if ($exception || ($response && $response->getStatusCode() >= 500)) {
$breaker->reportFailure();
} else {
$breaker->reportSuccess();
}
return false; // 不自动重试,你可以在业务逻辑中手动重试
}), 'circuit_breaker');
$client = new Client(['handler' => $handlerStack]);
注意事项与最佳实践
| 要点 | 说明 |
|---|---|
| 状态存储 | 生产环境必须用 Redis/Memcached,避免文件锁瓶颈。 |
| 失败阈值 | 建议根据 SLA 动态调整,如 5 次/10 秒窗口,而不是固定次数。 |
| 超时时间 | 初始设置 30 秒,后续根据服务恢复速度动态调整。 |
| 半开试探 | 使用指数退避:第一次试探失败后,下次超时时间翻倍。 |
| 降级策略 | 熔断不等于空响应:返回缓存数据、默认值、友好的错误提示。 |
| 监控 | 将熔断事件写入 ELK / Prometheus,方便排查。 |
开源 PHP 熔断组件(可选)
如果不想手写,可考虑以下成熟方案:
| 组件 | 特点 |
|---|---|
| PHP Circuit Breaker | 轻量级,支持内存/Redis/APCu |
| Yii2 Circuit Breaker | Yii 框架专用 |
| Symfony Circuit Breaker | Symfony 官方组件 |
| Ganeti Circuit Breaker | 支持滑动窗口算法 |
示例:使用 PHP Circuit Breaker 库:
composer require ackintosh/php-circuit-breaker
use ackintosh\CircuitBreaker\CircuitBreaker;
$cb = new CircuitBreaker('api', [
'failureCountThreshold' => 3,
'timeout' => 30,
]);
$cb->call(function () {
// 你的远程调用
});
对于 PHP 项目,最推荐基于 Redis 的熔断器实现,具有良好的分布式支持、性能较好,核心是:
- 在每次调用前检查
isAvailable() - 调用成功上报
reportSuccess() - 调用失败上报
reportFailure() - 配合降级响应(缓存/默认值/友好提示)
这样就能有效保护你的 PHP 后端服务不被雪崩效应拖垮。