package services import ( "log" "runtime" "time" "go-taobao/config" "go-taobao/utils" ) // Timer 定时触发器,精确控制抢购时机 type Timer struct { cfg *config.RushConfig timeSync *utils.TimeSync } // NewTimer 创建定时器 func NewTimer(cfg *config.RushConfig, ts *utils.TimeSync) *Timer { return &Timer{cfg: cfg, timeSync: ts} } // WaitUntilPreload 等待到预加载时间点(目标时间 - PreloadBefore) // 返回后应立即开始预加载购物车 func (t *Timer) WaitUntilPreload() { preloadTime := t.cfg.TargetTime.Add(-t.cfg.PreloadBefore) remaining := t.timeSync.UntilTarget(preloadTime) if remaining <= 0 { log.Println("[定时器] 已过预加载时间,立即执行") return } log.Printf("[定时器] 距离预加载还有 %v,等待中...", remaining.Round(time.Second)) t.sleepUntil(preloadTime) log.Println("[定时器] 预加载时间到!") } // WaitUntilRush 等待到抢购时间点(目标时间 - SubmitAdvance) // 使用 Sleep + 自旋忙等 的混合策略确保毫秒级精度 func (t *Timer) WaitUntilRush() { // 实际触发时间 = 目标时间 - 提前量(补偿网络延迟) rushTime := t.cfg.TargetTime.Add(-t.cfg.SubmitAdvance) remaining := t.timeSync.UntilTarget(rushTime) if remaining <= 0 { log.Println("[定时器] 已过抢购时间,立即执行") return } log.Printf("[定时器] 距离抢购还有 %v (含 %v 提前量)", remaining.Round(time.Millisecond), t.cfg.SubmitAdvance) t.sleepUntil(rushTime) serverNow := t.timeSync.Now() diff := serverNow.Sub(t.cfg.TargetTime) log.Printf("[定时器] 触发! 服务器时间: %s (偏差: %v)", serverNow.Format("15:04:05.000"), diff) } // sleepUntil 混合等待策略:先 Sleep 粗等,最后 3 秒自旋忙等 func (t *Timer) sleepUntil(target time.Time) { // 阶段1: 距离目标 >5s 时,用 Sleep 粗等(省 CPU) for { remaining := t.timeSync.UntilTarget(target) if remaining <= 3*time.Second { break } // 每次 sleep 剩余时间的一半,逐步逼近 sleepDur := remaining / 2 if sleepDur > 1*time.Second { sleepDur = 1 * time.Second } time.Sleep(sleepDur) } // 阶段2: 最后 3 秒,锁定 OS 线程 + 自旋忙等(最高精度) runtime.LockOSThread() defer runtime.UnlockOSThread() log.Println("[定时器] 进入自旋忙等模式...") for t.timeSync.Now().Before(target) { // 自旋,不让出 CPU runtime.Gosched() // 偶尔让出一下,避免完全饿死其他 goroutine } } // PrintCountdown 持续打印倒计时日志(在单独的 goroutine 中运行) func (t *Timer) PrintCountdown(done <-chan struct{}) { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for { select { case <-done: return case <-ticker.C: remaining := t.timeSync.UntilTarget(t.cfg.TargetTime) if remaining <= 0 { return } hours := int(remaining.Hours()) mins := int(remaining.Minutes()) % 60 secs := int(remaining.Seconds()) % 60 log.Printf("[倒计时] %02d:%02d:%02d (服务器时间: %s)", hours, mins, secs, t.timeSync.Now().Format("15:04:05")) } } }