mirror of
https://devops.lemonos.cn/lawson/FendxPHP.git
synced 2026-06-15 23:12:49 +08:00
858 lines
27 KiB
PHP
858 lines
27 KiB
PHP
|
|
<?php
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace Fendx\Service\LoadBalancer\Strategy;
|
||
|
|
|
||
|
|
use Fendx\Service\LoadBalancer\Strategy\Router\TrafficRouter;
|
||
|
|
use Fendx\Service\LoadBalancer\Strategy\Analyzer\TrafficAnalyzer;
|
||
|
|
use Fendx\Service\LoadBalancer\Strategy\Balancer\TrafficBalancer;
|
||
|
|
use Fendx\Service\LoadBalancer\Strategy\Monitor\TrafficMonitor;
|
||
|
|
|
||
|
|
class TrafficDistributionStrategy
|
||
|
|
{
|
||
|
|
protected TrafficRouter $router;
|
||
|
|
protected TrafficAnalyzer $analyzer;
|
||
|
|
protected TrafficBalancer $balancer;
|
||
|
|
protected TrafficMonitor $monitor;
|
||
|
|
protected array $config = [];
|
||
|
|
protected array $rules = [];
|
||
|
|
protected array $trafficStats = [];
|
||
|
|
protected array $distributionHistory = [];
|
||
|
|
|
||
|
|
public function __construct(array $config = [])
|
||
|
|
{
|
||
|
|
$this->config = array_merge($this->getDefaultConfig(), $config);
|
||
|
|
$this->router = new TrafficRouter($this->config);
|
||
|
|
$this->analyzer = new TrafficAnalyzer($this->config);
|
||
|
|
$this->balancer = new TrafficBalancer($this->config);
|
||
|
|
$this->monitor = new TrafficMonitor($this->config);
|
||
|
|
|
||
|
|
$this->initialize();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Distribute traffic to services based on strategy.
|
||
|
|
*/
|
||
|
|
public function distributeTraffic(array $instances, array $request, string $strategy = 'weighted'): ?array
|
||
|
|
{
|
||
|
|
if (empty($instances)) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Analyze request
|
||
|
|
$requestAnalysis = $this->analyzer->analyzeRequest($request);
|
||
|
|
|
||
|
|
// Apply routing rules
|
||
|
|
$filteredInstances = $this->applyRoutingRules($instances, $requestAnalysis);
|
||
|
|
|
||
|
|
if (empty($filteredInstances)) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Select instance based on strategy
|
||
|
|
$selectedInstance = $this->selectInstance($filteredInstances, $requestAnalysis, $strategy);
|
||
|
|
|
||
|
|
if ($selectedInstance) {
|
||
|
|
// Record traffic distribution
|
||
|
|
$this->recordTrafficDistribution($selectedInstance['id'], $requestAnalysis, $strategy);
|
||
|
|
|
||
|
|
// Update monitoring
|
||
|
|
$this->monitor->recordRequest($selectedInstance['id'], $requestAnalysis);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $selectedInstance;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add traffic distribution rule.
|
||
|
|
*/
|
||
|
|
public function addRule(string $name, array $rule): void
|
||
|
|
{
|
||
|
|
$this->validateRule($rule);
|
||
|
|
|
||
|
|
$this->rules[$name] = array_merge([
|
||
|
|
'name' => $name,
|
||
|
|
'enabled' => true,
|
||
|
|
'priority' => 100,
|
||
|
|
'conditions' => [],
|
||
|
|
'actions' => [],
|
||
|
|
'weight' => 1,
|
||
|
|
'created_at' => time(),
|
||
|
|
'updated_at' => time()
|
||
|
|
], $rule);
|
||
|
|
|
||
|
|
// Sort rules by priority
|
||
|
|
uasort($this->rules, function($a, $b) {
|
||
|
|
return $a['priority'] <=> $b['priority'];
|
||
|
|
});
|
||
|
|
|
||
|
|
$this->logInfo("Traffic distribution rule added: {$name}");
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Remove traffic distribution rule.
|
||
|
|
*/
|
||
|
|
public function removeRule(string $name): bool
|
||
|
|
{
|
||
|
|
if (!isset($this->rules[$name])) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
unset($this->rules[$name]);
|
||
|
|
|
||
|
|
$this->logInfo("Traffic distribution rule removed: {$name}");
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update traffic distribution rule.
|
||
|
|
*/
|
||
|
|
public function updateRule(string $name, array $updates): bool
|
||
|
|
{
|
||
|
|
if (!isset($this->rules[$name])) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->rules[$name] = array_merge($this->rules[$name], $updates);
|
||
|
|
$this->rules[$name]['updated_at'] = time();
|
||
|
|
|
||
|
|
$this->logInfo("Traffic distribution rule updated: {$name}");
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all rules.
|
||
|
|
*/
|
||
|
|
public function getRules(): array
|
||
|
|
{
|
||
|
|
return $this->rules;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get rule by name.
|
||
|
|
*/
|
||
|
|
public function getRule(string $name): ?array
|
||
|
|
{
|
||
|
|
return $this->rules[$name] ?? null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Enable/disable rule.
|
||
|
|
*/
|
||
|
|
public function setRuleEnabled(string $name, bool $enabled): bool
|
||
|
|
{
|
||
|
|
if (!isset($this->rules[$name])) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->rules[$name]['enabled'] = $enabled;
|
||
|
|
$this->rules[$name]['updated_at'] = time();
|
||
|
|
|
||
|
|
$this->logInfo("Rule " . ($enabled ? 'enabled' : 'disabled') . ": {$name}");
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set traffic weights for services.
|
||
|
|
*/
|
||
|
|
public function setTrafficWeights(array $weights): void
|
||
|
|
{
|
||
|
|
$this->balancer->setWeights($weights);
|
||
|
|
|
||
|
|
$this->logInfo("Traffic weights updated for " . count($weights) . " services");
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get traffic weights.
|
||
|
|
*/
|
||
|
|
public function getTrafficWeights(): array
|
||
|
|
{
|
||
|
|
return $this->balancer->getWeights();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Distribute traffic by percentage.
|
||
|
|
*/
|
||
|
|
public function distributeByPercentage(array $instances, array $percentages): ?array
|
||
|
|
{
|
||
|
|
if (empty($instances) || empty($percentages)) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate percentages sum to 100
|
||
|
|
$totalPercentage = array_sum($percentages);
|
||
|
|
if (abs($totalPercentage - 100) > 0.01) {
|
||
|
|
throw new \InvalidArgumentException("Percentages must sum to 100, got: {$totalPercentage}");
|
||
|
|
}
|
||
|
|
|
||
|
|
// Select instance based on percentage distribution
|
||
|
|
return $this->balancer->selectByPercentage($instances, $percentages);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Distribute traffic by geographic location.
|
||
|
|
*/
|
||
|
|
public function distributeByGeography(array $instances, string $clientLocation): ?array
|
||
|
|
{
|
||
|
|
if (empty($instances)) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Find closest instances
|
||
|
|
$closestInstances = $this->findClosestInstances($instances, $clientLocation);
|
||
|
|
|
||
|
|
if (empty($closestInstances)) {
|
||
|
|
// Fallback to any instance
|
||
|
|
return $instances[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
// Select from closest instances using weighted random
|
||
|
|
return $this->balancer->selectWeightedRandom($closestInstances);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Distribute traffic by user segment.
|
||
|
|
*/
|
||
|
|
public function distributeBySegment(array $instances, string $userSegment): ?array
|
||
|
|
{
|
||
|
|
if (empty($instances)) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Filter instances by segment
|
||
|
|
$segmentInstances = array_filter($instances, function($instance) use ($userSegment) {
|
||
|
|
$segments = $instance['segments'] ?? [];
|
||
|
|
return in_array($userSegment, $segments) || in_array('all', $segments);
|
||
|
|
});
|
||
|
|
|
||
|
|
if (empty($segmentInstances)) {
|
||
|
|
// Fallback to instances without segment restriction
|
||
|
|
$segmentInstances = array_filter($instances, function($instance) {
|
||
|
|
$segments = $instance['segments'] ?? [];
|
||
|
|
return empty($segments) || in_array('all', $segments);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (empty($segmentInstances)) {
|
||
|
|
return $instances[0]; // Ultimate fallback
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->balancer->selectRoundRobin(array_values($segmentInstances));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Distribute traffic by time of day.
|
||
|
|
*/
|
||
|
|
public function distributeByTime(array $instances, \DateTime $dateTime = null): ?array
|
||
|
|
{
|
||
|
|
$dateTime = $dateTime ?? new \DateTime();
|
||
|
|
$hour = (int) $dateTime->format('H');
|
||
|
|
|
||
|
|
if (empty($instances)) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Filter instances by time availability
|
||
|
|
$availableInstances = array_filter($instances, function($instance) use ($hour) {
|
||
|
|
$availability = $instance['availability'] ?? [];
|
||
|
|
|
||
|
|
if (empty($availability)) {
|
||
|
|
return true; // Always available
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ($availability as $period) {
|
||
|
|
if ($hour >= $period['start'] && $hour < $period['end']) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
});
|
||
|
|
|
||
|
|
if (empty($availableInstances)) {
|
||
|
|
return $instances[0]; // Fallback
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->balancer->selectWeightedRandom(array_values($availableInstances));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Distribute traffic by load capacity.
|
||
|
|
*/
|
||
|
|
public function distributeByCapacity(array $instances): ?array
|
||
|
|
{
|
||
|
|
if (empty($instances)) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate available capacity for each instance
|
||
|
|
$capacityScores = [];
|
||
|
|
|
||
|
|
foreach ($instances as $instance) {
|
||
|
|
$maxCapacity = $instance['max_capacity'] ?? 100;
|
||
|
|
$currentLoad = $instance['current_load'] ?? 0;
|
||
|
|
$availableCapacity = max(0, $maxCapacity - $currentLoad);
|
||
|
|
|
||
|
|
$capacityScores[$instance['id']] = $availableCapacity;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Select instance with most available capacity
|
||
|
|
$maxCapacity = max($capacityScores);
|
||
|
|
$bestInstanceIds = array_keys($capacityScores, $maxCapacity);
|
||
|
|
|
||
|
|
if (count($bestInstanceIds) === 1) {
|
||
|
|
$bestInstanceId = $bestInstanceIds[0];
|
||
|
|
} else {
|
||
|
|
// Multiple instances with same capacity, use round-robin
|
||
|
|
$bestInstanceId = $bestInstanceIds[array_rand($bestInstanceIds)];
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ($instances as $instance) {
|
||
|
|
if ($instance['id'] === $bestInstanceId) {
|
||
|
|
return $instance;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get traffic distribution statistics.
|
||
|
|
*/
|
||
|
|
public function getStatistics(): array
|
||
|
|
{
|
||
|
|
$totalRequests = 0;
|
||
|
|
$serviceStats = [];
|
||
|
|
$ruleStats = [];
|
||
|
|
|
||
|
|
// Calculate service statistics
|
||
|
|
foreach ($this->trafficStats as $serviceId => $stats) {
|
||
|
|
$requests = $stats['total_requests'] ?? 0;
|
||
|
|
$totalRequests += $requests;
|
||
|
|
|
||
|
|
$serviceStats[$serviceId] = [
|
||
|
|
'total_requests' => $requests,
|
||
|
|
'percentage' => 0, // Will be calculated below
|
||
|
|
'average_response_time' => $this->calculateAverageResponseTime($serviceId),
|
||
|
|
'error_rate' => $this->calculateErrorRate($serviceId),
|
||
|
|
'last_request' => $stats['last_request'] ?? null
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate percentages
|
||
|
|
if ($totalRequests > 0) {
|
||
|
|
foreach ($serviceStats as $serviceId => &$stats) {
|
||
|
|
$stats['percentage'] = ($stats['total_requests'] / $totalRequests) * 100;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate rule statistics
|
||
|
|
foreach ($this->rules as $ruleName => $rule) {
|
||
|
|
$ruleStats[$ruleName] = [
|
||
|
|
'enabled' => $rule['enabled'],
|
||
|
|
'priority' => $rule['priority'],
|
||
|
|
'match_count' => $rule['match_count'] ?? 0,
|
||
|
|
'last_matched' => $rule['last_matched'] ?? null
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
return [
|
||
|
|
'total_requests' => $totalRequests,
|
||
|
|
'service_statistics' => $serviceStats,
|
||
|
|
'rule_statistics' => $ruleStats,
|
||
|
|
'active_rules' => count(array_filter($this->rules, fn($r) => $r['enabled'])),
|
||
|
|
'distribution_history' => array_slice($this->distributionHistory, -10)
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get traffic distribution history.
|
||
|
|
*/
|
||
|
|
public function getDistributionHistory(int $limit = 100): array
|
||
|
|
{
|
||
|
|
return array_slice($this->distributionHistory, -$limit);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get real-time traffic metrics.
|
||
|
|
*/
|
||
|
|
public function getRealTimeMetrics(): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'current_rps' => $this->monitor->getCurrentRPS(),
|
||
|
|
'active_connections' => $this->monitor->getActiveConnections(),
|
||
|
|
'average_response_time' => $this->monitor->getAverageResponseTime(),
|
||
|
|
'error_rate' => $this->monitor->getErrorRate(),
|
||
|
|
'top_services' => $this->monitor->getTopServices(10),
|
||
|
|
'geographic_distribution' => $this->monitor->getGeographicDistribution(),
|
||
|
|
'user_segment_distribution' => $this->monitor->getSegmentDistribution()
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Optimize traffic distribution.
|
||
|
|
*/
|
||
|
|
public function optimizeDistribution(): array
|
||
|
|
{
|
||
|
|
$recommendations = [];
|
||
|
|
$currentStats = $this->getStatistics();
|
||
|
|
|
||
|
|
// Analyze service performance
|
||
|
|
foreach ($currentStats['service_statistics'] as $serviceId => $stats) {
|
||
|
|
if ($stats['error_rate'] > 5) {
|
||
|
|
$recommendations[] = [
|
||
|
|
'type' => 'high_error_rate',
|
||
|
|
'service_id' => $serviceId,
|
||
|
|
'message' => "Service {$serviceId} has high error rate: {$stats['error_rate']}%",
|
||
|
|
'action' => 'consider_reducing_weight_or_failing_over'
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($stats['average_response_time'] > 1000) { // 1 second
|
||
|
|
$recommendations[] = [
|
||
|
|
'type' => 'slow_response',
|
||
|
|
'service_id' => $serviceId,
|
||
|
|
'message' => "Service {$serviceId} has slow response time: {$stats['average_response_time']}ms",
|
||
|
|
'action' => 'consider_reducing_weight'
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Analyze rule effectiveness
|
||
|
|
foreach ($currentStats['rule_statistics'] as $ruleName => $stats) {
|
||
|
|
if ($stats['enabled'] && $stats['match_count'] === 0) {
|
||
|
|
$recommendations[] = [
|
||
|
|
'type' => 'unused_rule',
|
||
|
|
'rule_name' => $ruleName,
|
||
|
|
'message' => "Rule {$ruleName} is enabled but never matches",
|
||
|
|
'action' => 'review_or_disable_rule'
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Suggest weight adjustments
|
||
|
|
$weightSuggestions = $this->suggestWeightAdjustments();
|
||
|
|
$recommendations = array_merge($recommendations, $weightSuggestions);
|
||
|
|
|
||
|
|
return $recommendations;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Reset traffic statistics.
|
||
|
|
*/
|
||
|
|
public function resetStatistics(): void
|
||
|
|
{
|
||
|
|
$this->trafficStats = [];
|
||
|
|
$this->distributionHistory = [];
|
||
|
|
|
||
|
|
foreach ($this->rules as &$rule) {
|
||
|
|
$rule['match_count'] = 0;
|
||
|
|
$rule['last_matched'] = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->logInfo("Traffic statistics reset");
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Export traffic configuration.
|
||
|
|
*/
|
||
|
|
public function exportConfiguration(string $format = 'json'): string
|
||
|
|
{
|
||
|
|
$data = [
|
||
|
|
'rules' => $this->rules,
|
||
|
|
'traffic_weights' => $this->getTrafficWeights(),
|
||
|
|
'statistics' => $this->getStatistics(),
|
||
|
|
'distribution_history' => $this->distributionHistory,
|
||
|
|
'exported_at' => date('Y-m-d H:i:s'),
|
||
|
|
'version' => $this->config['version'] ?? '1.0'
|
||
|
|
];
|
||
|
|
|
||
|
|
switch ($format) {
|
||
|
|
case 'json':
|
||
|
|
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||
|
|
case 'php':
|
||
|
|
return '<?php return ' . var_export($data, true) . ';';
|
||
|
|
default:
|
||
|
|
throw new \InvalidArgumentException("Unsupported export format: {$format}");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Apply routing rules to instances.
|
||
|
|
*/
|
||
|
|
protected function applyRoutingRules(array $instances, array $requestAnalysis): array
|
||
|
|
{
|
||
|
|
$filteredInstances = $instances;
|
||
|
|
|
||
|
|
foreach ($this->rules as $rule) {
|
||
|
|
if (!$rule['enabled']) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($this->matchesRuleConditions($rule['conditions'], $requestAnalysis)) {
|
||
|
|
$rule['match_count'] = ($rule['match_count'] ?? 0) + 1;
|
||
|
|
$rule['last_matched'] = time();
|
||
|
|
|
||
|
|
// Apply rule actions
|
||
|
|
$filteredInstances = $this->applyRuleActions($filteredInstances, $rule['actions']);
|
||
|
|
|
||
|
|
// If rule has stop_processing flag, break
|
||
|
|
if ($rule['stop_processing'] ?? false) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $filteredInstances;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if request matches rule conditions.
|
||
|
|
*/
|
||
|
|
protected function matchesRuleConditions(array $conditions, array $requestAnalysis): bool
|
||
|
|
{
|
||
|
|
foreach ($conditions as $condition) {
|
||
|
|
if (!$this->matchesCondition($condition, $requestAnalysis)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if single condition matches.
|
||
|
|
*/
|
||
|
|
protected function matchesCondition(array $condition, array $requestAnalysis): bool
|
||
|
|
{
|
||
|
|
$field = $condition['field'];
|
||
|
|
$operator = $condition['operator'];
|
||
|
|
$value = $condition['value'];
|
||
|
|
$requestValue = $this->getRequestValue($requestAnalysis, $field);
|
||
|
|
|
||
|
|
switch ($operator) {
|
||
|
|
case 'equals':
|
||
|
|
return $requestValue === $value;
|
||
|
|
case 'not_equals':
|
||
|
|
return $requestValue !== $value;
|
||
|
|
case 'in':
|
||
|
|
return in_array($requestValue, (array) $value);
|
||
|
|
case 'not_in':
|
||
|
|
return !in_array($requestValue, (array) $value);
|
||
|
|
case 'contains':
|
||
|
|
return is_string($requestValue) && strpos($requestValue, $value) !== false;
|
||
|
|
case 'starts_with':
|
||
|
|
return is_string($requestValue) && strpos($requestValue, $value) === 0;
|
||
|
|
case 'ends_with':
|
||
|
|
return is_string($requestValue) && substr($requestValue, -strlen($value)) === $value;
|
||
|
|
case 'greater_than':
|
||
|
|
return $requestValue > $value;
|
||
|
|
case 'less_than':
|
||
|
|
return $requestValue < $value;
|
||
|
|
case 'between':
|
||
|
|
return $requestValue >= $value[0] && $requestValue <= $value[1];
|
||
|
|
case 'regex':
|
||
|
|
return preg_match($value, (string) $requestValue) === 1;
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get value from request analysis.
|
||
|
|
*/
|
||
|
|
protected function getRequestValue(array $requestAnalysis, string $field)
|
||
|
|
{
|
||
|
|
$fields = explode('.', $field);
|
||
|
|
$value = $requestAnalysis;
|
||
|
|
|
||
|
|
foreach ($fields as $f) {
|
||
|
|
if (!is_array($value) || !array_key_exists($f, $value)) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
$value = $value[$f];
|
||
|
|
}
|
||
|
|
|
||
|
|
return $value;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Apply rule actions to instances.
|
||
|
|
*/
|
||
|
|
protected function applyRuleActions(array $instances, array $actions): array
|
||
|
|
{
|
||
|
|
$filteredInstances = $instances;
|
||
|
|
|
||
|
|
foreach ($actions as $action) {
|
||
|
|
switch ($action['type']) {
|
||
|
|
case 'filter':
|
||
|
|
$filteredInstances = $this->filterInstances($filteredInstances, $action['conditions']);
|
||
|
|
break;
|
||
|
|
case 'set_weight':
|
||
|
|
$filteredInstances = $this->setInstanceWeights($filteredInstances, $action['weights']);
|
||
|
|
break;
|
||
|
|
case 'prioritize':
|
||
|
|
$filteredInstances = $this->prioritizeInstances($filteredInstances, $action['criteria']);
|
||
|
|
break;
|
||
|
|
case 'exclude':
|
||
|
|
$filteredInstances = $this->excludeInstances($filteredInstances, $action['instances']);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $filteredInstances;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Select instance based on strategy.
|
||
|
|
*/
|
||
|
|
protected function selectInstance(array $instances, array $requestAnalysis, string $strategy): ?array
|
||
|
|
{
|
||
|
|
switch ($strategy) {
|
||
|
|
case 'weighted':
|
||
|
|
return $this->balancer->selectWeightedRandom($instances);
|
||
|
|
case 'round_robin':
|
||
|
|
return $this->balancer->selectRoundRobin($instances);
|
||
|
|
case 'least_connections':
|
||
|
|
return $this->balancer->selectLeastConnections($instances);
|
||
|
|
case 'response_time':
|
||
|
|
return $this->balancer->selectByResponseTime($instances);
|
||
|
|
case 'geographic':
|
||
|
|
$location = $requestAnalysis['client']['location'] ?? 'unknown';
|
||
|
|
return $this->distributeByGeography($instances, $location);
|
||
|
|
case 'segment':
|
||
|
|
$segment = $requestAnalysis['user']['segment'] ?? 'default';
|
||
|
|
return $this->distributeBySegment($instances, $segment);
|
||
|
|
case 'capacity':
|
||
|
|
return $this->distributeByCapacity($instances);
|
||
|
|
default:
|
||
|
|
return $instances[0];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Find closest instances by geography.
|
||
|
|
*/
|
||
|
|
protected function findClosestInstances(array $instances, string $clientLocation): array
|
||
|
|
{
|
||
|
|
// This would use a proper geolocation service in production
|
||
|
|
// For now, return instances with location matching
|
||
|
|
return array_filter($instances, function($instance) use ($clientLocation) {
|
||
|
|
$instanceLocation = $instance['location'] ?? 'unknown';
|
||
|
|
return $instanceLocation === $clientLocation || $instanceLocation === 'global';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Record traffic distribution.
|
||
|
|
*/
|
||
|
|
protected function recordTrafficDistribution(string $serviceId, array $requestAnalysis, string $strategy): void
|
||
|
|
{
|
||
|
|
// Update service stats
|
||
|
|
if (!isset($this->trafficStats[$serviceId])) {
|
||
|
|
$this->trafficStats[$serviceId] = [
|
||
|
|
'total_requests' => 0,
|
||
|
|
'requests_by_strategy' => [],
|
||
|
|
'response_times' => [],
|
||
|
|
'errors' => 0,
|
||
|
|
'last_request' => null
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->trafficStats[$serviceId]['total_requests']++;
|
||
|
|
$this->trafficStats[$serviceId]['requests_by_strategy'][$strategy] =
|
||
|
|
($this->trafficStats[$serviceId]['requests_by_strategy'][$strategy] ?? 0) + 1;
|
||
|
|
$this->trafficStats[$serviceId]['last_request'] = time();
|
||
|
|
|
||
|
|
// Add to distribution history
|
||
|
|
$this->distributionHistory[] = [
|
||
|
|
'timestamp' => time(),
|
||
|
|
'service_id' => $serviceId,
|
||
|
|
'strategy' => $strategy,
|
||
|
|
'request_analysis' => $requestAnalysis
|
||
|
|
];
|
||
|
|
|
||
|
|
// Limit history size
|
||
|
|
if (count($this->distributionHistory) > 1000) {
|
||
|
|
$this->distributionHistory = array_slice($this->distributionHistory, -1000);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Calculate average response time for service.
|
||
|
|
*/
|
||
|
|
protected function calculateAverageResponseTime(string $serviceId): float
|
||
|
|
{
|
||
|
|
$stats = $this->trafficStats[$serviceId] ?? [];
|
||
|
|
$responseTimes = $stats['response_times'] ?? [];
|
||
|
|
|
||
|
|
if (empty($responseTimes)) {
|
||
|
|
return 0.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return array_sum($responseTimes) / count($responseTimes);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Calculate error rate for service.
|
||
|
|
*/
|
||
|
|
protected function calculateErrorRate(string $serviceId): float
|
||
|
|
{
|
||
|
|
$stats = $this->trafficStats[$serviceId] ?? [];
|
||
|
|
$totalRequests = $stats['total_requests'] ?? 0;
|
||
|
|
$errors = $stats['errors'] ?? 0;
|
||
|
|
|
||
|
|
if ($totalRequests === 0) {
|
||
|
|
return 0.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return ($errors / $totalRequests) * 100;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Suggest weight adjustments.
|
||
|
|
*/
|
||
|
|
protected function suggestWeightAdjustments(): array
|
||
|
|
{
|
||
|
|
$suggestions = [];
|
||
|
|
$stats = $this->getStatistics();
|
||
|
|
|
||
|
|
foreach ($stats['service_statistics'] as $serviceId => $serviceStats) {
|
||
|
|
if ($serviceStats['error_rate'] > 2) {
|
||
|
|
$suggestions[] = [
|
||
|
|
'type' => 'weight_adjustment',
|
||
|
|
'service_id' => $serviceId,
|
||
|
|
'current_weight' => $this->balancer->getWeight($serviceId),
|
||
|
|
'suggested_weight' => max(1, (int) ($this->balancer->getWeight($serviceId) * 0.5)),
|
||
|
|
'reason' => 'High error rate detected'
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $suggestions;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Validate rule configuration.
|
||
|
|
*/
|
||
|
|
protected function validateRule(array $rule): void
|
||
|
|
{
|
||
|
|
if (empty($rule['conditions'])) {
|
||
|
|
throw new \InvalidArgumentException("Rule must have at least one condition");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (empty($rule['actions'])) {
|
||
|
|
throw new \InvalidArgumentException("Rule must have at least one action");
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ($rule['conditions'] as $condition) {
|
||
|
|
if (!isset($condition['field']) || !isset($condition['operator']) || !isset($condition['value'])) {
|
||
|
|
throw new \InvalidArgumentException("Condition must have field, operator, and value");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Initialize traffic distribution strategy.
|
||
|
|
*/
|
||
|
|
protected function initialize(): void
|
||
|
|
{
|
||
|
|
// Load configuration from storage
|
||
|
|
$this->loadConfiguration();
|
||
|
|
|
||
|
|
// Start monitoring
|
||
|
|
$this->monitor->start();
|
||
|
|
|
||
|
|
$this->logInfo("Traffic distribution strategy initialized");
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Load configuration from storage.
|
||
|
|
*/
|
||
|
|
protected function loadConfiguration(): void
|
||
|
|
{
|
||
|
|
// This would load from persistent storage in production
|
||
|
|
$this->logInfo("Configuration loaded");
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Log info message.
|
||
|
|
*/
|
||
|
|
protected function logInfo(string $message): void
|
||
|
|
{
|
||
|
|
if ($this->config['logging_enabled']) {
|
||
|
|
error_log("[TrafficDistributionStrategy] {$message}");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get default configuration.
|
||
|
|
*/
|
||
|
|
protected function getDefaultConfig(): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'default_strategy' => 'weighted',
|
||
|
|
'enable_monitoring' => true,
|
||
|
|
'monitoring_interval' => 60,
|
||
|
|
'history_retention' => 1000,
|
||
|
|
'logging_enabled' => true,
|
||
|
|
'optimization_enabled' => true,
|
||
|
|
'optimization_interval' => 300,
|
||
|
|
'version' => '1.0'
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get configuration.
|
||
|
|
*/
|
||
|
|
public function getConfig(): array
|
||
|
|
{
|
||
|
|
return $this->config;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set configuration.
|
||
|
|
*/
|
||
|
|
public function setConfig(array $config): void
|
||
|
|
{
|
||
|
|
$this->config = array_merge($this->config, $config);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create traffic distribution strategy instance.
|
||
|
|
*/
|
||
|
|
public static function create(array $config = []): self
|
||
|
|
{
|
||
|
|
return new self($config);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create for high traffic.
|
||
|
|
*/
|
||
|
|
public static function forHighTraffic(): self
|
||
|
|
{
|
||
|
|
return new self([
|
||
|
|
'default_strategy' => 'capacity',
|
||
|
|
'enable_monitoring' => true,
|
||
|
|
'monitoring_interval' => 30,
|
||
|
|
'optimization_enabled' => true,
|
||
|
|
'optimization_interval' => 120
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create for global distribution.
|
||
|
|
*/
|
||
|
|
public static function forGlobalDistribution(): self
|
||
|
|
{
|
||
|
|
return new self([
|
||
|
|
'default_strategy' => 'geographic',
|
||
|
|
'enable_monitoring' => true,
|
||
|
|
'logging_enabled' => true
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|