2026-04-25 17:39:16 +08:00
|
|
|
|
package core
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"sync"
|
|
|
|
|
|
"sync/atomic"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/panjf2000/ants/v2"
|
|
|
|
|
|
"github.com/shadow1ng/fscan/common"
|
2026-05-23 15:18:40 +08:00
|
|
|
|
"github.com/shadow1ng/fscan/common/i18n"
|
2026-04-25 17:39:16 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
// HealthSignal 健康评估结果
|
|
|
|
|
|
type HealthSignal int
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
HealthUnknown HealthSignal = iota // 样本不足,无法判断
|
|
|
|
|
|
HealthGood // 一切正常,可以提速
|
|
|
|
|
|
HealthOK // 正常,维持现状
|
|
|
|
|
|
HealthStressed // 有压力信号,轻微降速
|
|
|
|
|
|
HealthCongested // 明确拥塞,大幅降速
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// AdaptivePool 自适应线程池(AIMD + 慢启动)
|
|
|
|
|
|
//
|
|
|
|
|
|
// 三阶段工作模式:
|
|
|
|
|
|
// 1. 慢启动:从 target/4 起步,每个检查周期翻倍,直到达到 target 或检测到拥塞
|
|
|
|
|
|
// 2. 稳态 AIMD:健康时加性增(+5% target),拥塞时乘性减(×0.5)
|
|
|
|
|
|
// 3. 恢复上限受 ceiling 约束,不会无限增长
|
|
|
|
|
|
//
|
|
|
|
|
|
// 健康评估基于两个信号:
|
|
|
|
|
|
// - 资源耗尽率(fd/端口不足)
|
|
|
|
|
|
// - RTT 趋势(fast EMA / slow EMA)
|
2026-04-25 17:39:16 +08:00
|
|
|
|
type AdaptivePool struct {
|
2026-06-12 09:46:02 +08:00
|
|
|
|
pool *ants.PoolWithFunc
|
|
|
|
|
|
metrics *ScanMetrics
|
2026-04-25 17:39:16 +08:00
|
|
|
|
|
2026-06-13 12:39:24 +08:00
|
|
|
|
// 网络环境(影响健康评估阈值)
|
|
|
|
|
|
networkEnv NetworkEnv
|
|
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
// 并发控制
|
|
|
|
|
|
target int32 // 探测推荐的目标值
|
|
|
|
|
|
ceiling int32 // 绝对上限(用户指定或探测推荐)
|
|
|
|
|
|
currentSize int32
|
2026-04-25 17:39:16 +08:00
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
// 慢启动
|
|
|
|
|
|
inSlowStart bool
|
|
|
|
|
|
ssThreshold int32 // 慢启动阈值(拥塞后降为当前值)
|
2026-04-25 17:39:16 +08:00
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
// 检查定时
|
|
|
|
|
|
checkInterval time.Duration
|
|
|
|
|
|
lastCheck atomic.Int64 // UnixNano
|
2026-04-25 17:39:16 +08:00
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
// 增量计算
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
|
prevSnapshot MetricsSnapshot
|
2026-04-25 17:39:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewAdaptivePool 创建自适应线程池
|
2026-06-12 09:46:02 +08:00
|
|
|
|
// target: 目标并发数(来自 NetworkProfile.RecommendConcurrency)
|
|
|
|
|
|
// ceiling: 最大并发上限
|
|
|
|
|
|
// metrics: 共享的扫描度量(scanSinglePort 写入,pool 读取)
|
2026-06-13 12:39:24 +08:00
|
|
|
|
func NewAdaptivePool(target, ceiling int, fn func(interface{}), metrics *ScanMetrics, env ...NetworkEnv) (*AdaptivePool, error) {
|
2026-06-12 09:46:02 +08:00
|
|
|
|
// 慢启动初始值:target 的 25%,但不低于 10
|
|
|
|
|
|
initial := target / 4
|
|
|
|
|
|
if initial < 10 {
|
|
|
|
|
|
initial = 10
|
|
|
|
|
|
}
|
|
|
|
|
|
if initial > target {
|
|
|
|
|
|
initial = target
|
2026-04-25 17:39:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
pool, err := ants.NewPoolWithFunc(initial, fn)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
2026-04-25 17:39:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-13 12:39:24 +08:00
|
|
|
|
netEnv := EnvWAN
|
|
|
|
|
|
if len(env) > 0 {
|
|
|
|
|
|
netEnv = env[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-25 17:39:16 +08:00
|
|
|
|
return &AdaptivePool{
|
2026-06-12 09:46:02 +08:00
|
|
|
|
pool: pool,
|
|
|
|
|
|
metrics: metrics,
|
2026-06-13 12:39:24 +08:00
|
|
|
|
networkEnv: netEnv,
|
2026-06-12 09:46:02 +08:00
|
|
|
|
target: int32(target),
|
|
|
|
|
|
ceiling: int32(ceiling),
|
|
|
|
|
|
currentSize: int32(initial),
|
|
|
|
|
|
inSlowStart: true,
|
|
|
|
|
|
ssThreshold: int32(target),
|
|
|
|
|
|
checkInterval: 500 * time.Millisecond,
|
2026-04-25 17:39:16 +08:00
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
// Invoke 提交任务
|
2026-04-25 17:39:16 +08:00
|
|
|
|
func (ap *AdaptivePool) Invoke(task interface{}) error {
|
|
|
|
|
|
ap.maybeAdjust()
|
|
|
|
|
|
return ap.pool.Invoke(task)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
// maybeAdjust 周期性检查并调整并发数
|
2026-04-25 17:39:16 +08:00
|
|
|
|
func (ap *AdaptivePool) maybeAdjust() {
|
2026-06-12 09:46:02 +08:00
|
|
|
|
last := ap.lastCheck.Load()
|
2026-05-18 06:11:10 +08:00
|
|
|
|
now := time.Now().UnixNano()
|
2026-06-12 09:46:02 +08:00
|
|
|
|
if now-last < int64(ap.checkInterval) {
|
2026-04-25 17:39:16 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-06-12 09:46:02 +08:00
|
|
|
|
if !ap.lastCheck.CompareAndSwap(last, now) {
|
|
|
|
|
|
return
|
2026-05-18 06:11:10 +08:00
|
|
|
|
}
|
2026-04-25 17:39:16 +08:00
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
ap.adjust()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (ap *AdaptivePool) adjust() {
|
|
|
|
|
|
health := ap.assessHealth()
|
|
|
|
|
|
if health == HealthUnknown {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-04-25 17:39:16 +08:00
|
|
|
|
|
2026-06-13 12:39:24 +08:00
|
|
|
|
// RTT 漂移微调:fast EMA 远高于 slow EMA 说明延迟持续恶化
|
|
|
|
|
|
// 压低 target 让 AIMD 的天花板跟着降,而不是只靠乘性减
|
|
|
|
|
|
ap.maybeReduceTarget()
|
|
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
current := int(atomic.LoadInt32(&ap.currentSize))
|
|
|
|
|
|
target := int(atomic.LoadInt32(&ap.target))
|
|
|
|
|
|
ceiling := int(atomic.LoadInt32(&ap.ceiling))
|
2026-04-25 17:39:16 +08:00
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
var newSize int
|
2026-04-25 17:39:16 +08:00
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
if ap.inSlowStart {
|
|
|
|
|
|
newSize = ap.adjustSlowStart(health, current, target)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
newSize = ap.adjustAIMD(health, current, target)
|
2026-04-25 17:39:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
// 下限:ceiling 的 5%,但不低于 10
|
|
|
|
|
|
minSize := ceiling / 20
|
|
|
|
|
|
if minSize < 10 {
|
|
|
|
|
|
minSize = 10
|
|
|
|
|
|
}
|
2026-04-25 17:39:16 +08:00
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
if newSize < minSize {
|
|
|
|
|
|
newSize = minSize
|
|
|
|
|
|
}
|
|
|
|
|
|
if newSize > ceiling {
|
|
|
|
|
|
newSize = ceiling
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if newSize != current {
|
2026-04-25 17:39:16 +08:00
|
|
|
|
ap.tune(newSize)
|
2026-06-12 09:46:02 +08:00
|
|
|
|
|
|
|
|
|
|
// 显著变化时记录日志
|
|
|
|
|
|
delta := newSize - current
|
|
|
|
|
|
if delta < 0 {
|
|
|
|
|
|
delta = -delta
|
|
|
|
|
|
}
|
|
|
|
|
|
if delta > current/5 {
|
|
|
|
|
|
if newSize < current {
|
|
|
|
|
|
common.LogInfo(i18n.Tr("adaptive_pool_decrease", current, newSize))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
common.LogDebug(i18n.Tr("adaptive_pool_increase", current, newSize))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (ap *AdaptivePool) adjustSlowStart(health HealthSignal, current, target int) int {
|
|
|
|
|
|
switch health {
|
|
|
|
|
|
case HealthCongested, HealthStressed:
|
|
|
|
|
|
// 退出慢启动,设置阈值
|
|
|
|
|
|
ap.ssThreshold = int32(current)
|
|
|
|
|
|
ap.inSlowStart = false
|
|
|
|
|
|
common.LogDebug(i18n.Tr("adaptive_pool_slowstart_exit", current))
|
|
|
|
|
|
return int(float64(current) * 0.5)
|
|
|
|
|
|
default:
|
|
|
|
|
|
// 翻倍
|
|
|
|
|
|
newSize := current * 2
|
|
|
|
|
|
if newSize >= target {
|
|
|
|
|
|
newSize = target
|
|
|
|
|
|
ap.inSlowStart = false
|
2026-04-25 17:39:16 +08:00
|
|
|
|
}
|
2026-06-12 09:46:02 +08:00
|
|
|
|
return newSize
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (ap *AdaptivePool) adjustAIMD(health HealthSignal, current, target int) int {
|
|
|
|
|
|
switch health {
|
|
|
|
|
|
case HealthCongested:
|
|
|
|
|
|
// 乘性减:×0.5
|
|
|
|
|
|
newSize := int(float64(current) * 0.5)
|
|
|
|
|
|
ap.ssThreshold = int32(newSize)
|
|
|
|
|
|
return newSize
|
|
|
|
|
|
case HealthStressed:
|
|
|
|
|
|
// 温和降低:×0.85
|
|
|
|
|
|
return int(float64(current) * 0.85)
|
|
|
|
|
|
case HealthGood:
|
|
|
|
|
|
// 加性增:+5% of target,至少 +1
|
|
|
|
|
|
inc := target / 20
|
|
|
|
|
|
if inc < 1 {
|
|
|
|
|
|
inc = 1
|
2026-04-25 17:39:16 +08:00
|
|
|
|
}
|
2026-06-12 09:46:02 +08:00
|
|
|
|
return current + inc
|
|
|
|
|
|
default:
|
|
|
|
|
|
return current
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// assessHealth 综合健康评估
|
|
|
|
|
|
func (ap *AdaptivePool) assessHealth() HealthSignal {
|
|
|
|
|
|
snap := ap.metrics.Snapshot()
|
|
|
|
|
|
|
|
|
|
|
|
ap.mu.Lock()
|
|
|
|
|
|
prev := ap.prevSnapshot
|
|
|
|
|
|
ap.prevSnapshot = snap
|
|
|
|
|
|
ap.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
// 计算本周期增量
|
|
|
|
|
|
deltaTotal := snap.Total() - prev.Total()
|
|
|
|
|
|
deltaExhausted := snap.Exhausted - prev.Exhausted
|
|
|
|
|
|
|
|
|
|
|
|
// 样本不足
|
|
|
|
|
|
if deltaTotal < 30 {
|
|
|
|
|
|
return HealthUnknown
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
exhaustRate := float64(deltaExhausted) / float64(deltaTotal)
|
|
|
|
|
|
rttRatio := ap.metrics.RTTRatio()
|
|
|
|
|
|
|
2026-06-13 12:39:24 +08:00
|
|
|
|
// 阈值根据网络环境调整:内网收紧,公网放宽
|
|
|
|
|
|
var congestExhaust, stressExhaust, congestRTT, stressRTT, goodRTT float64
|
|
|
|
|
|
switch ap.networkEnv {
|
|
|
|
|
|
case EnvLAN:
|
|
|
|
|
|
congestExhaust, stressExhaust = 0.08, 0.03
|
|
|
|
|
|
congestRTT, stressRTT, goodRTT = 1.8, 1.4, 1.15
|
|
|
|
|
|
case EnvWAN:
|
|
|
|
|
|
congestExhaust, stressExhaust = 0.15, 0.05
|
|
|
|
|
|
congestRTT, stressRTT, goodRTT = 2.5, 1.8, 1.3
|
|
|
|
|
|
default: // Internet / Slow
|
|
|
|
|
|
congestExhaust, stressExhaust = 0.25, 0.10
|
|
|
|
|
|
congestRTT, stressRTT, goodRTT = 3.5, 2.5, 1.5
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-12 09:46:02 +08:00
|
|
|
|
switch {
|
2026-06-13 12:39:24 +08:00
|
|
|
|
case exhaustRate > congestExhaust:
|
2026-06-12 09:46:02 +08:00
|
|
|
|
return HealthCongested
|
2026-06-13 12:39:24 +08:00
|
|
|
|
case rttRatio > congestRTT:
|
2026-06-12 09:46:02 +08:00
|
|
|
|
return HealthCongested
|
2026-06-13 12:39:24 +08:00
|
|
|
|
case exhaustRate > stressExhaust:
|
2026-06-12 09:46:02 +08:00
|
|
|
|
return HealthStressed
|
2026-06-13 12:39:24 +08:00
|
|
|
|
case rttRatio > stressRTT:
|
2026-06-12 09:46:02 +08:00
|
|
|
|
return HealthStressed
|
2026-06-13 12:39:24 +08:00
|
|
|
|
case exhaustRate < 0.01 && rttRatio < goodRTT:
|
2026-06-12 09:46:02 +08:00
|
|
|
|
return HealthGood
|
|
|
|
|
|
default:
|
|
|
|
|
|
return HealthOK
|
2026-04-25 17:39:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-13 12:39:24 +08:00
|
|
|
|
// maybeReduceTarget 当 RTT 持续恶化时压低 target
|
|
|
|
|
|
// 不低于 ceiling 的 20%,避免过度收缩
|
|
|
|
|
|
func (ap *AdaptivePool) maybeReduceTarget() {
|
|
|
|
|
|
rttRatio := ap.metrics.RTTRatio()
|
|
|
|
|
|
if rttRatio <= 3.0 {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
target := atomic.LoadInt32(&ap.target)
|
|
|
|
|
|
ceiling := atomic.LoadInt32(&ap.ceiling)
|
|
|
|
|
|
minTarget := ceiling / 5
|
|
|
|
|
|
if minTarget < 10 {
|
|
|
|
|
|
minTarget = 10
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 压低 10%
|
|
|
|
|
|
newTarget := int32(float64(target) * 0.9)
|
|
|
|
|
|
if newTarget < minTarget {
|
|
|
|
|
|
newTarget = minTarget
|
|
|
|
|
|
}
|
|
|
|
|
|
if newTarget < target {
|
|
|
|
|
|
atomic.StoreInt32(&ap.target, newTarget)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-25 17:39:16 +08:00
|
|
|
|
func (ap *AdaptivePool) tune(newSize int) {
|
|
|
|
|
|
ap.pool.Tune(newSize)
|
|
|
|
|
|
atomic.StoreInt32(&ap.currentSize, int32(newSize))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Running 返回当前运行中的 goroutine 数量
|
2026-06-12 09:46:02 +08:00
|
|
|
|
func (ap *AdaptivePool) Running() int { return ap.pool.Running() }
|
2026-04-25 17:39:16 +08:00
|
|
|
|
|
|
|
|
|
|
// Cap 返回当前池容量
|
2026-06-12 09:46:02 +08:00
|
|
|
|
func (ap *AdaptivePool) Cap() int { return int(atomic.LoadInt32(&ap.currentSize)) }
|
2026-04-25 17:39:16 +08:00
|
|
|
|
|
|
|
|
|
|
// Release 释放线程池
|
2026-06-12 09:46:02 +08:00
|
|
|
|
func (ap *AdaptivePool) Release() { ap.pool.Release() }
|
2026-04-25 17:39:16 +08:00
|
|
|
|
|
2026-06-13 12:39:24 +08:00
|
|
|
|
// Wait 等待所有任务完成(最多等待 10 分钟)
|
2026-04-25 17:39:16 +08:00
|
|
|
|
func (ap *AdaptivePool) Wait() {
|
2026-06-13 12:39:24 +08:00
|
|
|
|
deadline := time.After(10 * time.Minute)
|
2026-04-25 17:39:16 +08:00
|
|
|
|
for ap.pool.Running() > 0 {
|
2026-06-13 12:39:24 +08:00
|
|
|
|
select {
|
|
|
|
|
|
case <-deadline:
|
|
|
|
|
|
common.LogError(i18n.Tr("adaptive_pool_wait_timeout"))
|
|
|
|
|
|
return
|
|
|
|
|
|
default:
|
|
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
|
|
}
|
2026-04-25 17:39:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|