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
187 lines
4.6 KiB
Go
187 lines
4.6 KiB
Go
// 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)
|
||
}
|
||
}
|
||
}
|
||
}
|