将项目文件整理到 refer 目录
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
"""请求调度器(§5.6)。
|
||||
|
||||
按 capability 分别维护令牌桶。Phase 0:基础实现;Phase 1 接入批量合并、优先级队列。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .capabilities import Cap, CapabilitySet
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Bucket:
|
||||
capacity: int # 每分钟令牌数
|
||||
tokens: float
|
||||
last_refill: float # 单位:秒
|
||||
|
||||
def consume(self, n: int = 1) -> float:
|
||||
"""尝试消费 n 个令牌,返回需要等待的秒数(0 表示无需等待)。"""
|
||||
now = time.monotonic()
|
||||
elapsed = now - self.last_refill
|
||||
# 60s 内补满 capacity,匀速补
|
||||
refill = elapsed * (self.capacity / 60.0)
|
||||
self.tokens = min(self.capacity, self.tokens + refill)
|
||||
self.last_refill = now
|
||||
|
||||
if self.tokens >= n:
|
||||
self.tokens -= n
|
||||
return 0.0
|
||||
deficit = n - self.tokens
|
||||
# 还需多少秒才能补齐
|
||||
wait = deficit / (self.capacity / 60.0)
|
||||
# 不预扣,留给下一次再竞争(避免饿死优先级高的请求)
|
||||
return wait
|
||||
|
||||
|
||||
class Scheduler:
|
||||
"""每个 capability 一个桶。"""
|
||||
|
||||
def __init__(self, capset: CapabilitySet) -> None:
|
||||
self._capset = capset
|
||||
self._buckets: dict[Cap, _Bucket] = {}
|
||||
self._locks: dict[Cap, asyncio.Lock] = {}
|
||||
for cap, lim in capset.all().items():
|
||||
if lim.rpm:
|
||||
self._buckets[cap] = _Bucket(
|
||||
capacity=lim.rpm,
|
||||
tokens=lim.rpm,
|
||||
last_refill=time.monotonic(),
|
||||
)
|
||||
self._locks[cap] = asyncio.Lock()
|
||||
|
||||
async def acquire(self, cap: Cap, n: int = 1) -> None:
|
||||
"""阻塞直到拿到 n 个令牌。无桶 = 不限流(由调用方保证)。"""
|
||||
bucket = self._buckets.get(cap)
|
||||
if bucket is None:
|
||||
return
|
||||
lock = self._locks[cap]
|
||||
async with lock:
|
||||
while True:
|
||||
wait = bucket.consume(n)
|
||||
if wait == 0:
|
||||
return
|
||||
await asyncio.sleep(wait)
|
||||
Reference in New Issue
Block a user