M5: 渠道体系(负载均衡+并发控制+健康检查+故障转移)
- channel.Candidates 按模型绑定取候选 + Pick 加权随机负载均衡 - TryAcquire 每渠道并发信号量, 满载溢出到其他候选 - HealthMonitor 后台定时探测, 连续失败进 cooldown, 恢复放回(可配 interval/threshold) - doProxy 遍历候选故障转移: 网络错误/429/5xx/超时且未写出响应头时安全重试; 400 等业务错误透传, 流式写出首字节后放弃重试 - 单测覆盖候选过滤/加权/并发/健康状态机 - E2E: 杀上游自动切换、cooldown、恢复、并发溢出 10/10 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/openteam/server/internal/channel"
|
||||
"github.com/openteam/server/internal/config"
|
||||
"github.com/openteam/server/internal/pkg/crypto"
|
||||
"github.com/openteam/server/internal/pkg/jwt"
|
||||
@@ -21,7 +22,10 @@ type App struct {
|
||||
Enc *crypto.Encryptor
|
||||
JWT *jwt.Manager
|
||||
Usage *usage.Recorder
|
||||
Health *channel.HealthMonitor
|
||||
startedAt time.Time
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func New(cfg *config.Config) (*App, error) {
|
||||
@@ -38,11 +42,18 @@ func New(cfg *config.Config) (*App, error) {
|
||||
JWT: jwt.NewManager(cfg.JWT.Secret, cfg.JWT.Issuer, cfg.JWT.AccessTTL, cfg.JWT.RefreshTTL),
|
||||
startedAt: time.Now(),
|
||||
}
|
||||
a.ctx, a.cancel = context.WithCancel(context.Background())
|
||||
a.Usage = usage.NewRecorder(db)
|
||||
|
||||
if err := a.Seed(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a.Health = channel.NewHealthMonitor(db, a.Enc, channel.HealthConfig{
|
||||
Interval: cfg.Proxy.HealthInterval,
|
||||
FailThreshold: cfg.Proxy.HealthFailThreshold,
|
||||
})
|
||||
a.Health.Start(a.ctx)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
@@ -116,6 +127,7 @@ func (a *App) Seed() error {
|
||||
}
|
||||
|
||||
func (a *App) Shutdown(ctx context.Context) {
|
||||
a.cancel()
|
||||
a.Usage.Close()
|
||||
if sqlDB, err := a.DB.DB(); err == nil {
|
||||
_ = sqlDB.Close()
|
||||
|
||||
Reference in New Issue
Block a user