本文目录导读:

在PHP项目中实现熔断降级,通常有两种主要思路:
轻量级方案:使用Redis实现熔断器
基础熔断器类
<?php
class CircuitBreaker {
private $redis;
private $prefix = 'circuit_breaker:';
private $failureThreshold = 5; // 失败阈值
private $successThreshold = 3; // 半开状态下成功阈值
private $timeout = 30; // 熔断时间(秒)
private $halfOpenTimeout = 10; // 半开状态超时
public function __construct($redis) {
$this->redis = $redis;
}
// 检查是否可以执行操作
public function isAvailable($service) {
$status = $this->getStatus($service);
switch ($status) {
case 'closed':
return true;
case 'open':
// 检查是否达到超时时间
$openedAt = $this->redis->get($this->prefix . $service . ':opened_at');
if (time() - $openedAt >= $this->timeout) {
$this->setStatus($service, 'half_open');
return true;
}
return false;
case 'half_open':
return true;
default:
return true;
}
}
// 记录成功
public function recordSuccess($service) {
$status = $this->getStatus($service);
if ($status === 'half_open') {
$successCount = $this->redis->incr($this->prefix . $service . ':success_count');
if ($successCount >= $this->successThreshold) {
$this->resetService($service);
}
}
// 重置失败计数
$this->redis->del($this->prefix . $service . ':failure_count');
}
// 记录失败
public function recordFailure($service) {
$status = $this->getStatus($service);
if ($status === 'closed' || $status === 'half_open') {
$failureCount = $this->redis->incr($this->prefix . $service . ':failure_count');
if ($failureCount >= $this->failureThreshold) {
$this->setOpened($service);
}
}
}
private function getStatus($service) {
return $this->redis->get($this->prefix . $service . ':status') ?: 'closed';
}
private function setStatus($service, $status) {
$this->redis->set($this->prefix . $service . ':status', $status);
}
private function setOpened($service) {
$this->setStatus($service, 'open');
$this->redis->set($this->prefix . $service . ':opened_at', time());
}
private function resetService($service) {
$this->redis->del($this->prefix . $service . ':status');
$this->redis->del($this->prefix . $service . ':opened_at');
$this->redis->del($this->prefix . $service . ':failure_count');
$this->redis->del($this->prefix . $service . ':success_count');
}
}
使用示例
<?php
class UserService {
private $circuitBreaker;
private $redis;
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
$this->circuitBreaker = new CircuitBreaker($this->redis);
}
public function getUser($userId) {
$service = 'user_api';
// 检查熔断状态
if (!$this->circuitBreaker->isAvailable($service)) {
return $this->fallbackGetUser($userId);
}
try {
// 调用远程服务
$result = $this->callRemoteUserApi($userId);
// 记录成功
$this->circuitBreaker->recordSuccess($service);
return $result;
} catch (Exception $e) {
// 记录失败
$this->circuitBreaker->recordFailure($service);
// 执行降级方案
return $this->fallbackGetUser($userId);
}
}
// 降级方案:从本地缓存获取
private function fallbackGetUser($userId) {
// 从缓存获取用户信息
$cachedUser = $this->redis->get('user:' . $userId);
if ($cachedUser) {
return json_decode($cachedUser, true);
}
// 返回默认值或错误提示
return [
'error' => 'service_unavailable',
'message' => '用户服务暂时不可用,请稍后重试'
];
}
private function callRemoteUserApi($userId) {
// 模拟远程调用
$url = "https://api.example.com/users/{$userId}";
$response = file_get_contents($url);
if ($response === false) {
throw new Exception('API调用失败');
}
return json_decode($response, true);
}
}
使用中间件方案(框架集成)
Laravel/Symfony 中间件实现
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Redis;
use Illuminate\Http\Request;
class CircuitBreakerMiddleware
{
private $config;
// 配置:服务名 => [阈值, 超时时间, 半开成功阈值]
private $services = [
'user_service' => [5, 30, 3],
'order_service' => [3, 60, 2],
];
public function handle(Request $request, Closure $next, $service)
{
// 检查熔断
if ($this->isOpen($service)) {
return response()->json([
'code' => 503,
'message' => '服务暂时不可用',
'fallback' => true
], 503);
}
try {
$response = $next($request);
// 记录成功
$this->recordSuccess($service);
return $response;
} catch (\Exception $e) {
// 记录失败
$this->recordFailure($service);
// 触发降级
return $this->degrade($service, $request);
}
}
private function isOpen($service)
{
$status = Redis::get("circuit_breaker:{$service}:status");
if ($status === 'open') {
$openedAt = Redis::get("circuit_breaker:{$service}:opened_at");
$timeout = $this->services[$service][1] ?? 30;
if (time() - $openedAt >= $timeout) {
Redis::set("circuit_breaker:{$service}:status", 'half_open');
return false;
}
return true;
}
return false;
}
private function recordSuccess($service)
{
$status = Redis::get("circuit_breaker:{$service}:status");
if ($status === 'half_open') {
$successCount = Redis::incr("circuit_breaker:{$service}:success_count");
$threshold = $this->services[$service][2] ?? 3;
if ($successCount >= $threshold) {
// 恢复正常
Redis::del("circuit_breaker:{$service}:status");
Redis::del("circuit_breaker:{$service}:opened_at");
Redis::del("circuit_breaker:{$service}:failure_count");
Redis::del("circuit_breaker:{$service}:success_count");
}
} else {
// 重置失败计数
Redis::del("circuit_breaker:{$service}:failure_count");
}
}
private function recordFailure($service)
{
$status = Redis::get("circuit_breaker:{$service}:status") ?: 'closed';
if ($status === 'closed' || $status === 'half_open') {
$failureCount = Redis::incr("circuit_breaker:{$service}:failure_count");
$threshold = $this->services[$service][0] ?? 5;
if ($failureCount >= $threshold) {
Redis::set("circuit_breaker:{$service}:status", 'open');
Redis::set("circuit_breaker:{$service}:opened_at", time());
}
}
}
private function degrade($service, $request)
{
// 根据服务类型执行不同的降级策略
switch ($service) {
case 'user_service':
return $this->userServiceDegrade($request);
case 'order_service':
return $this->orderServiceDegrade($request);
default:
return response()->json(['message' => '服务降级'], 503);
}
}
private function userServiceDegrade($request)
{
$userId = $request->route('user');
// 从缓存获取用户信息
$cached = Redis::get("user_cache:{$userId}");
if ($cached) {
return response()->json(json_decode($cached, true));
}
// 返回默认用户信息
return response()->json([
'id' => $userId,
'name' => '未知用户',
'is_cache' => true
]);
}
private function orderServiceDegrade($request)
{
// 订单服务降级:只返回缓存数据
$orderId = $request->route('order');
return response()->json([
'order_id' => $orderId,
'status' => 'unknown',
'degraded' => true
]);
}
}
路由配置(Laravel)
// routes/api.php
Route::middleware('circuit_breaker:user_service')->group(function () {
Route::get('/users/{user}', 'UserController@show');
Route::post('/users', 'UserController@store');
});
Route::middleware('circuit_breaker:order_service')->group(function () {
Route::get('/orders/{order}', 'OrderController@show');
});
集成监控和告警
<?php
class CircuitBreakerMonitor
{
private $circuitBreaker;
public function collectMetrics($service)
{
$metrics = [
'service' => $service,
'status' => $this->circuitBreaker->getStatus($service),
'failure_count' => $this->circuitBreaker->getFailureCount($service),
'success_count' => $this->circuitBreaker->getSuccessCount($service),
'last_opened_at' => $this->circuitBreaker->getOpenedAt($service),
'timestamp' => time()
];
// 存储到监控系统
$this->storeMetrics($metrics);
// 检查是否需要告警
if ($metrics['status'] === 'open') {
$this->sendAlert($service, $metrics);
}
return $metrics;
}
private function storeMetrics($metrics)
{
// 存储到数据库或监控系统
// ...
}
private function sendAlert($service, $metrics)
{
// 发送告警通知(邮件、短信、企业微信等)
$message = "服务 {$service} 已被熔断,时间:{$metrics['last_opened_at']}";
// 发送到告警通道
// ...
}
}
最佳实践建议
- 合理设置阈值:根据业务重要性和服务稳定性调整参数
- 实施分级降级:核心功能保留,非核心功能降级
- 缓存策略:使用多级缓存(本地内存+Redis)
- 监控告警:实时监控熔断状态,及时发现问题
- 渐进式恢复:半开状态下逐步放量恢复
- 隔离性:不同服务使用独立的熔断器实例
注意事项
- PHP本身是无状态进程,熔断状态需要依赖外部存储(Redis/Memcached)
- 考虑使用异步处理,避免在请求周期内做过多操作
- 对于高并发场景,考虑使用本地内存缓存熔断状态减少Redis压力
- 降级响应要友好,给用户明确的提示信息