Files
huya-go/internal/sniper/timing.go
T

80 lines
3.0 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 sniper
import (
"time"
"huya-go/internal/huya"
)
// MinTriggerAdvance 是触发提前量的下限。
//
// 动态提前量只补偿「请求在途时间 + 时钟误差」,正常情况下就是几十毫秒;
// 下限 50ms 是为了在时钟校准异常(误差边界为 0)时仍然留一点余量。
const MinTriggerAdvance = 50 * time.Millisecond
const (
// WarmupLead 是预热连接相对开抢时刻的提前量。
//
// 2 秒足够建连 + 握手(实测单账号建连约 300ms),又不会让连接闲置太久:
// 旧实现一启动就预热、然后干等几十分钟,连接是否还活着只能靠运气。
WarmupLead = 2 * time.Second
// ResyncLead 是「临近复校」相对开抢时刻的提前量。
//
// 只做一次校准是不够的:pc-1/pc-2/pc-3 三台机器的本机偏移分别是 ≈0 / -370ms / +900ms,
// 而且本机时钟在等待期间还会漂移。触发前 3 秒再校准一次,能把这部分误差压掉。
ResyncLead = 3 * 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
}