first commit
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
package hd4k
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 全局限流器
|
||||
//
|
||||
// 算法对齐参考实现(E-Hentai Downloader 的「请求间隔」设置):
|
||||
// 用一个全局的 nextRequestTime,每次请求都把自己排到它后面,再把它往后推。
|
||||
//
|
||||
// next = max(next, now) + interval
|
||||
// 等待到 next 时刻再真正发出
|
||||
//
|
||||
// 效果是「所有请求之间至少间隔 interval 毫秒」,与并发数无关 ——
|
||||
// 哪怕 40 个 worker 同时来,也会被串成每 interval 放行一个。
|
||||
//
|
||||
// interval = 0 时完全旁路,不产生任何额外开销。
|
||||
|
||||
// Limiter 全局请求节流器
|
||||
type Limiter struct {
|
||||
mu sync.Mutex
|
||||
interval time.Duration
|
||||
next time.Time // 下一个请求允许发出的时刻
|
||||
until time.Time // 全局冷却截止时刻(429 时设置)
|
||||
}
|
||||
|
||||
// NewLimiter 建限流器(interval <= 0 表示不限流)
|
||||
func NewLimiter(interval time.Duration) *Limiter {
|
||||
if interval < 0 {
|
||||
interval = 0
|
||||
}
|
||||
return &Limiter{interval: interval}
|
||||
}
|
||||
|
||||
// SetInterval 运行时改间隔
|
||||
func (l *Limiter) SetInterval(d time.Duration) {
|
||||
if d < 0 {
|
||||
d = 0
|
||||
}
|
||||
l.mu.Lock()
|
||||
l.interval = d
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// Interval 当前间隔
|
||||
func (l *Limiter) Interval() time.Duration {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.interval
|
||||
}
|
||||
|
||||
// Cooldown 触发全局冷却:所有在途和后续请求一起等 d。
|
||||
//
|
||||
// 这是 429 的正确应对方式。当前实现里 40 个 worker 各自独立退避,
|
||||
// 撞到 429 后它们会错峰重试 —— 结果是持续不断往已经限流的服务器上
|
||||
// 打请求,永远缓不过来。全局冷却让**所有** worker 一起停下,
|
||||
// 冷却期满再一起放行,这才是真能让对方缓过来的节奏。
|
||||
//
|
||||
// 多次触发取最晚时刻,避免短时间连环 429 把冷却不断重置到更短。
|
||||
func (l *Limiter) Cooldown(d time.Duration) {
|
||||
if d <= 0 {
|
||||
return
|
||||
}
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if until := time.Now().Add(d); until.After(l.until) {
|
||||
l.until = until
|
||||
}
|
||||
}
|
||||
|
||||
// CooldownLeft 当前剩余的全局冷却时长(0 = 没有冷却)
|
||||
func (l *Limiter) CooldownLeft() time.Duration {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if d := time.Until(l.until); d > 0 {
|
||||
return d
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Wait 占用一个请求名额,必要时阻塞到放行时刻。
|
||||
// 返回 error 是为了能响应取消(点「停止」时不该继续排队)。
|
||||
//
|
||||
// ⚠️ 全局冷却(Cooldown)必须在 interval 旁路**之前**判断:
|
||||
// 默认 interval=0 会直接 return,不检查冷却的话 Cooldown 就是死代码。
|
||||
func (l *Limiter) Wait(ctx context.Context) error {
|
||||
// 先等全局冷却结束
|
||||
for {
|
||||
l.mu.Lock()
|
||||
cooldown := time.Until(l.until)
|
||||
l.mu.Unlock()
|
||||
if cooldown <= 0 {
|
||||
break
|
||||
}
|
||||
// 分片等,期间可能有新的更长冷却被设置进来
|
||||
if cooldown > 300*time.Millisecond {
|
||||
cooldown = 300 * time.Millisecond
|
||||
}
|
||||
if err := sleepCtx(ctx, cooldown); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
interval := l.interval
|
||||
if interval <= 0 {
|
||||
l.mu.Unlock()
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
// 关键:如果过去的排队已经堆积,next 可能远在未来,
|
||||
// 这里取 max 让第一个请求立刻发出去,避免冷启动时集体等一大轮。
|
||||
if l.next.Before(now) {
|
||||
l.next = now
|
||||
}
|
||||
at := l.next
|
||||
l.next = at.Add(interval)
|
||||
l.mu.Unlock()
|
||||
|
||||
return sleepCtx(ctx, time.Until(at))
|
||||
}
|
||||
|
||||
// sleepCtx 睡到 d 后返回,期间被取消则返回 ctx.Err()
|
||||
func sleepCtx(ctx context.Context, d time.Duration) error {
|
||||
if d <= 0 {
|
||||
return ctx.Err()
|
||||
}
|
||||
t := time.NewTimer(d)
|
||||
defer t.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-t.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user