lolly/internal/loadbalance/slow_start.go
xfy f1dd8cbeb0 fix: complete Stage 1 deep review fixes
Batch 1 - Crash/data corruption:
- Add nil/empty config guards in server and app
- Protect against fasthttp buffer mutation in logging
- Ensure mimeutil falls back before caching empty MIME
- Make proxy health checker and resolver tolerate nil cfg
- Prevent Lua EMERG/ALERT/CRIT log levels from killing process

Batch 2 - Concurrency/race conditions:
- Add focused race tests for compression pool, ssl/ocsp,
  server proxies slice, Lua timer cancel, and TCP socket connect
- Fix Lua timer active race and socket ConnectAsync race

Batch 3 - Resource leaks/functional damage:
- Track and stop rate limiter cleanup goroutines on shutdown
- Add tests for proxy connection count leaks, server pool deadlock,
  Linux sendfile integrity, WebSocket frame data loss,
  stream UDP stop deadlock, and upstream name mismatch
- Fix stream ListenTCP/upstream lookup and UDP shutdown

Batch 4 - High severity technical debt:
- Add nil guard in security headers middleware
- Validate sliding window divisor and add tests
- Protect static handler fields with existing RWMutex
- Avoid mutating live HostClient.Addr on DNS updates
- Fix slow start Start/Stop and resolver restart
- Initialize variable fallback context maps
- Add tests for request_id/time_local and logging file handle close

Also:
- Relax integration variable performance threshold under race detector
- Update benchmark plan docs for new stream ListenTCP signature

Verification:
- make test: pass
- go test -race -count=1 ./internal/...: pass
- make lint: 0 issues
- make build: success
2026-06-17 09:48:43 +08:00

187 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package loadbalance 提供负载均衡算法实现。
//
// 该文件实现 SlowStartManager 慢启动管理器,支持:
// - 目标恢复健康时渐进式增加权重
// - 权重从 1 线性增加到配置权重
// - EffectiveWeight 字段方案(零侵入)
//
// 主要用途:
//
// 防止刚恢复的后端服务器被大量请求压垮。
//
// 作者xfy
package loadbalance
import (
"sync"
"sync/atomic"
"time"
)
// SlowStartManager 慢启动管理器。
//
// 统一管理所有目标的慢启动状态和权重计算。
// 使用 EffectiveWeight 字段方案,无需修改 Balancer 实现。
type SlowStartManager struct {
targets map[string]*SlowStartState // key: target.URL
mu sync.RWMutex
interval time.Duration // 权重更新间隔
stopCh chan struct{}
running atomic.Bool
// 目标查找回调
findTarget func(url string) *Target
}
// SlowStartState 慢启动状态。
type SlowStartState struct {
BaseWeight int // 配置的基础权重
RecoverTime time.Time // 恢复健康的时间
SlowStart time.Duration // 慢启动持续时间
}
// NewSlowStartManager 创建慢启动管理器。
func NewSlowStartManager(interval time.Duration) *SlowStartManager {
if interval <= 0 {
interval = time.Second // 默认 1 秒更新一次
}
return &SlowStartManager{
targets: make(map[string]*SlowStartState),
interval: interval,
stopCh: make(chan struct{}),
}
}
// OnTargetHealthy 目标恢复健康时调用。
//
// 初始化慢启动状态,设置 EffectiveWeight = 1。
func (m *SlowStartManager) OnTargetHealthy(target *Target) {
if target.SlowStart <= 0 {
return // 未配置慢启动
}
m.mu.Lock()
defer m.mu.Unlock()
m.targets[target.URL] = &SlowStartState{
BaseWeight: target.Weight,
RecoverTime: time.Now(),
SlowStart: target.SlowStart,
}
// 设置初始权重为 1
target.EffectiveWeight.Store(1)
}
// OnTargetUnhealthy 目标变为不健康时调用。
//
// 清除慢启动状态,重置 EffectiveWeight = 0。
func (m *SlowStartManager) OnTargetUnhealthy(target *Target) {
m.mu.Lock()
delete(m.targets, target.URL)
m.mu.Unlock()
// 重置有效权重
target.EffectiveWeight.Store(0)
}
// Start 启动后台权重更新。
//
// 定期遍历所有慢启动中的目标,计算并更新 EffectiveWeight。
// 支持 Start-Stop-Start 周期:每次启动都会重新创建 stopCh
// 避免 Stop 关闭通道后 updateLoop 立即退出。
func (m *SlowStartManager) Start() {
if m.running.Swap(true) {
return // 已经在运行
}
// 重新创建 stopCh确保新一轮更新循环使用未关闭的通道
m.mu.Lock()
m.stopCh = make(chan struct{})
stopCh := m.stopCh
m.mu.Unlock()
go m.updateLoop(stopCh)
}
// SetFindTarget 设置目标查找回调。
//
// 慢启动管理器通过该回调在内部状态与负载均衡目标之间建立映射,
// 用于更新 EffectiveWeight。必须在 Start 之前设置。
//
// 参数:
// - findTarget: 根据 target URL 查找对应 Target 的回调函数
func (m *SlowStartManager) SetFindTarget(findTarget func(url string) *Target) {
m.mu.Lock()
defer m.mu.Unlock()
m.findTarget = findTarget
}
// Stop 停止后台更新。
func (m *SlowStartManager) Stop() {
if !m.running.Swap(false) {
return
}
select {
case <-m.stopCh:
// 已经关闭
default:
close(m.stopCh)
}
}
// updateLoop 后台更新循环。
func (m *SlowStartManager) updateLoop(stopCh <-chan struct{}) {
ticker := time.NewTicker(m.interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
m.updateEffectiveWeights()
case <-stopCh:
return
}
}
}
// updateEffectiveWeights 更新所有慢启动目标的有效权重。
func (m *SlowStartManager) updateEffectiveWeights() {
now := time.Now()
var toDelete []string
m.mu.Lock()
defer m.mu.Unlock()
for url, state := range m.targets {
elapsed := now.Sub(state.RecoverTime)
if elapsed >= state.SlowStart {
// 慢启动完成,标记删除
toDelete = append(toDelete, url)
continue
}
// 线性增长:从 1 增加到 BaseWeight
progress := float64(elapsed) / float64(state.SlowStart)
effectiveWeight := min(max(int(1+progress*float64(state.BaseWeight-1)), 1), state.BaseWeight)
// 查找目标并更新 EffectiveWeight
if m.findTarget != nil {
if target := m.findTarget(url); target != nil {
target.EffectiveWeight.Store(int64(effectiveWeight))
}
}
}
// 删除已完成的慢启动状态
for _, url := range toDelete {
delete(m.targets, url)
// 重置 EffectiveWeight 为 0表示使用配置权重
if m.findTarget != nil {
if target := m.findTarget(url); target != nil {
target.EffectiveWeight.Store(0)
}
}
}
}