import asyncio
import sys
import threading
import time
from olc.bean.match_wrapper import MatchWrapper
from olc.bean.olc_config_rule import FlowPolicy
from olc.limit.token_service import TokenService
from olc.utils.atomic_num import AtomicNum
from olc.utils.time_utils import TimeUnit, TimeUtils
class QpsTokenBucket(TokenService):
@staticmethod
def create(wrapper: MatchWrapper[FlowPolicy]) -> "QpsTokenBucket":
policy = wrapper.policy
return QpsTokenBucket(policy.time_unit_to_seconds(), TimeUnit.SECOND, policy.rate_limit, policy.burst_limit,
policy.max_wait_time_ms)
def __init__(self, time_interval: int, time_unit: TimeUnit, rate_limit: int, buster_limit: int,
wait_time_ms: int = 0, need_refresh: bool = True):
"""
初始化令牌桶
:param time_interval: 时间间隔
:param time_unit: 时间单位
:param rate_limit: 速率
:param buster_limit: 桶大小
:param wait_time_ms: 等待时间
:param need_refresh: 是否需要刷新
"""
self._buster_limit = buster_limit
if rate_limit <= 0:
self._token_interval_ms = sys.maxsize
else:
self._token_interval_ms = time_unit.to_millisecond(time_interval) / rate_limit
self._time_interval = time_interval
self._time_unit = time_unit
self._lock = threading.Lock()
self._wait_time_ms = wait_time_ms
self._last_time = AtomicNum(TimeUtils.get_milliseconds())
self._need_refresh = need_refresh
if self._need_refresh:
self._token_number = AtomicNum(float(buster_limit))
else:
self._token_number = AtomicNum(float(0))
self._last_token_number = self._token_number.get()
def _refresh_token(self):
"""刷新令牌桶"""
if not self._need_refresh:
return
current_time = TimeUtils.get_milliseconds()
last_time = self._last_time.get()
if current_time > last_time:
add_tokens = (current_time - last_time) / self._token_interval_ms
if add_tokens > 0 and self._last_time.compare_and_set(last_time, current_time):
self._token_number.add_and_limit(add_tokens, self._buster_limit)
def _acquire(self, num: int) -> bool:
"""
申请令牌
:param num: 令牌数量
:return:
"""
if self._token_number.get() >= num:
self._last_token_number = self._token_number.get()
self._token_number.dec_and_limit(num, 0)
return True
return False
def _calculate_token_wait_time(self, num: int) -> int:
"""计算获取指定数量令牌需要等待的毫秒数(纯计算,不修改任何状态)"""
current_tokens = self._token_number.get()
if current_tokens >= num:
return 0
needed_tokens = num - current_tokens
return int(needed_tokens * self._token_interval_ms)
def _roll_back(self):
"""回滚令牌"""
current_tokens = self._token_number.get()
self._token_number.set(self._last_token_number)
rollback_time = int(
(current_tokens - self._last_token_number)
* self._token_interval_ms
)
self._last_time.set(self._last_time.get() - rollback_time)
def _try_acquire_locked(self, token_number: int) -> int:
"""锁内执行令牌获取逻辑,返回需等待的毫秒数;-1 表示获取失败"""
self._refresh_token()
if self._wait_time_ms <= 0:
return 0 if self._acquire(token_number) else -1
current_time = TimeUtils.get_milliseconds()
wait_time_ms = self._calculate_token_wait_time(token_number)
last_time = self._last_time.get()
real_wait_time = last_time + wait_time_ms - current_time
if real_wait_time > self._wait_time_ms:
return -1
self._last_token_number = self._token_number.get()
self._token_number.dec_and_limit(token_number, 0)
if wait_time_ms > 0:
self._last_time.add(wait_time_ms)
return max(0, real_wait_time)
def try_acquire(self, token_number: int = 1) -> bool:
with self._lock:
wait_time_ms = self._try_acquire_locked(token_number)
if wait_time_ms < 0:
return False
if wait_time_ms > 0:
try:
time.sleep(wait_time_ms / 1000.0)
except InterruptedError | KeyboardInterrupt:
pass
return True
async def async_try_acquire(self, num: int = 1) -> bool:
with self._lock:
wait_time_ms = self._try_acquire_locked(num)
if wait_time_ms < 0:
return False
if wait_time_ms > 0:
try:
await asyncio.sleep(wait_time_ms / 1000.0)
except asyncio.CancelledError:
pass
return True
def pre_check(self, num: int = 1) -> bool:
"""预检查是否能在 wait_time_ms 内获取到指定数量的令牌"""
self._refresh_token()
current_tokens = self._token_number.get()
if current_tokens >= num:
return True
if self._wait_time_ms <= 0:
return False
wait_time_ms = self._calculate_token_wait_time(num)
current_time = TimeUtils.get_milliseconds()
real_wait_time = self._last_time.get() + wait_time_ms - current_time
return real_wait_time <= self._wait_time_ms
def get_token_number(self):
return self._token_number.get()
def set_token_number(self, token_number: int):
if token_number > 0:
self._token_number.set(min(token_number, self._buster_limit))
def add_token_number(self, num: int):
self._token_number.add_and_limit(num, self._buster_limit)
def dec_token_number(self, num: int):
self._token_number.dec_and_limit(num, 0)
def update_rate(self, rate_limit: int):
if rate_limit <= 0:
self._token_interval_ms = sys.maxsize
else:
new_interval = float(self._time_unit.to_millisecond(self._time_interval)) / rate_limit
if self._token_interval_ms != sys.maxsize:
ratio = self._token_interval_ms / new_interval
if ratio != 1 and ratio > 0:
current_token = self._token_number.get()
new_token = int(current_token * ratio)
self._token_number.set(min(new_token, self._buster_limit))
self._token_interval_ms = new_interval