| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- # -*- coding: utf-8 -*-
- """
- Load-Balancing-Strategie Basisklassen.
- Definiert das Interface für alle Load-Balancing-Strategien.
- """
- from abc import ABC, abstractmethod
- from dataclasses import dataclass
- from typing import Any, TYPE_CHECKING
- if TYPE_CHECKING:
- from trixy_core.satellite.satellite import Satellite
- @dataclass
- class SelectionResult:
- """
- Ergebnis einer Satellite-Auswahl.
- Attributes:
- satellite: Der ausgewählte Satellite
- reason: Grund für die Auswahl
- score: Optionaler Score (für Weighted-Strategien)
- metadata: Zusätzliche Informationen
- """
- satellite: "Satellite"
- reason: str = ""
- score: float = 0.0
- metadata: dict[str, Any] | None = None
- class LoadBalancingStrategy(ABC):
- """
- Abstrakte Basisklasse für Load-Balancing-Strategien.
- Definiert das Interface für alle Strategien zur
- Auswahl eines Satellites aus einer Gruppe.
- Example:
- class MyStrategy(LoadBalancingStrategy):
- def select(self, satellites):
- # Eigene Logik
- return SelectionResult(satellites[0])
- """
- @property
- @abstractmethod
- def name(self) -> str:
- """Name der Strategie."""
- ...
- @abstractmethod
- def select(
- self,
- satellites: list["Satellite"],
- context: dict[str, Any] | None = None,
- ) -> SelectionResult | None:
- """
- Wählt einen Satellite aus der Liste aus.
- Args:
- satellites: Liste verfügbarer Satellites
- context: Optionaler Kontext für die Auswahl
- Returns:
- SelectionResult oder None wenn keine Auswahl möglich
- """
- ...
- def on_request_start(self, satellite: "Satellite") -> None:
- """
- Wird aufgerufen, wenn eine Anfrage an einen Satellite beginnt.
- Kann von Strategien überschrieben werden, die den Zustand
- verfolgen müssen (z.B. Least-Connections).
- Args:
- satellite: Der verwendete Satellite
- """
- pass
- def on_request_end(self, satellite: "Satellite", success: bool = True) -> None:
- """
- Wird aufgerufen, wenn eine Anfrage an einen Satellite endet.
- Args:
- satellite: Der verwendete Satellite
- success: Ob die Anfrage erfolgreich war
- """
- pass
- def reset(self) -> None:
- """Setzt den internen Zustand zurück."""
- pass
- class LoadBalancer:
- """
- Load-Balancer mit konfigurierbarer Strategie.
- Wählt Satellites basierend auf der konfigurierten Strategie aus.
- Unterstützt Fallback-Strategie und Filter.
- Example:
- balancer = LoadBalancer(RoundRobinStrategy())
- # Satellite auswählen
- result = balancer.select(satellites)
- if result:
- satellite = result.satellite
- await satellite.speak("Hallo")
- balancer.end_request(satellite)
- # Mit Filter
- result = balancer.select(
- satellites,
- filter_func=lambda s: s.room_id == "wohnzimmer"
- )
- """
- def __init__(
- self,
- strategy: LoadBalancingStrategy,
- fallback_strategy: LoadBalancingStrategy | None = None,
- ) -> None:
- """
- Initialisiert den Load-Balancer.
- Args:
- strategy: Primäre Strategie
- fallback_strategy: Optionale Fallback-Strategie
- """
- self._strategy = strategy
- self._fallback = fallback_strategy
- self._total_requests: int = 0
- self._failed_requests: int = 0
- @property
- def strategy(self) -> LoadBalancingStrategy:
- """Die aktive Strategie."""
- return self._strategy
- @strategy.setter
- def strategy(self, value: LoadBalancingStrategy) -> None:
- self._strategy = value
- @property
- def fallback_strategy(self) -> LoadBalancingStrategy | None:
- """Die Fallback-Strategie."""
- return self._fallback
- def select(
- self,
- satellites: list["Satellite"],
- filter_func: Any | None = None,
- context: dict[str, Any] | None = None,
- ) -> SelectionResult | None:
- """
- Wählt einen Satellite aus.
- Args:
- satellites: Liste verfügbarer Satellites
- filter_func: Optionale Filter-Funktion
- context: Optionaler Kontext
- Returns:
- SelectionResult oder None
- """
- # Nur verbundene Satellites
- available = [s for s in satellites if s.is_connected]
- # Optionalen Filter anwenden
- if filter_func:
- available = [s for s in available if filter_func(s)]
- if not available:
- return None
- self._total_requests += 1
- # Primäre Strategie versuchen
- result = self._strategy.select(available, context)
- # Fallback wenn nötig
- if result is None and self._fallback:
- result = self._fallback.select(available, context)
- if result:
- self._strategy.on_request_start(result.satellite)
- return result
- def start_request(self, satellite: "Satellite") -> None:
- """Markiert den Start einer Anfrage."""
- self._strategy.on_request_start(satellite)
- def end_request(self, satellite: "Satellite", success: bool = True) -> None:
- """Markiert das Ende einer Anfrage."""
- if not success:
- self._failed_requests += 1
- self._strategy.on_request_end(satellite, success)
- def reset(self) -> None:
- """Setzt den Zustand zurück."""
- self._strategy.reset()
- if self._fallback:
- self._fallback.reset()
- self._total_requests = 0
- self._failed_requests = 0
- def get_statistics(self) -> dict[str, Any]:
- """Gibt Statistiken zurück."""
- return {
- "strategy": self._strategy.name,
- "fallback_strategy": self._fallback.name if self._fallback else None,
- "total_requests": self._total_requests,
- "failed_requests": self._failed_requests,
- "success_rate": (
- (self._total_requests - self._failed_requests) / self._total_requests
- if self._total_requests > 0 else 1.0
- ),
- }
|