feat: Implement smart group#2711
Open
zhboner wants to merge 2 commits intoMetaCubeX:Alphafrom
Open
Conversation
…ure/smart-proxy-group
5a5e312 to
8d53952
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#1226
0. 实现原理
Smart Proxy Group 的核心思想是用多维评分替代单一延迟指标,通过被动观察 + 主动测试 + 历史记忆,选出综合表现最优的代理节点。
评分模型
每个候选节点的综合评分由三个维度加权计算:
分数越低越好。不存活节点直接给最高分 10.0。
速度指标设计
速度不是主动测量的,而是被动观察已有连接的吞吐量推导出来的。
SpeedObserver 每秒采样所有活跃 TCP Tracker,计算 delta 速度,按 leaf proxy 名称聚合后推送。采用双时间戳设计分离 currentSpeed 和 peakSpeed:
currentSpeed/currentAt— 最新采样值,30s 无更新自动归零peakSpeed/peakAt— 历史峰值,只有新速度 ≥ 当前衰减峰值时才更新评分时使用
effectiveSpeed = max(衰减峰值, 新鲜当前速度)。衰减公式为peakSpeed × e^(-λ × elapsed),λ=0.001,半衰期约 11.5 分钟。优势:下载完成不惩罚(峰值还在),长期空闲自动遗忘,小流量不污染峰值,曾经表现好的节点有"记忆优势"。
基线与退化检测
Smart 为每个 proxy 维护独立的 EMA 延迟基线:
new = old × 0.8 + current × 0.2,单次最多上涨 25%退化标记:延迟 > 基线×1.5 或连接失败时标记。
滞后清除:延迟 ≤ 基线×1.2 且连续 2 次成功才清除(防止抖动)。
切换防抖(Tolerance)
评分选出最优节点后,检查评分容差:
默认 tolerance = 0.1(评分差距 10% 才切换)。维度一致(都是评分),避免"评分选路但延迟防抖"的不一致。
嵌套组支持
Smart 支持嵌套其他代理组(URLTest/Fallback/Selector/Smart)。通过
resolve()方法递归 unwrap 到 leaf proxy,从同一 leaf 读取所有物理指标。关键设计:延迟/丢包保留在 wrapper 层(URL 作用域指标),速度委托给 leaf(物理层指标)。
Adaptive Retry
连接失败时区分自修复组和非自修复组:
1. 变更总览
constant/adapters.goadapter/adapter.gotunnel/statistic/speedobserver.gotunnel/statistic/manager.goadapter/outboundgroup/smart.goadapter/outboundgroup/parser.goadapter/outboundgroup/urltest.goadapter/outboundgroup/fallback.goadapter/outboundgroup/selector.goadapter/outboundgroup/groupbase.go2. constant/adapters.go
C.Proxy 接口扩展
新增 6 个方法:
SpeedHistory()— 返回速度历史记录(bounded queue,120 条)PushSpeed(speed uint64)— SpeedObserver 推送速度时调用LastSpeed() uint64— 返回新鲜的原始采样速度,超过 30s 无更新返回 0EffectiveSpeed() uint64— 评分用:max(衰减峰值, 新鲜当前速度)PacketLossRate(url string) float64— 返回丢包率PushTestResult(url string, success bool)— 记录 health check 结果同时新增
SmartAdapterType 和SpeedHistory结构体(Time + Speed)。3. adapter/adapter.go
新增常量
defaultSpeedHistoriesNumdefaultTestResultsNumspeedStaleThresholdspeedDecayLambdaProxy struct 新增字段
速度指标(双时间戳设计):
peakSpeed/peakAt— 历史最高速度及其更新时间currentSpeed/currentAt— 最新采样速度及其更新时间speedHistoryMu+speedHistory— 速度历史记录(mutex 保护,bounded queue)测试结果:
testResultsMu+testResults— health check 结果记录(mutex 保护,bounded queue)实现的方法
PushSpeed — SpeedObserver 每秒推送速度时调用:
LastSpeed — 返回新鲜的原始采样速度:
EffectiveSpeed — 评分用速度指标:
effectiveSpeed = max(衰减峰值, 新鲜当前速度)SpeedHistory — 返回速度历史记录拷贝(线程安全)
PushTestResult — 记录 health check 结果(bounded queue,最多 20 条)
PacketLossRate — 简单计算全部 testResults 的失败率
4. tunnel/statistic/speedobserver.go(新建)
核心结构
trackerSample — per-tracker 采样状态:
mu— per-tracker 锁,避免 Leave/Tick 竞态leaf— Chain[0],即 leaf egress proxy 名称lastTotal— 上次采样的下载总量windowBytes— 当前窗口累积字节windowStart— 当前窗口起始时间closed— 是否已关闭SpeedObserver — 速度观察器:
trackers— 活跃 tracker 映射(xsync.Map)leaveTemp— Leave 产生的速度暂存(atomic.Uint64),由下一 tick 统一合并Join(tracker)
Leave(tracker)
Tick(now, resolve)
注意:当前 resolve 函数返回 nil(待后续集成 proxy 查找逻辑),SpeedObserver 已就绪但暂未实际推送速度。
trackerSample.flush()
5. tunnel/statistic/manager.go
变更
speedObserver字段init()中初始化 SpeedObserverJoin()/Leave()调用 speedObserver 对应方法handle()中每秒调用Tick()6. adapter/outboundgroup/smart.go(新建,675 行)
常量定义
速度归一化:
speedRefFloorspeedRefCeilingminSpeedSamplesEMA 基线更新:
baselineAlphabaselineMinSamplesspikeIgnoreFactorbaselineMaxStepUpreseedHighStreak退化检测与恢复:
degradeFactorrecoverFactorrecoverSuccessesdegradePenalty评分权重(默认值,可在 YAML 中覆盖):
defaultWeightDelaydefaultWeightLossdefaultWeightSpeed其他默认参数:
defaultTolerancedefaultDegradeFactordefaultDecayLambda数据结构
nodeMetrics — 评分时单个节点的指标快照(proxy, delay, loss, speed, alive)
NodeState — Smart 内部维护的 per-proxy 状态(mutex 保护):
Smart — Smart group 主体:
Smart.resolve(proxy) — 嵌套组解析
通过
proxy.Adapter()获取底层 adapter(因为候选节点可能是*adapter.Proxy包装),type switch 到具体 group 类型,调用其 fast/findAliveProxy/selectedProxy 拿到 leaf。leaf proxy 直接返回,因为速度/延迟/丢包等物理指标只在 leaf 层有意义。支持嵌套:URLTest → Fallback → Selector → Smart → leaf proxy。
Smart.fast(touch) — 核心选路
Smart.selectBest(metrics) — 评分选择
Smart.calculateScore(m, speedRef) — 评分计算
(1 - normSpeed)Smart.computeSpeedRef(metrics) — 速度参考值
NodeState 并发安全模型
每个 proxy 的 Smart 内部状态使用独立 mutex 保护。所有状态更新方法(updateBaseline, updateDegraded, markDegraded, isDegraded, PushTestResult, PacketLossRate)通过
state.mu.Lock()保护。updateBaseline(proxyName, success, delay) — Capped EMA 基线更新
3 阶段更新:
updateDegraded(proxyName, success, delay) — 退化检测与滞后清除
PacketLossRate(proxyName) — 丢包率计算
onDialFailed(proxy, err) — Adaptive Retry
NewSmart() — 构造函数
checkNoLoadBalance — 递归检查
递归遍历所有子节点(包括嵌套组内部),发现 LoadBalance 即拒绝创建。通过
proxy.Adapter()type switch 到具体 group 类型,递归检查其子节点。7. adapter/outboundgroup/parser.go
注册 "smart" 类型
在 ParseProxyGroup 的 switch 中添加 "smart" case,调用 parseSmartOption 解析配置后创建 Smart group。
parseSmartOption(config)
解析 4 个用户可配置参数:
weight-delayweight-lossweight-speedtolerance权重归一化:三个权重之和归一化为 1.0。全零时拒绝创建。
已移除的配置项(写死为常量):
degrade-factor(固定 1.5)decay-lambda(固定 0.001)8. adapter/outboundgroup/{urltest,fallback,selector}.go
EffectiveSpeed() 委托
三个单活跃子节点的 group 都添加了
EffectiveSpeed()方法,委托给当前选中的 leaf proxy:u.fast(false).EffectiveSpeed()f.findAliveProxy(false).EffectiveSpeed()s.selectedProxy(false).EffectiveSpeed()速度是物理层指标,只有 leaf proxy 才有真实速度数据,所以委托给 leaf。延迟/丢包保留在 wrapper 层(URL 作用域指标)。
9. 已知限制