本文目录导读:

为PHP项目设置熔断机制(Circuit Breaker),通常用于保护微服务架构或API调用,防止级联故障,常见的实现方式有以下几种:
使用第三方库
推荐库:ackintosh/ganesha(基于Redis)
这是一个轻量级的PHP熔断器库。
composer require ackintosh/ganesha
使用示例:
<?php
require 'vendor/autoload.php';
use Ackintosh\Ganesha;
use Ackintosh\Ganesha\Builder;
use Ackintosh\Ganesha\Strategy\Rate;
// 创建熔断器实例
$ganesha = Builder::withRateStrategy()
->failureRateThreshold(50) // 失败率阈值(%)
->intervalToHalfOpen(10) // 半开状态等待时间(秒)
->minimumRequests(10) // 触发计算的最小请求数
->adapter(new \Ackintosh\Ganesha\Adapter\Redis(
new \Predis\Client('tcp://127.0.0.1:6379')
))
->build();
// 包装要保护的函数
try {
// 检查熔断器状态
if ($ganesha->isAvailable('service_api')) {
// 执行远程API调用
$result = callExternalApi();
// 记录成功
$ganesha->success('service_api');
return $result;
} else {
throw new \RuntimeException('Circuit is open');
}
} catch (\Throwable $e) {
// 记录失败
$ganesha->failure('service_api');
throw $e;
}
手动实现简单熔断器
适用于不需要复杂配置的场景:
<?php
class CircuitBreaker
{
private $storage;
private $threshold = 5; // 失败阈值
private $timeout = 30; // 熔断持续时间(秒)
private $halfOpenTimeout = 10; // 半开状态探测间隔
public function __construct($redis) {
$this->storage = $redis;
}
// 检查是否允许调用
public function isAvailable($service) {
$state = $this->getState($service);
if ($state === 'open') {
$lastFailure = $this->storage->get("circuit:$service:last_failure");
if ($lastFailure && (time() - $lastFailure) > $this->timeout) {
$this->setState($service, 'half-open');
return true;
}
return false;
}
return true;
}
// 记录成功
public function recordSuccess($service) {
$this->reset($service);
}
// 记录失败
public function recordFailure($service) {
$count = $this->storage->incr("circuit:$service:failures");
$this->storage->expire("circuit:$service:failures", $this->timeout);
if ($count >= $this->threshold) {
$this->setState($service, 'open');
$this->storage->set("circuit:$service:last_failure", time());
}
}
// 重置熔断器
public function reset($service) {
$this->storage->del("circuit:$service:failures");
$this->setState($service, 'closed');
}
private function getState($service) {
return $this->storage->get("circuit:$service:state") ?: 'closed';
}
private function setState($service, $state) {
$this->storage->set("circuit:$service:state", $state);
}
}
// 使用示例
$breaker = new CircuitBreaker($redis);
if ($breaker->isAvailable('payment_service')) {
try {
$result = $paymentService->processPayment($order);
$breaker->recordSuccess('payment_service');
return $result;
} catch (\Throwable $e) {
$breaker->recordFailure('payment_service');
throw $e;
}
} else {
// 返回降级响应
return ['status' => 'service_unavailable', 'message' => '支付服务暂时不可用'];
}
结合Guzzle HTTP客户端
用于HTTP API调用场景:
<?php
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
class ApiCircuitBreaker
{
private $client;
private $breaker;
public function __construct($baseUri) {
$this->breaker = new CircuitBreaker($redis);
$handler = HandlerStack::create();
$handler->push(Middleware::retry(function ($retries, $request, $response, $exception) {
if ($retries >= 3) return false;
if ($response && $response->getStatusCode() >= 500) return true;
if ($exception) return true;
return false;
}, function ($retries) {
return 100 * pow(2, $retries); // 指数退避
}));
$this->client = new Client([
'base_uri' => $baseUri,
'handler' => $handler,
'timeout' => 5,
'connect_timeout' => 3,
]);
}
public function request($method, $uri, $options = []) {
$serviceKey = md5($uri);
if (!$this->breaker->isAvailable($serviceKey)) {
throw new \RuntimeException('Circuit breaker is open');
}
try {
$response = $this->client->request($method, $uri, $options);
$this->breaker->recordSuccess($serviceKey);
return $response;
} catch (\Throwable $e) {
$this->breaker->recordFailure($serviceKey);
throw $e;
}
}
}
// 使用
$api = new ApiCircuitBreaker('https://api.example.com');
try {
$response = $api->request('GET', '/users/123');
} catch (RuntimeException $e) {
// 使用缓存数据或返回降级响应
}
使用Redis作为分布式熔断存储
对于多实例部署:
<?php
class RedisCircuitBreaker
{
private $redis;
private $luaReset = <<<LUA
local key = KEYS[1]
local failures_key = key .. ':failures'
local state_key = key .. ':state'
local last_failure_key = key .. ':last_failure'
redis.call('DEL', failures_key)
redis.call('SET', state_key, 'closed')
redis.call('DEL', last_failure_key)
return 1
LUA;
private $luaRecordFailure = <<<LUA
local key = KEYS[1]
local failures_key = key .. ':failures'
local state_key = key .. ':state'
local threshold = tonumber(ARGV[1])
local timeout = tonumber(ARGV[2])
local count = redis.call('INCR', failures_key)
redis.call('EXPIRE', failures_key, timeout)
if count >= threshold then
redis.call('SET', state_key, 'open')
redis.call('SET', key .. ':last_failure', ARGV[3])
redis.call('EXPIRE', state_key, timeout)
end
return count
LUA;
public function __construct($redis) {
$this->redis = $redis;
}
public function isAvailable($service) {
$state = $this->redis->get("circuit:$service:state");
if ($state === 'open') {
$lastFailure = $this->redis->get("circuit:$service:last_failure");
if ($lastFailure && (time() - $lastFailure) > 30) {
$this->redis->set("circuit:$service:state", 'half-open');
$this->redis->expire("circuit:$service:state", 30);
return true;
}
return false;
}
return true;
}
public function recordSuccess($service) {
$this->redis->eval($this->luaReset, 1, "circuit:$service");
}
public function recordFailure($service) {
$this->redis->eval($this->luaRecordFailure, 1,
"circuit:$service",
5, // 阈值
30, // 超时时间
time()
);
}
}
配置熔断策略的最佳实践
<?php
$config = [
'services' => [
'payment' => [
'failure_threshold' => 5, // 连续失败5次触发熔断
'timeout' => 30, // 熔断时间30秒
'half_open_requests' => 1, // 半开状态允许1个探测请求
'success_threshold' => 3, // 半开状态连续成功3次恢复
],
'user_api' => [
'failure_threshold' => 10,
'timeout' => 60,
'half_open_requests' => 3,
'success_threshold' => 5,
]
],
'monitoring' => [
'enable' => true,
'alerts' => [
'threshold' => 100, // 每分钟触发100次熔断则告警
'webhook' => 'https://hooks.slack.com/services/...'
]
]
];
关键策略建议
- 选择合适的策略:使用
ackintosh/ganesha等成熟库减少开发成本 - 合理设置阈值:根据业务QPS和重要性调整参数
- 组合使用:熔断 + 重试 + 降级 + 限流
- 监控报警:集成Prometheus/Grafana监控熔断次数
- 处理半开状态:让部分请求通过以测试服务是否恢复
- 分布式一致性:使用Redis/Predis存储状态确保所有实例同步
对于生产环境,推荐使用ackintosh/ganesha结合Redis的方案,它提供了完善的策略实现和测试覆盖。