第一版

This commit is contained in:
zyj
2026-02-09 16:05:56 +08:00
parent fcdc5b8f2d
commit 514d7f9b0e
11 changed files with 1137 additions and 365 deletions

112
services/timer.go Normal file
View File

@@ -0,0 +1,112 @@
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"))
}
}
}