diff --git a/web/src/types.ts b/web/src/types.ts index 17618fb..923ec38 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -259,9 +259,44 @@ export interface RingSnapshot { nodeKey?: string; } -// BalanceReport is the load balancer's read-only view (GET /cluster/balance). -// It exists because the balancer is otherwise silent in exactly the healthy -// case, which is indistinguishable from "balancing is switched off". +// BalanceReport is one node's own view of rebalancing (GET /cluster/balance). +// +// It is per-node by design: every member runs the same rule against its own +// forwards, and the answer that matters is the one on the node doing (or +// refusing to do) the releasing. So this reports "my share" first and the peers +// second, and there is no cluster-wide "would migrate" plan — the ring's normal +// lowest-load claim decides where a released forward lands. +export interface BalanceReleasePlan { + local: string; + remote: string; + remotePort: number; + owner: string; +} + +export interface BalanceReport { + mode: "off" | "dry-run" | "on"; + enabled: boolean; // mode !== off + dryRun: boolean; // mode !== on + isMember: boolean; + self: string; + + selfForwards: number; + share: number; // the even split: ceil(total / alive) + totalForwards: number; + aliveNodes: number; + selfOverShare: boolean; + + // wouldRelease is present whenever this node is over its share — including + // while it is deliberately holding back, which is what blockedBy explains. + wouldRelease?: BalanceReleasePlan; + releasing?: BalanceReleasePlan; + blockedBy?: string; // single-node | below-min | balanced | nothing-active | cooldown | in-flight + + cooldownSec: number; + lastReleaseAt?: number; + nodes: BalanceNodeReport[]; +} + export interface BalanceNodeReport { id: string; alive: boolean; @@ -272,27 +307,6 @@ export interface BalanceNodeReport { score: number; } -// BalanceMigrationPlan describes one forward moving between two nodes. -export interface BalanceMigrationPlan { - local: string; - remote: string; - remotePort: number; - from: string; - to: string; -} - -export interface BalanceReport { - enabled: boolean; // false = dry-run (observe only) - dryRun: boolean; - ratio: number; // overload = score above ratio x mean - sustainSec: number; // seconds an imbalance must persist before acting - isLeader: boolean; // only the leader acts; followers report empty plans - nodes: BalanceNodeReport[]; - overloadedSince?: number; // unix sec the current imbalance started - wouldMigrate?: BalanceMigrationPlan; // plan once the window is satisfied - inFlight?: BalanceMigrationPlan; // migration currently between phases -} - // ---- Auth / accounts / API keys (M7) ---- export type AccessLevel = 'read' | 'write' | 'admin' | 'superadmin'; diff --git a/web/src/views/ClusterView.vue b/web/src/views/ClusterView.vue index 5ba3955..2846843 100644 --- a/web/src/views/ClusterView.vue +++ b/web/src/views/ClusterView.vue @@ -128,77 +128,106 @@
W4F_BALANCE_MODE=on 才会真正迁移
+ 设 W4F_BALANCE_MODE=on 才会真正释放
+ 每个节点各自判断自己的转发,超了就自己撤销一条并丢回环上, + 由环节点按「最闲者优先」重新认领。没有中心调度。 +
+ +