Files
huya-go/internal/sniper/timing.go
T
yml2213 a135effd7a feat(sniper): 将预热提前量调优至 10 秒并设为异步安全默认
- 预热时序重构:WarmupLead 从 2s 延长至 10s,ResyncLead 从 3s 延长至 15s;开抢前 10s 开始异步建连,开抢前 5s 全部就绪待命
- 兼顾零建连延迟与发令安全:既彻底杜绝了 2s 极限时间被慢网络超时拖垮发车的问题,又保留了首发 0 建连耗时的长连接极速优势
- 默认配置:Prewarm 恢复为默认开启,并在界面增加明确的调度行为提示
- 验证方式:go test ./... -count=1 全通,npm run build 构建通过
2026-10-17 18:36:39 +08:00

79 lines
2.9 KiB
Go

package sniper
import (
"time"
"huya-go/internal/huya"
)
// MinTriggerAdvance 是触发提前量的下限。
//
// 动态提前量只补偿「请求在途时间 + 时钟误差」,正常情况下就是几十毫秒;
// 下限 50ms 是为了在时钟校准异常(误差边界为 0)时仍然留一点余量。
const MinTriggerAdvance = 50 * time.Millisecond
const (
// WarmupLead 是预热连接相对开抢时刻的提前量。
//
// 提前 10 秒开始预热连接:为 30+ 账号留足 3~5 秒平滑建连缓冲,开抢前 5 秒全部就绪;
// 既享受长连接 0 延迟首发,又能在开抢前形成静默隔离带,绝不阻塞整点发车。
WarmupLead = 10 * time.Second
// ResyncLead 是「临近复校」相对开抢时刻的提前量。
//
// 提前 15 秒复校时钟:在预热前完成服务端时钟校准,排定精确的预热与触发时刻。
ResyncLead = 15 * time.Second
)
// TriggerPlan 描述一次定时抢购的关键时刻(全部是「本机时钟」下的时刻)。
type TriggerPlan struct {
// Advance 是相对开抢时刻的提前量(含 RTT/2 与时钟误差补偿)。
Advance time.Duration
// OpenAt 是开抢时刻换算到本机时钟后的时刻。
OpenAt time.Time
// WarmupAt 是应当开始预热连接的时刻。
WarmupAt time.Time
// ResyncAt 是应当复校服务端时钟的时刻。
ResyncAt time.Time
// TriggerAt 是应当开始发请求的时刻。
TriggerAt time.Time
// WindowEnd 是抢购窗口结束时刻。
WindowEnd time.Time
}
// PlanTrigger 计算定时抢购的预热 / 复校 / 触发 / 窗口结束时刻。
//
// target 是界面上填写的时间,按「服务端时间」理解;offset 是「服务端 - 本机」的偏差。
// advance 默认取 calibration.ErrorBound + calibration.RTT/2(参考 dygou_go 的
// dynamicAdvance):固定 2500ms 的提前量既浪费又伤身——2026-10-16 实测,开抢前那 2.5 秒
// 打出去的请求全部被「活动太火爆」挡回,等于先把账号的令牌桶烧掉。
// overrideLead > 0 时沿用旧行为(兼容界面里显式填写的提前量)。
func PlanTrigger(target time.Time, calibration huya.TimeCalibration, overrideLead time.Duration, window time.Duration) TriggerPlan {
advance := overrideLead
if advance <= 0 {
advance = calibration.ErrorBound + calibration.RTT/2
if advance < MinTriggerAdvance {
advance = MinTriggerAdvance
}
}
if window <= 0 {
window = DefaultWindowSeconds * time.Second
}
openAt := target.Add(-calibration.Offset)
return TriggerPlan{
Advance: advance,
OpenAt: openAt,
WarmupAt: openAt.Add(-WarmupLead),
ResyncAt: openAt.Add(-ResyncLead),
TriggerAt: openAt.Add(-advance),
WindowEnd: openAt.Add(window),
}
}
// WindowDuration 把「窗口秒数」配置换算成时长;<=0 时取默认值。
func WindowDuration(seconds int) time.Duration {
if seconds <= 0 {
return DefaultWindowSeconds * time.Second
}
return time.Duration(seconds) * time.Second
}