| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- # -*- coding: utf-8 -*-
- """
- Round-Robin Load-Balancing-Strategie.
- Verteilt Anfragen gleichmäßig auf alle verfügbaren Satellites.
- """
- from typing import Any, TYPE_CHECKING
- from trixy_core.satellite.loadbalancing.strategy import (
- LoadBalancingStrategy,
- SelectionResult,
- )
- if TYPE_CHECKING:
- from trixy_core.satellite.satellite import Satellite
- class RoundRobinStrategy(LoadBalancingStrategy):
- """
- Round-Robin-Strategie.
- Wählt Satellites der Reihe nach aus und beginnt dann von vorne.
- Gewährleistet gleichmäßige Verteilung über alle Satellites.
- Example:
- strategy = RoundRobinStrategy()
- # Bei 3 Satellites: A, B, C
- # Auswahl-Reihenfolge: A, B, C, A, B, C, ...
- balancer = LoadBalancer(strategy)
- result = balancer.select(satellites)
- """
- def __init__(self) -> None:
- """Initialisiert die Round-Robin-Strategie."""
- self._current_index: int = 0
- self._satellite_order: list[str] = []
- @property
- def name(self) -> str:
- """Name der Strategie."""
- return "round_robin"
- def select(
- self,
- satellites: list["Satellite"],
- context: dict[str, Any] | None = None,
- ) -> SelectionResult | None:
- """
- Wählt den nächsten Satellite in der Rotation.
- Args:
- satellites: Liste verfügbarer Satellites
- context: Optionaler Kontext (wird ignoriert)
- Returns:
- SelectionResult oder None wenn keine Satellites verfügbar
- """
- if not satellites:
- return None
- # Sortiere Satellites für konsistente Reihenfolge
- sorted_satellites = sorted(satellites, key=lambda s: s.id)
- satellite_ids = [s.id for s in sorted_satellites]
- # Prüfe ob sich die Liste geändert hat
- if satellite_ids != self._satellite_order:
- self._satellite_order = satellite_ids
- # Index anpassen falls nötig
- if self._current_index >= len(sorted_satellites):
- self._current_index = 0
- # Satellite auswählen
- selected = sorted_satellites[self._current_index]
- # Index für nächste Auswahl vorbereiten
- self._current_index = (self._current_index + 1) % len(sorted_satellites)
- return SelectionResult(
- satellite=selected,
- reason=f"Round-Robin Index {self._current_index}",
- score=1.0 / len(sorted_satellites),
- )
- def reset(self) -> None:
- """Setzt den Index zurück."""
- self._current_index = 0
- self._satellite_order = []
- class WeightedRoundRobinStrategy(LoadBalancingStrategy):
- """
- Gewichtetes Round-Robin.
- Berücksichtigt Gewichte bei der Rotation. Satellites mit
- höherem Gewicht werden häufiger ausgewählt.
- Example:
- strategy = WeightedRoundRobinStrategy()
- strategy.set_weight("satellite_1", 3) # 3x so oft
- strategy.set_weight("satellite_2", 1) # Normal
- strategy.set_weight("satellite_3", 2) # 2x so oft
- # Reihenfolge: 1, 1, 1, 3, 3, 2, 1, 1, 1, ...
- """
- def __init__(self, default_weight: int = 1) -> None:
- """
- Initialisiert die gewichtete Round-Robin-Strategie.
- Args:
- default_weight: Standard-Gewicht für neue Satellites
- """
- self._default_weight = default_weight
- self._weights: dict[str, int] = {}
- self._current_index: int = 0
- self._expanded_list: list[str] = []
- @property
- def name(self) -> str:
- """Name der Strategie."""
- return "weighted_round_robin"
- def set_weight(self, satellite_id: str, weight: int) -> None:
- """
- Setzt das Gewicht für einen Satellite.
- Args:
- satellite_id: Satellite-ID
- weight: Gewicht (muss >= 1 sein)
- """
- self._weights[satellite_id] = max(1, weight)
- self._expanded_list = [] # Neuberechnung erzwingen
- def get_weight(self, satellite_id: str) -> int:
- """Gibt das Gewicht eines Satellites zurück."""
- return self._weights.get(satellite_id, self._default_weight)
- def _build_expanded_list(self, satellites: list["Satellite"]) -> list[str]:
- """Baut die expandierte Liste basierend auf Gewichten."""
- expanded = []
- for satellite in sorted(satellites, key=lambda s: s.id):
- weight = self.get_weight(satellite.id)
- expanded.extend([satellite.id] * weight)
- return expanded
- def select(
- self,
- satellites: list["Satellite"],
- context: dict[str, Any] | None = None,
- ) -> SelectionResult | None:
- """
- Wählt den nächsten Satellite basierend auf Gewichten.
- Args:
- satellites: Liste verfügbarer Satellites
- context: Optionaler Kontext
- Returns:
- SelectionResult oder None
- """
- if not satellites:
- return None
- # ID -> Satellite Mapping
- satellite_map = {s.id: s for s in satellites}
- # Expandierte Liste erstellen wenn nötig
- current_ids = sorted(s.id for s in satellites)
- if not self._expanded_list or set(self._expanded_list) != set(current_ids):
- self._expanded_list = self._build_expanded_list(satellites)
- if self._current_index >= len(self._expanded_list):
- self._current_index = 0
- if not self._expanded_list:
- return None
- # Auswahl
- selected_id = self._expanded_list[self._current_index]
- selected = satellite_map.get(selected_id)
- if selected is None:
- # Satellite nicht mehr verfügbar, Liste neu aufbauen
- self._expanded_list = self._build_expanded_list(satellites)
- self._current_index = 0
- if not self._expanded_list:
- return None
- selected_id = self._expanded_list[0]
- selected = satellite_map.get(selected_id)
- if selected is None:
- return None
- # Index vorrücken
- self._current_index = (self._current_index + 1) % len(self._expanded_list)
- return SelectionResult(
- satellite=selected,
- reason=f"Weighted Round-Robin (weight={self.get_weight(selected.id)})",
- score=self.get_weight(selected.id) / sum(self.get_weight(s.id) for s in satellites),
- )
- def reset(self) -> None:
- """Setzt den Zustand zurück."""
- self._current_index = 0
- self._expanded_list = []
|