feat(server): add proactive channel probes and load-aware stable routing / 服务端新增主动探活与负载感知稳定路由 (#330)

* wip: preserve local changes before upstream merge

* Improve proxy routing stability and add stable-first strategy

* Sync explicit-group routing strategy to eligible source routes

* Fix proxy retry and runtime health review issues

* fix: make proxy channel retry attempts configurable

* fix: harden proxy retry recovery and bookkeeping

* fix: address pr 257 review comments

* feat(proxy): add sticky session channel coordination

* feat(server): add proactive channel probes and load-aware stable routing

* test(server): stabilize session load routing selection assertion

* fix(proxy): harden codex forwarding stability

* fix(router): rotate stable-first channels by healthy order

* fix(server): address probe review feedback

* fix(server): address probe review follow-ups

* fix(ci): address CodeQL and core test failures

* feat(server): refine stable-first routing recovery and observation

* Finish PR 330 settings and review fixes

* Fix chat site endpoint CI mock

* Address latest PR 330 review comments

---------

Co-authored-by: Cita <juricek.chen@gmail.com>
This commit is contained in:
Bainily
2026-04-02 09:49:04 +08:00
committed by GitHub
parent 1133846998
commit 596d1b93cb
75 changed files with 6365 additions and 507 deletions
+20 -4
View File
@@ -34,6 +34,8 @@ import type { RouteCandidateView, RouteTokenOption } from '../helpers/routeModel
import { SortableChannelRow } from './SortableChannelRow.js';
import {
getRouteRoutingStrategyLabel,
getRouteRoutingStrategyDescription,
getRouteRoutingStrategyHint,
normalizeRouteRoutingStrategyValue,
} from './routingStrategy.js';
import {
@@ -199,6 +201,8 @@ function RouteCardInner({
const channelManagementDisabled = explicitGroupRoute;
const title = resolveRouteTitle(route);
const routingStrategy = normalizeRouteRoutingStrategyValue(route.routingStrategy);
const routingStrategyDescription = getRouteRoutingStrategyDescription(routingStrategy);
const routingStrategyHint = getRouteRoutingStrategyHint(routingStrategy);
const hasCachedDecisionSnapshot = !!route.decisionSnapshot;
const cachedDecisionTooltip = route.decisionRefreshedAt
? `${tr('最近刷新')}: ${formatDateTimeMinuteLocal(route.decisionRefreshedAt)}`
@@ -207,17 +211,17 @@ function RouteCardInner({
{
value: 'weighted',
label: tr('权重随机'),
description: tr('按优先级、权重和成本信号综合选择'),
description: getRouteRoutingStrategyDescription('weighted'),
},
{
value: 'round_robin',
label: tr('轮询'),
description: tr('按全局顺序轮流调用,忽略优先级,连续失败 3 次后进入分级冷却'),
description: getRouteRoutingStrategyDescription('round_robin'),
},
{
value: 'stable_first',
label: tr('稳定优先'),
description: tr('按优先级优先选择当前最稳、最快、成功率更高的通道,不做随机分流'),
description: getRouteRoutingStrategyDescription('stable_first'),
},
] as const;
@@ -359,7 +363,11 @@ function RouteCardInner({
{tr('0 通道')}
</span>
) : (
<span className="badge badge-muted" style={{ fontSize: 10, flexShrink: 0 }}>
<span
className="badge badge-muted"
style={{ fontSize: 10, flexShrink: 0 }}
data-tooltip={`${getRouteRoutingStrategyLabel(routingStrategy)}:${routingStrategyDescription}`}
>
{getRouteRoutingStrategyLabel(routingStrategy)}
</span>
)}
@@ -545,6 +553,14 @@ function RouteCardInner({
placeholder={tr('选择路由策略')}
emptyLabel={tr('暂无可选策略')}
/>
<div style={{ marginTop: 8, display: 'flex', flexDirection: 'column', gap: 4 }}>
<div style={{ fontSize: 12, lineHeight: 1.55, color: 'var(--color-text-secondary)' }}>
{routingStrategyDescription}
</div>
<div style={{ fontSize: 11, lineHeight: 1.5, color: 'var(--color-text-muted)' }}>
{routingStrategyHint}
</div>
</div>
</div>
</div>
)}
@@ -12,3 +12,25 @@ export function getRouteRoutingStrategyLabel(value?: RouteRoutingStrategy | null
if (strategy === 'stable_first') return tr('稳定优先');
return tr('权重随机');
}
export function getRouteRoutingStrategyDescription(value?: RouteRoutingStrategy | null): string {
const strategy = normalizeRouteRoutingStrategyValue(value);
if (strategy === 'round_robin') {
return tr('忽略 P 值,按全局顺序依次调用;连续失败 3 次后进入分级冷却');
}
if (strategy === 'stable_first') {
return tr('先避开最近失败或不健康站点,再在稳定池里按顺序轮询;P 值表示轮询顺位');
}
return tr('P 值是硬优先级,只会在当前最高可用优先级内结合权重、成本和健康度随机选择');
}
export function getRouteRoutingStrategyHint(value?: RouteRoutingStrategy | null): string {
const strategy = normalizeRouteRoutingStrategyValue(value);
if (strategy === 'round_robin') {
return tr('当前策略不看 P 值;如果之后切回其他策略,拖拽保存的顺序仍会保留。');
}
if (strategy === 'stable_first') {
return tr('当前策略下,稳定站点会按 P 顺序轮换;不稳定站点会被自动降权或临时避让。');
}
return tr('只要更高优先级还有可用通道,后面的通道本次就不会参与选择。');
}