feat: 十轮网关优化 - 安全加固/可观测性/性能/可靠性
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
CI / security-scan (push) Has been cancelled

- SSE Keepalive Ping (15s心跳防止代理断连)
- Timing HTTP 头 (X-Timing-Queue/Inference/Total-Ms)
- Adapter Request-ID 传播到后端
- Session 清理日志回调
- Server 安全加固 (ReadHeaderTimeout/MaxHeaderBytes 防 slowloris)
- Usage Tracker 数据保留清理 (retentionDays + 定期清理)
- Config Reload 后 Adapter Registry 更新 (RegisterIfAbsent + RWMutex)
- Rate Limiter 空闲 Bucket 清理 (30分钟过期)
- Shutdown Drain 超时可配置 (ShutdownDrainSeconds)
- Config 模型字段校验增强 (provider/endpoint/actual_model)
- Auth 过期 Key 自动清理 (5分钟扫描)
- Admin API Rate Limiting
- Adapter Health Check 独立超时 (每个 adapter 3s)
- TCP 连接阶段超时 (DialContext 5s + KeepAlive 30s)
- 幂等键缓存、审计日志、Gzip 中间件、CORS Expose Headers
- Backpressure 响应头、熔断器 Prometheus 指标
- 连接池优化、Trace-ID 全链路传播
This commit is contained in:
selfrelease
2026-08-03 15:43:11 +08:00
parent e7e98271d4
commit da9c8334d8
27 changed files with 3002 additions and 309 deletions
+56 -9
View File
@@ -7,27 +7,27 @@ import (
// CircuitBreaker implements a sliding-window circuit breaker for adapter health.
type CircuitBreaker struct {
mu sync.Mutex
mu sync.Mutex
errorRateThreshold float64
minRequests int
windowSeconds int
openDuration time.Duration
halfOpenMax int
minRequests int
windowSeconds int
openDuration time.Duration
halfOpenMax int
// sliding window state
requests []time.Time
requests []time.Time
errors []time.Time
// breaker state
state breakerState
openedAt time.Time
state breakerState
openedAt time.Time
halfOpenCount int
}
type breakerState int
const (
breakerClosed breakerState = iota
breakerClosed breakerState = iota
breakerOpen
breakerHalfOpen
)
@@ -126,6 +126,53 @@ func (cb *CircuitBreaker) State() string {
return "unknown"
}
// BreakerStats 熔断器统计信息。
type BreakerStats struct {
State string `json:"state"`
TotalRequests int `json:"total_requests"`
TotalErrors int `json:"total_errors"`
ErrorRate float64 `json:"error_rate"`
WindowSeconds int `json:"window_seconds"`
OpenDuration string `json:"open_duration"`
Threshold float64 `json:"error_rate_threshold"`
MinRequests int `json:"min_requests"`
}
// Stats 返回熔断器的详细统计信息。
func (cb *CircuitBreaker) Stats() BreakerStats {
cb.mu.Lock()
defer cb.mu.Unlock()
now := time.Now()
cb.prune(now)
total := len(cb.requests)
errors := len(cb.errors)
var errorRate float64
if total > 0 {
errorRate = float64(errors) / float64(total)
}
stateName := "closed"
switch cb.state {
case breakerOpen:
stateName = "open"
case breakerHalfOpen:
stateName = "half_open"
}
return BreakerStats{
State: stateName,
TotalRequests: total,
TotalErrors: errors,
ErrorRate: errorRate,
WindowSeconds: cb.windowSeconds,
OpenDuration: cb.openDuration.String(),
Threshold: cb.errorRateThreshold,
MinRequests: cb.minRequests,
}
}
// prune removes entries outside the sliding window.
func (cb *CircuitBreaker) prune(now time.Time) {
cutoff := now.Add(-time.Duration(cb.windowSeconds) * time.Second)
+83 -17
View File
@@ -14,29 +14,33 @@ import (
// Scheduler manages task queuing and execution with priority-based scheduling.
type Scheduler struct {
mu sync.Mutex
queue *priorityQueue
running map[string]*task.Task
maxRunning int
maxQueued int
notifyCh chan struct{}
logger *observability.Logger
ctx context.Context
cancel context.CancelFunc
mu sync.Mutex
queue *priorityQueue
running map[string]*task.Task
maxRunning int
maxQueued int
notifyCh chan struct{}
logger *observability.Logger
ctx context.Context
cancel context.CancelFunc
agingSeconds int // 优先级老化阈值(秒),0 表示禁用
agingMinPrio int // 老化生效的最低优先级(仅 P2-P4 老化,P0/P1 不老化)
}
// NewScheduler creates a new scheduler.
func NewScheduler(cfg *config.SchedulerConfig, logger *observability.Logger) *Scheduler {
ctx, cancel := context.WithCancel(context.Background())
s := &Scheduler{
queue: &priorityQueue{},
running: make(map[string]*task.Task),
maxRunning: cfg.MaxRunningTasks,
maxQueued: cfg.MaxQueuedTasks,
notifyCh: make(chan struct{}, 1),
logger: logger,
ctx: ctx,
cancel: cancel,
queue: &priorityQueue{},
running: make(map[string]*task.Task),
maxRunning: cfg.MaxRunningTasks,
maxQueued: cfg.MaxQueuedTasks,
notifyCh: make(chan struct{}, 1),
logger: logger,
ctx: ctx,
cancel: cancel,
agingSeconds: cfg.PriorityAgingSeconds,
agingMinPrio: 2, // P2 及以上优先级才会老化
}
heap.Init(s.queue)
return s
@@ -71,6 +75,10 @@ func (s *Scheduler) Submit(t *task.Task) error {
func (s *Scheduler) GetNext(ctx context.Context) (*task.Task, error) {
for {
s.mu.Lock()
// 优先级老化:提升等待过久的低优先级任务
if s.agingSeconds > 0 {
s.applyPriorityAging()
}
if s.queue.Len() > 0 && len(s.running) < s.maxRunning {
t := heap.Pop(s.queue).(*task.Task)
s.running[t.ID] = t
@@ -88,6 +96,35 @@ func (s *Scheduler) GetNext(ctx context.Context) (*task.Task, error) {
}
}
// applyPriorityAging 对队列中等待超过 agingSeconds 的低优先级任务提升一级优先级。
// 必须在持有 s.mu 锁的情况下调用。
func (s *Scheduler) applyPriorityAging() {
if s.agingSeconds <= 0 || s.queue.Len() == 0 {
return
}
now := time.Now()
aged := 0
for i := 0; i < s.queue.Len(); i++ {
t := (*s.queue)[i]
if int(t.Priority) < s.agingMinPrio {
continue // P0/P1 不老化
}
waitSec := int(now.Sub(t.CreatedAt).Seconds())
if waitSec >= s.agingSeconds {
t.Priority-- // 提升一级(数值越小优先级越高)
if t.Priority < 0 {
t.Priority = 0
}
aged++
}
}
if aged > 0 {
heap.Init(s.queue) // 重新堆化
s.logger.Info("priority aging applied",
observability.F().Event("priority_aging").Set("aged_count", aged))
}
}
// Complete marks a task as completed and removes it from running.
func (s *Scheduler) Complete(taskID string) {
s.mu.Lock()
@@ -130,6 +167,35 @@ func (s *Scheduler) GetTask(taskID string) (*task.Task, bool) {
return nil, false
}
// ListTasks 返回所有运行中和排队中的任务(支持分页)。
// status 过滤:running、queued、空字符串表示全部。
func (s *Scheduler) ListTasks(status string, limit, offset int) ([]*task.Task, int) {
s.mu.Lock()
defer s.mu.Unlock()
var all []*task.Task
if status == "" || status == "running" {
for _, t := range s.running {
all = append(all, t)
}
}
if status == "" || status == "queued" {
for i := 0; i < s.queue.Len(); i++ {
all = append(all, (*s.queue)[i])
}
}
total := len(all)
if offset >= total {
return []*task.Task{}, total
}
end := offset + limit
if end > total {
end = total
}
return all[offset:end], total
}
// Stop shuts down the scheduler.
func (s *Scheduler) Stop() {
s.cancel()