feat: weighted random routing + mode switching + EWMA smoothing + stale eviction
- Weighted random routing (math/rand/v2 per-goroutine ChaCha8) - PUT /api/route/mode for runtime mode switch (best / weighted_random) - EWMA score smoothing (configurable smoothing_factor, default 0.3) - Periodic stale node eviction (5 min threshold, 60s interval) - Non-blocking WS broadcast (per-client buffered channel + writePump) - Background rate limiter cleanup goroutine - In-memory trunk gateway cache (zero disk I/O in hot path) - Configurable load_avg multiplier (default 50.0, backward compat) - UI mode indicator + admin Switch button in dashboard - 42 tests: weighted random, mode switching, EWMA, eviction, API integration
This commit is contained in:
@@ -30,6 +30,8 @@ type ScoringConfig struct {
|
||||
IdleCPUMin float64 `json:"idle_cpu_min"`
|
||||
CallFailureRateLethal float64 `json:"call_failure_rate_lethal"`
|
||||
Weights ScoringWeights `json:"weights"`
|
||||
SmoothingFactor float64 `json:"smoothing_factor"` // 0 = без сглаживания, 0.3 = рекомендуемый EWMA
|
||||
LoadAvgMultiplier float64 `json:"load_avg_multiplier"` // множитель load_avg → score (стандартный 50)
|
||||
}
|
||||
|
||||
// TLSConfig — настройки HTTPS.
|
||||
@@ -97,6 +99,7 @@ type Config struct {
|
||||
RateLimit RateLimitConfig `json:"rate_limit"`
|
||||
Log LogConfig `json:"log"`
|
||||
ESL ESLConfig `json:"esl"`
|
||||
BalanceMode string `json:"balance_mode"` // "best" | "weighted_random"
|
||||
}
|
||||
|
||||
// Validate проверяет корректность всех полей конфига.
|
||||
@@ -125,6 +128,12 @@ func (c *Config) Validate() error {
|
||||
if c.Scoring.CallFailureRateLethal < 0 || c.Scoring.CallFailureRateLethal > 100 {
|
||||
errs = append(errs, "scoring.call_failure_rate_lethal должен быть 0..100")
|
||||
}
|
||||
if c.Scoring.SmoothingFactor < 0 || c.Scoring.SmoothingFactor > 1 {
|
||||
errs = append(errs, "scoring.smoothing_factor должен быть 0..1")
|
||||
}
|
||||
if c.Scoring.LoadAvgMultiplier < 0 {
|
||||
errs = append(errs, "scoring.load_avg_multiplier должен быть >= 0")
|
||||
}
|
||||
|
||||
w := &c.Scoring.Weights
|
||||
sum := w.CallScore + w.LoadScore + w.IdleScore + w.FailScore
|
||||
@@ -158,6 +167,13 @@ func (c *Config) Validate() error {
|
||||
errs = append(errs, "log.format должен быть text/json")
|
||||
}
|
||||
|
||||
// Balance mode validation
|
||||
switch c.BalanceMode {
|
||||
case "best", "weighted_random", "":
|
||||
default:
|
||||
errs = append(errs, "balance_mode должен быть best или weighted_random")
|
||||
}
|
||||
|
||||
// ESL validation
|
||||
if c.ESL.Host != "" {
|
||||
if c.ESL.Port <= 0 || c.ESL.Port > 65535 {
|
||||
@@ -374,6 +390,8 @@ func (m *Manager) createDefaultConfig() error {
|
||||
IdleScore: 0.20,
|
||||
FailScore: 0.10,
|
||||
},
|
||||
SmoothingFactor: 0.3,
|
||||
LoadAvgMultiplier: 50.0,
|
||||
},
|
||||
TLS: TLSConfig{
|
||||
Enabled: false,
|
||||
@@ -397,6 +415,7 @@ func (m *Manager) createDefaultConfig() error {
|
||||
SofiaProfile: "external",
|
||||
GatewayPrefix: "pulse",
|
||||
},
|
||||
BalanceMode: "weighted_random",
|
||||
}
|
||||
|
||||
return m.SaveConfig(&cfg)
|
||||
|
||||
Reference in New Issue
Block a user