Files
go-taobao/services/timer.go
2026-02-09 16:05:56 +08:00

113 lines
3.1 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 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"))
}
}
}