Implement user-specific question numbering and update database schema. Added triggers for automatic question numbering and adjustments upon deletion. Enhanced CRUD operations to manage user_question_number effectively.
This commit is contained in:
142
services/rate_limiting/rate_limit_service.py
Normal file
142
services/rate_limiting/rate_limit_service.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""
|
||||
Сервис для управления rate limiting в AnonBot
|
||||
"""
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
|
||||
from aiogram.exceptions import TelegramAPIError, TelegramRetryAfter
|
||||
|
||||
from config.constants import MIN_REQUESTS_FOR_ADAPTATION, HIGH_ERROR_RATE_THRESHOLD, LOW_ERROR_RATE_THRESHOLD
|
||||
from services.infrastructure.logger import get_logger
|
||||
from .rate_limit_config import RateLimitSettings, get_adaptive_config, get_rate_limit_config
|
||||
from .rate_limiter import send_with_rate_limit, telegram_rate_limiter
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class RateLimitService:
|
||||
"""Сервис для управления rate limiting"""
|
||||
|
||||
def __init__(self):
|
||||
self.rate_limiter = telegram_rate_limiter
|
||||
self.config = get_rate_limit_config()
|
||||
self.stats = {
|
||||
'total_requests': 0,
|
||||
'successful_requests': 0,
|
||||
'failed_requests': 0,
|
||||
'retry_after_errors': 0,
|
||||
'other_errors': 0,
|
||||
'total_wait_time': 0.0
|
||||
}
|
||||
|
||||
async def send_with_rate_limit(
|
||||
self,
|
||||
send_func: Callable,
|
||||
chat_id: int,
|
||||
*args,
|
||||
**kwargs
|
||||
) -> Any:
|
||||
"""
|
||||
Отправляет сообщение с соблюдением rate limit
|
||||
|
||||
Args:
|
||||
send_func: Функция отправки (например, bot.send_message)
|
||||
chat_id: ID чата
|
||||
*args, **kwargs: Аргументы для функции отправки
|
||||
|
||||
Returns:
|
||||
Результат выполнения функции отправки
|
||||
"""
|
||||
self.stats['total_requests'] += 1
|
||||
logger.info(f"Обработка rate limit запроса для чата {chat_id}")
|
||||
|
||||
try:
|
||||
result, wait_time = await self.rate_limiter.send_with_rate_limit(send_func, chat_id, *args, **kwargs)
|
||||
self.stats['successful_requests'] += 1
|
||||
self.stats['total_wait_time'] += wait_time
|
||||
logger.info(f"Rate limited сообщение успешно отправлено в чат {chat_id}, время ожидания: {wait_time:.2f}с")
|
||||
return result
|
||||
|
||||
except TelegramRetryAfter as e:
|
||||
self.stats['failed_requests'] += 1
|
||||
self.stats['retry_after_errors'] += 1
|
||||
logger.warning(f"Превышен rate limit для чата {chat_id}: {e}")
|
||||
raise
|
||||
|
||||
except TelegramAPIError as e:
|
||||
self.stats['failed_requests'] += 1
|
||||
self.stats['other_errors'] += 1
|
||||
logger.error(f"Ошибка Telegram API для чата {chat_id}: {e}")
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
self.stats['failed_requests'] += 1
|
||||
self.stats['other_errors'] += 1
|
||||
logger.error(f"Неожиданная ошибка в rate limit сервисе для чата {chat_id}: {e}")
|
||||
raise
|
||||
|
||||
def get_stats(self) -> Dict[str, Any]:
|
||||
"""Получает статистику rate limiting"""
|
||||
total = self.stats['total_requests']
|
||||
if total == 0:
|
||||
return {
|
||||
'total_requests': 0,
|
||||
'successful_requests': 0,
|
||||
'failed_requests': 0,
|
||||
'success_rate': 0.0,
|
||||
'error_rate': 0.0,
|
||||
'retry_after_errors': 0,
|
||||
'other_errors': 0,
|
||||
'retry_after_rate': 0.0,
|
||||
'other_error_rate': 0.0,
|
||||
'average_wait_time': 0.0
|
||||
}
|
||||
|
||||
return {
|
||||
'total_requests': total,
|
||||
'successful_requests': self.stats['successful_requests'],
|
||||
'failed_requests': self.stats['failed_requests'],
|
||||
'success_rate': self.stats['successful_requests'] / total,
|
||||
'error_rate': self.stats['failed_requests'] / total,
|
||||
'retry_after_errors': self.stats['retry_after_errors'],
|
||||
'other_errors': self.stats['other_errors'],
|
||||
'retry_after_rate': self.stats['retry_after_errors'] / total,
|
||||
'other_error_rate': self.stats['other_errors'] / total,
|
||||
'average_wait_time': self.stats['total_wait_time'] / total if total > 0 else 0.0
|
||||
}
|
||||
|
||||
def reset_stats(self):
|
||||
"""Сбрасывает статистику"""
|
||||
self.stats = {
|
||||
'total_requests': 0,
|
||||
'successful_requests': 0,
|
||||
'failed_requests': 0,
|
||||
'retry_after_errors': 0,
|
||||
'other_errors': 0,
|
||||
'total_wait_time': 0.0
|
||||
}
|
||||
logger.info("Статистика rate limit сброшена")
|
||||
|
||||
def update_config(self, new_config: RateLimitSettings):
|
||||
"""Обновляет конфигурацию rate limiting"""
|
||||
self.config = new_config
|
||||
logger.info(f"Конфигурация rate limit обновлена: {new_config}")
|
||||
|
||||
def get_adaptive_config(self) -> RateLimitSettings:
|
||||
"""Получает адаптивную конфигурацию на основе текущей статистики"""
|
||||
error_rate = self.stats['failed_requests'] / max(1, self.stats['total_requests'])
|
||||
return get_adaptive_config(error_rate, self.config)
|
||||
|
||||
def should_adapt_config(self) -> bool:
|
||||
"""Определяет, нужно ли адаптировать конфигурацию"""
|
||||
if self.stats['total_requests'] < MIN_REQUESTS_FOR_ADAPTATION: # Недостаточно данных
|
||||
return False
|
||||
|
||||
error_rate = self.stats['failed_requests'] / self.stats['total_requests']
|
||||
return error_rate > HIGH_ERROR_RATE_THRESHOLD or error_rate < LOW_ERROR_RATE_THRESHOLD # Высокий или низкий уровень ошибок
|
||||
|
||||
async def adapt_config_if_needed(self):
|
||||
"""Адаптирует конфигурацию если необходимо"""
|
||||
if self.should_adapt_config():
|
||||
new_config = self.get_adaptive_config()
|
||||
self.update_config(new_config)
|
||||
logger.info("Конфигурация rate limit адаптирована на основе текущей производительности")
|
||||
Reference in New Issue
Block a user