- 预热时序重构:WarmupLead 从 2s 延长至 10s,ResyncLead 从 3s 延长至 15s;开抢前 10s 开始异步建连,开抢前 5s 全部就绪待命 - 兼顾零建连延迟与发令安全:既彻底杜绝了 2s 极限时间被慢网络超时拖垮发车的问题,又保留了首发 0 建连耗时的长连接极速优势 - 默认配置:Prewarm 恢复为默认开启,并在界面增加明确的调度行为提示 - 验证方式:go test ./... -count=1 全通,npm run build 构建通过
79 lines
2.9 KiB
Go
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
|
|
}
|