wpmind/includes/Failover/FailoverManager.php
LinuxJoy 74db7a20af refactor: 方法名 camelCase → snake_case (39文件, WordPress 编码规范)
将项目自有代码的方法名从 camelCase 统一为 snake_case,
符合 WordPress PHP 编码规范。涉及 Routing、Failover、Budget、
Analytics、Usage、ErrorHandler 等模块。外部库接口方法和
Image Provider 子系统保持不变。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-07 03:21:31 +08:00

269 lines
7.7 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Failover Manager - 故障转移管理器
*
* 管理多个 Provider 的故障转移逻辑
*
* @package WPMind
* @since 1.5.0
*/
declare(strict_types=1);
namespace WPMind\Failover;
class FailoverManager
{
private static ?FailoverManager $instance = null;
/** @var array<string, CircuitBreaker> */
private array $circuitBreakers = [];
/** @var array Provider 配置 */
private array $providers = [];
/**
* 获取单例实例
*/
public static function instance(): FailoverManager
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct()
{
// 从 WPMind 获取已启用的 Provider
if (function_exists('WPMind\\wpmind')) {
$endpoints = \WPMind\wpmind()->get_custom_endpoints();
foreach ($endpoints as $id => $config) {
if (!empty($config['enabled']) && !empty($config['api_key'])) {
$this->providers[$id] = $config;
$this->circuitBreakers[$id] = new CircuitBreaker($id);
}
}
}
}
/**
* 选择最佳可用 Provider
*
* 优先级:
* 1. 用户设置的首选 Provider如果可用
* 2. 健康分数最高的 Provider
*
* @param string|null $preferredProvider 首选 Provider ID
* @return string|null 选中的 Provider ID
*/
public function select_provider(?string $preferredProvider = null): ?string
{
$available = $this->get_available_providers();
if (empty($available)) {
return null;
}
// 优先使用用户首选
if ($preferredProvider && in_array($preferredProvider, $available, true)) {
return $preferredProvider;
}
// 按健康分数排序
usort($available, function ($a, $b) {
$scoreA = ProviderHealthTracker::get_health_score($a);
$scoreB = ProviderHealthTracker::get_health_score($b);
// 分数相同时,按延迟排序
if ($scoreA === $scoreB) {
$latencyA = ProviderHealthTracker::get_average_latency($a);
$latencyB = ProviderHealthTracker::get_average_latency($b);
return $latencyA - $latencyB;
}
return $scoreB - $scoreA;
});
return $available[0];
}
/**
* 获取所有可用的 Provider
*
* @return array<string> 可用的 Provider ID 列表
*/
public function get_available_providers(): array
{
$available = [];
foreach ($this->circuitBreakers as $providerId => $breaker) {
if ($breaker->is_available()) {
$available[] = $providerId;
}
}
return $available;
}
/**
* 获取故障转移链
*
* 返回按优先级排序的 Provider 列表,用于依次尝试
*
* @param string|null $preferredProvider 首选 Provider ID
* @return array<string> Provider ID 列表
*/
public function get_failover_chain(?string $preferredProvider = null): array
{
$available = $this->get_available_providers();
if (empty($available)) {
return [];
}
// 检查是否有手动优先级设置
$manual_priority = [];
if (class_exists('\\WPMind\\Routing\\IntelligentRouter')) {
$router = \WPMind\Routing\IntelligentRouter::instance();
$manual_priority = $router->get_manual_priority();
}
if (!empty($manual_priority)) {
// 使用手动优先级排序
$sorted = [];
foreach ($manual_priority as $providerId) {
if (in_array($providerId, $available, true)) {
$sorted[] = $providerId;
}
}
// 添加未在手动列表中的可用 Provider
foreach ($available as $providerId) {
if (!in_array($providerId, $sorted, true)) {
$sorted[] = $providerId;
}
}
$available = $sorted;
} else {
// 按健康分数排序
usort($available, function ($a, $b) {
return ProviderHealthTracker::get_health_score($b) - ProviderHealthTracker::get_health_score($a);
});
}
// 首选 Provider 放在最前面
if ($preferredProvider && in_array($preferredProvider, $available, true)) {
$available = array_values(array_diff($available, [$preferredProvider]));
array_unshift($available, $preferredProvider);
}
return $available;
}
/**
* 记录请求结果
*
* @param string $providerId Provider ID
* @param bool $success 是否成功
* @param int $latencyMs 延迟(毫秒)
*/
public function record_result(string $providerId, bool $success, int $latencyMs = 0): void
{
// 更新熔断器状态
if (isset($this->circuitBreakers[$providerId])) {
if ($success) {
$this->circuitBreakers[$providerId]->record_success();
} else {
$this->circuitBreakers[$providerId]->record_failure();
}
}
// 记录健康追踪
ProviderHealthTracker::record($providerId, $success, $latencyMs);
}
/**
* 获取 Provider 状态摘要
*
* @return array Provider 状态信息
*/
public function get_status_summary(): array
{
$summary = [];
foreach ($this->circuitBreakers as $providerId => $breaker) {
$cbStatus = $breaker->get_status_details();
$healthStatus = ProviderHealthTracker::get_provider_status($providerId);
$summary[$providerId] = [
'name' => $this->providers[$providerId]['name'] ?? $providerId,
'display_name' => $this->providers[$providerId]['display_name'] ?? $providerId,
'state' => $cbStatus['state'],
'state_label' => $cbStatus['state_label'],
'available' => $breaker->is_available_read_only(),
'health_score' => $healthStatus['health_score'],
'avg_latency' => $healthStatus['avg_latency'],
'success_rate' => $healthStatus['success_rate'],
'recovery_in' => $cbStatus['recovery_in'],
];
}
return $summary;
}
/**
* 检查是否有可用的 Provider
*
* @return bool
*/
public function has_available_provider(): bool
{
return !empty($this->get_available_providers());
}
/**
* 重置指定 Provider 的熔断器
*
* @param string $providerId Provider ID
*/
public function reset_provider(string $providerId): void
{
if (isset($this->circuitBreakers[$providerId])) {
$this->circuitBreakers[$providerId]->reset();
}
ProviderHealthTracker::clear_provider($providerId);
}
/**
* 重置所有熔断器
*/
public function reset_all(): void
{
foreach ($this->circuitBreakers as $breaker) {
$breaker->reset();
}
ProviderHealthTracker::clear_all();
}
/**
* 获取熔断器实例
*
* @param string $providerId Provider ID
* @return CircuitBreaker|null
*/
public function get_circuit_breaker(string $providerId): ?CircuitBreaker
{
return $this->circuitBreakers[$providerId] ?? null;
}
/**
* 刷新 Provider 列表
*
* 当 Provider 配置变更时调用
*/
public function refresh(): void
{
self::$instance = null;
self::instance();
}
}