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:
@@ -198,6 +198,7 @@ export interface BalancerInfo {
|
||||
route_total: number;
|
||||
route_fallbacks: number;
|
||||
uptime_sec: number;
|
||||
balance_mode: string;
|
||||
}
|
||||
|
||||
export function connectWebSocket(onMessage: (data: any) => void): WebSocket {
|
||||
@@ -223,3 +224,12 @@ export function connectWebSocket(onMessage: (data: any) => void): WebSocket {
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
// --- Route mode ---
|
||||
|
||||
export async function setBalanceMode(mode: 'best' | 'weighted_random') {
|
||||
return request<{ mode: string }>('/route/mode', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ mode }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,20 +3,36 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import {
|
||||
getNodes, connectWebSocket, isAuthenticated,
|
||||
getNodes, connectWebSocket, isAuthenticated, setBalanceMode,
|
||||
type NodeInfo, type BalancerInfo
|
||||
} from '$lib/api';
|
||||
import { Activity, Phone, Zap, AlertTriangle, Wifi, Server, Plug, Globe, ArrowLeftRight, Network } from 'lucide-svelte';
|
||||
import { Activity, Phone, Zap, AlertTriangle, Wifi, Server, Plug, Globe, ArrowLeftRight, Network, Shuffle } from 'lucide-svelte';
|
||||
|
||||
let nodes: NodeInfo[] = [];
|
||||
let balancer: BalancerInfo | null = null;
|
||||
let ws: WebSocket | null = null;
|
||||
let error: string | null = null;
|
||||
let setModeBusy = false;
|
||||
|
||||
if (browser && !isAuthenticated()) {
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
function getRole(): string | null {
|
||||
if (!browser) return null;
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return null;
|
||||
try {
|
||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||
return payload.role || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let role = browser ? getRole() : null;
|
||||
$: isAdmin = role === 'admin';
|
||||
|
||||
onMount(() => {
|
||||
loadNodes();
|
||||
ws = connectWebSocket((msg) => {
|
||||
@@ -40,6 +56,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleMode() {
|
||||
if (!balancer || setModeBusy) return;
|
||||
const newMode = balancer.balance_mode === 'weighted_random' ? 'best' : 'weighted_random';
|
||||
setModeBusy = true;
|
||||
try {
|
||||
await setBalanceMode(newMode);
|
||||
} catch (e: any) {
|
||||
error = e.message;
|
||||
} finally {
|
||||
setModeBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
function scoreColor(score: number): string {
|
||||
if (score >= 70) return 'text-green-600';
|
||||
if (score >= 40) return 'text-yellow-600';
|
||||
@@ -145,6 +174,21 @@
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
<span class="opacity-30">|</span>
|
||||
<span class="flex items-center gap-1 ml-auto">
|
||||
<Shuffle class="h-3 w-3" />
|
||||
<span class="font-medium">{balancer.balance_mode === 'weighted_random' ? 'Weighted' : 'Best'}</span>
|
||||
{#if isAdmin}
|
||||
<button
|
||||
on:click={toggleMode}
|
||||
disabled={setModeBusy}
|
||||
class="ml-1 px-2 py-0.5 text-[10px] border rounded hover:bg-secondary transition-colors disabled:opacity-50"
|
||||
title="Переключить режим балансировки"
|
||||
>
|
||||
{setModeBusy ? '...' : 'Switch'}
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user