初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,264 @@
|
||||
package balancer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/chenxin/hunjing/mcp/web-search/internal/config"
|
||||
"github.com/chenxin/hunjing/mcp/web-search/internal/models"
|
||||
)
|
||||
|
||||
// KeySearcher 按密钥执行搜索的接口。
|
||||
// 与 SearchProvider 解耦,方便测试时注入 mock。
|
||||
type KeySearcher interface {
|
||||
Search(ctx context.Context, apiKey string, req *models.SearchRequest) (*models.SearchResult, error)
|
||||
HealthCheck(ctx context.Context) error
|
||||
Name() models.ProviderKind
|
||||
}
|
||||
|
||||
// LoadBalancer 负载均衡器(goroutine-safe)。
|
||||
type LoadBalancer struct {
|
||||
pools map[models.ProviderKind]*KeyPool
|
||||
searchers map[models.ProviderKind]KeySearcher
|
||||
disabled map[models.ProviderKind]bool // 运行时动态禁用
|
||||
retryCount int
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewLoadBalancer(cfg *config.BalancerConfig) *LoadBalancer {
|
||||
return &LoadBalancer{
|
||||
pools: make(map[models.ProviderKind]*KeyPool),
|
||||
searchers: make(map[models.ProviderKind]KeySearcher),
|
||||
disabled: make(map[models.ProviderKind]bool),
|
||||
retryCount: cfg.RetryCount,
|
||||
}
|
||||
}
|
||||
|
||||
// AddProvider 注册 provider 及其密钥池。
|
||||
func (lb *LoadBalancer) AddProvider(
|
||||
provider models.ProviderKind,
|
||||
keys []string,
|
||||
weight int,
|
||||
searcher KeySearcher,
|
||||
) {
|
||||
lb.mu.Lock()
|
||||
defer lb.mu.Unlock()
|
||||
|
||||
cbThreshold := 5
|
||||
cbCooldown := 30 * time.Second
|
||||
lb.pools[provider] = NewKeyPool(provider, keys, weight, cbThreshold, cbCooldown)
|
||||
lb.searchers[provider] = searcher
|
||||
// 添加时默认启用
|
||||
delete(lb.disabled, provider)
|
||||
}
|
||||
|
||||
// RemoveProvider 移除 provider 及其密钥池。
|
||||
func (lb *LoadBalancer) RemoveProvider(provider models.ProviderKind) {
|
||||
lb.mu.Lock()
|
||||
defer lb.mu.Unlock()
|
||||
delete(lb.pools, provider)
|
||||
delete(lb.searchers, provider)
|
||||
delete(lb.disabled, provider)
|
||||
}
|
||||
|
||||
// EnableProvider 启用 provider。
|
||||
func (lb *LoadBalancer) EnableProvider(provider models.ProviderKind) {
|
||||
lb.mu.Lock()
|
||||
defer lb.mu.Unlock()
|
||||
delete(lb.disabled, provider)
|
||||
}
|
||||
|
||||
// DisableProvider 禁用 provider(搜索时跳过,不删除配置)。
|
||||
func (lb *LoadBalancer) DisableProvider(provider models.ProviderKind) {
|
||||
lb.mu.Lock()
|
||||
defer lb.mu.Unlock()
|
||||
lb.disabled[provider] = true
|
||||
}
|
||||
|
||||
// IsProviderEnabled 检查 provider 是否启用。
|
||||
func (lb *LoadBalancer) IsProviderEnabled(provider models.ProviderKind) bool {
|
||||
lb.mu.RLock()
|
||||
defer lb.mu.RUnlock()
|
||||
return !lb.disabled[provider]
|
||||
}
|
||||
|
||||
// SetRetryCount 更新重试次数。
|
||||
func (lb *LoadBalancer) SetRetryCount(n int) {
|
||||
lb.mu.Lock()
|
||||
defer lb.mu.Unlock()
|
||||
lb.retryCount = n
|
||||
}
|
||||
|
||||
// HasProvider 检查 provider 是否已注册。
|
||||
func (lb *LoadBalancer) HasProvider(provider models.ProviderKind) bool {
|
||||
lb.mu.RLock()
|
||||
defer lb.mu.RUnlock()
|
||||
_, ok := lb.pools[provider]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Search 执行搜索(自动选密钥、跨 provider 重试)。
|
||||
func (lb *LoadBalancer) Search(ctx context.Context, req *models.SearchRequest) (*models.SearchResult, error) {
|
||||
providers := lb.resolveProviders(req.Providers)
|
||||
|
||||
var lastErr error
|
||||
for attempt := 0; attempt <= lb.getRetryCount(); attempt++ {
|
||||
for _, prov := range providers {
|
||||
pool, ok := lb.pools[prov]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ks := pool.Select()
|
||||
if ks == nil {
|
||||
continue
|
||||
}
|
||||
searcher := lb.searchers[prov]
|
||||
if searcher == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
result, err := searcher.Search(ctx, ks.Key, req)
|
||||
duration := time.Since(start)
|
||||
|
||||
if err != nil {
|
||||
ks.RecordFailure()
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
ks.RecordSuccess(duration)
|
||||
result.Provider = prov
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
if lastErr != nil {
|
||||
return nil, fmt.Errorf("balancer: all providers exhausted: %w", lastErr)
|
||||
}
|
||||
return nil, fmt.Errorf("balancer: no available provider or key for query: %s", req.Query)
|
||||
}
|
||||
|
||||
// SearchAll 并发搜索所有启用 provider(用于 Merge 模式)。
|
||||
func (lb *LoadBalancer) SearchAll(ctx context.Context, req *models.SearchRequest) []*models.SearchResult {
|
||||
providers := lb.resolveProviders(nil)
|
||||
type result struct {
|
||||
prov models.ProviderKind
|
||||
res *models.SearchResult
|
||||
err error
|
||||
}
|
||||
|
||||
ch := make(chan result, len(providers))
|
||||
for _, prov := range providers {
|
||||
p := prov
|
||||
go func() {
|
||||
pool, ok := lb.pools[p]
|
||||
if !ok {
|
||||
ch <- result{prov: p, err: fmt.Errorf("no pool")}
|
||||
return
|
||||
}
|
||||
ks := pool.Select()
|
||||
if ks == nil {
|
||||
ch <- result{prov: p, err: fmt.Errorf("no available key")}
|
||||
return
|
||||
}
|
||||
searcher := lb.searchers[p]
|
||||
if searcher == nil {
|
||||
ch <- result{prov: p, err: fmt.Errorf("no searcher")}
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
res, err := searcher.Search(ctx, ks.Key, req)
|
||||
duration := time.Since(start)
|
||||
if err != nil {
|
||||
ks.RecordFailure()
|
||||
ch <- result{prov: p, err: err}
|
||||
return
|
||||
}
|
||||
ks.RecordSuccess(duration)
|
||||
res.Provider = p
|
||||
ch <- result{prov: p, res: res}
|
||||
}()
|
||||
}
|
||||
|
||||
var results []*models.SearchResult
|
||||
for range providers {
|
||||
r := <-ch
|
||||
if r.res != nil {
|
||||
results = append(results, r.res)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// ActiveKeyCount 返回每个 provider 的活跃密钥数。
|
||||
// ActiveKeyCount 返回每个 provider 的活跃密钥数。
|
||||
// ActiveKeyCount 返回每个 provider 的活跃密钥数。
|
||||
func (lb *LoadBalancer) ActiveKeyCount() map[models.ProviderKind]int {
|
||||
lb.mu.RLock()
|
||||
defer lb.mu.RUnlock()
|
||||
out := make(map[models.ProviderKind]int)
|
||||
for p, pool := range lb.pools {
|
||||
if lb.disabled[p] {
|
||||
continue
|
||||
}
|
||||
out[p] = pool.ActiveCount()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Providers 返回所有已注册的 provider 名称(无论启用与否)。
|
||||
func (lb *LoadBalancer) Providers() []models.ProviderKind {
|
||||
lb.mu.RLock()
|
||||
defer lb.mu.RUnlock()
|
||||
out := make([]models.ProviderKind, 0, len(lb.pools))
|
||||
for p := range lb.pools {
|
||||
out = append(out, p)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// HealthCheck 对指定 provider 执行健康检查(委托给 KeySearcher)。
|
||||
func (lb *LoadBalancer) HealthCheck(ctx context.Context, provider models.ProviderKind) error {
|
||||
lb.mu.RLock()
|
||||
searcher, ok := lb.searchers[provider]
|
||||
lb.mu.RUnlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("healthcheck: provider not found: %s", provider)
|
||||
}
|
||||
return searcher.HealthCheck(ctx)
|
||||
}
|
||||
|
||||
func (lb *LoadBalancer) getRetryCount() int {
|
||||
lb.mu.RLock()
|
||||
defer lb.mu.RUnlock()
|
||||
return lb.retryCount
|
||||
}
|
||||
|
||||
// resolveProviders 按用户偏好和可用性确定 provider 顺序(跳过已禁用的)。
|
||||
func (lb *LoadBalancer) resolveProviders(preferred []string) []models.ProviderKind {
|
||||
lb.mu.RLock()
|
||||
defer lb.mu.RUnlock()
|
||||
|
||||
if len(preferred) == 0 {
|
||||
out := make([]models.ProviderKind, 0, len(lb.pools))
|
||||
for p := range lb.pools {
|
||||
if !lb.disabled[p] {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
out := make([]models.ProviderKind, 0, len(preferred))
|
||||
seen := make(map[models.ProviderKind]bool)
|
||||
for _, name := range preferred {
|
||||
p := models.ProviderKind(name)
|
||||
if _, ok := lb.pools[p]; ok && !seen[p] && !lb.disabled[p] {
|
||||
out = append(out, p)
|
||||
seen[p] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in new issue
Block a user