Files
2026-08-21 16:00:13 +08:00

170 lines
3.6 KiB
Go

package balancer
import (
"math"
"sync"
"time"
"github.com/chenxin/hunjing/mcp/web-search/internal/models"
)
// KeyStatus 密钥状态。
type KeyStatus int
const (
KeyActive KeyStatus = iota // 正常
KeyCooldown // 冷却中(熔断后)
KeyDead // 死亡(多次失败不再恢复)
)
// KeyState 单个密钥状态。
type KeyState struct {
Provider models.ProviderKind
Key string
Weight int
mu sync.Mutex
status KeyStatus
latencyEWMA time.Duration // 指数加权滑动平均延迟
successCount int64
failCount int64
cb *CircuitBreaker
}
func newKeyState(provider models.ProviderKind, key string, weight int, cbThreshold int, cbCooldown time.Duration) *KeyState {
return &KeyState{
Provider: provider,
Key: key,
Weight: weight,
status: KeyActive,
cb: NewCircuitBreaker(cbThreshold, cbCooldown),
}
}
// Allow 检查是否允许使用此密钥。
func (ks *KeyState) Allow() bool {
ks.mu.Lock()
defer ks.mu.Unlock()
if ks.status != KeyActive {
return false
}
return ks.cb.Allow()
}
// RecordSuccess 记录成功调用,更新 EWMA 延迟。
func (ks *KeyState) RecordSuccess(latency time.Duration) {
ks.mu.Lock()
defer ks.mu.Unlock()
ks.successCount++
ks.cb.Success()
// EWMA 更新:α = 0.3
alpha := 0.3
if ks.latencyEWMA == 0 {
ks.latencyEWMA = latency
} else {
ks.latencyEWMA = time.Duration(
(1-alpha)*float64(ks.latencyEWMA) + alpha*float64(latency),
)
}
ks.status = KeyActive
}
// RecordFailure 记录失败调用。
func (ks *KeyState) RecordFailure() {
ks.mu.Lock()
defer ks.mu.Unlock()
ks.failCount++
ks.cb.Failure()
if ks.cb.State() == StateOpen {
ks.status = KeyCooldown
}
}
// CombinedScore 计算综合分数(越高优先选用)。
// 综合 = 基础权重 × 延迟惩罚 × 可靠性因子
func (ks *KeyState) CombinedScore() float64 {
ks.mu.Lock()
defer ks.mu.Unlock()
if ks.status != KeyActive {
return 0
}
if ks.cb.State() != StateClosed {
return 0
}
base := float64(ks.Weight)
latencyPenalty := 1.0
if ks.latencyEWMA > 0 {
latencyPenalty = 1.0 / (1.0 + ks.latencyEWMA.Seconds())
}
reliability := 1.0
total := ks.successCount + ks.failCount
if total > 0 && ks.failCount > 0 {
reliability = 1.0 - float64(ks.failCount)/float64(total)
}
return base * latencyPenalty * math.Max(0.1, reliability)
}
// KeyPool 管理一个 provider 下的多密钥。
type KeyPool struct {
provider models.ProviderKind
keys []*KeyState
mu sync.RWMutex
cbThreshold int
cbCooldown time.Duration
}
func NewKeyPool(provider models.ProviderKind, keys []string, weight int, cbThreshold int, cbCooldown time.Duration) *KeyPool {
kp := &KeyPool{
provider: provider,
keys: make([]*KeyState, 0, len(keys)),
cbThreshold: cbThreshold,
cbCooldown: cbCooldown,
}
for _, k := range keys {
kp.keys = append(kp.keys, newKeyState(provider, k, weight, cbThreshold, cbCooldown))
}
return kp
}
// Select 按综合分数选最佳密钥。
func (kp *KeyPool) Select() *KeyState {
kp.mu.RLock()
defer kp.mu.RUnlock()
var best *KeyState
var bestScore float64
for _, ks := range kp.keys {
if !ks.Allow() {
continue
}
score := ks.CombinedScore()
if score > bestScore {
bestScore = score
best = ks
}
}
return best
}
// ActiveCount 返回活跃密钥数。
func (kp *KeyPool) ActiveCount() int {
kp.mu.RLock()
defer kp.mu.RUnlock()
count := 0
for _, ks := range kp.keys {
if ks.status == KeyActive {
count++
}
}
return count
}
// TotalCount 返回密钥总数。
func (kp *KeyPool) TotalCount() int {
kp.mu.RLock()
defer kp.mu.RUnlock()
return len(kp.keys)
}