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:
Maksim Totmin
2026-06-25 20:42:46 +07:00
parent 3a082923cf
commit b0697da8e4
15 changed files with 1026 additions and 65 deletions
+10 -1
View File
@@ -63,6 +63,9 @@ func main() {
// 3. Engine
eng := engine.NewEngine(cfg)
// 3.1 Запускаем эвикцию stale-нод (очистка нод без метрик > 5 минут, проверка каждые 60 сек)
eng.StartEvictionLoop(60*time.Second, 5*time.Minute, nil)
// 4. ASCII логгер метрик
metricsLogger, err := filelog.NewLogger(dataDir)
if err != nil {
@@ -76,6 +79,8 @@ func main() {
}
defer sub.Close()
var apiHandler *api.API
// Авто-создание balance-транков при первой метрике от новой ноды
sub.SetOnNewNode(func(nodeID, sipGateway string) {
trunks, err := cfgMgr.ReadTrunks()
@@ -109,11 +114,13 @@ func main() {
log.Printf("[main] ошибка сохранения авто-созданного транка: %v", err)
return
}
apiHandler.RebuildTrunkCache()
log.Printf("[nats] авто-создан balance-транк: %s → %s (узел: %s)", trunkID, sipGateway, nodeID)
})
// 6. HTTP API
apiHandler := api.NewAPI(eng, cfgMgr, cfg.GetJWTSecret(), cfg.MonitoringAPIKey, sub.IsConnected, cfg)
apiHandler = api.NewAPI(eng, cfgMgr, cfg.GetJWTSecret(), cfg.MonitoringAPIKey, sub.IsConnected, cfg)
apiHandler.RebuildTrunkCache() // первичное построение in-memory кэша gateway
handler := apiHandler.Handler()
// 6.1 ESL-клиент (опционально — если сконфигурирован)
@@ -236,6 +243,8 @@ func main() {
sig := <-quit
log.Printf("[main] получен сигнал %s, завершение работы...", sig)
eng.StopEvictionLoop()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()