Add grade hot cache

This commit is contained in:
咕谷酱
2025-08-21 23:35:25 +08:00
parent 7c193937d1
commit 822d7c6377
13 changed files with 973 additions and 47 deletions

View File

@@ -6,6 +6,11 @@ from app.config import settings
from app.dependencies.database import get_db, get_redis
from app.dependencies.fetcher import get_fetcher
from app.log import logger
from app.scheduler.user_cache_scheduler import (
schedule_user_cache_cleanup_task,
schedule_user_cache_preload_task,
schedule_user_cache_warmup_task,
)
class CacheScheduler:
@@ -42,17 +47,26 @@ class CacheScheduler:
# 启动时执行一次排行榜缓存刷新
await self._refresh_ranking_cache()
# 启动时执行一次用户缓存预热
await self._warmup_user_cache()
beatmap_cache_counter = 0
ranking_cache_counter = 0
user_cache_counter = 0
user_cleanup_counter = 0
# 从配置文件获取间隔设置
check_interval = 5 * 60 # 5分钟检查间隔
beatmap_cache_interval = 30 * 60 # 30分钟beatmap缓存间隔
ranking_cache_interval = settings.ranking_cache_refresh_interval_minutes * 60 # 从配置读取
user_cache_interval = 15 * 60 # 15分钟用户缓存预加载间隔
user_cleanup_interval = 60 * 60 # 60分钟用户缓存清理间隔
beatmap_cache_cycles = beatmap_cache_interval // check_interval
ranking_cache_cycles = ranking_cache_interval // check_interval
user_cache_cycles = user_cache_interval // check_interval
user_cleanup_cycles = user_cleanup_interval // check_interval
while self.running:
try:
@@ -64,6 +78,8 @@ class CacheScheduler:
beatmap_cache_counter += 1
ranking_cache_counter += 1
user_cache_counter += 1
user_cleanup_counter += 1
# beatmap缓存预热
if beatmap_cache_counter >= beatmap_cache_cycles:
@@ -74,6 +90,16 @@ class CacheScheduler:
if ranking_cache_counter >= ranking_cache_cycles:
await self._refresh_ranking_cache()
ranking_cache_counter = 0
# 用户缓存预加载
if user_cache_counter >= user_cache_cycles:
await self._preload_user_cache()
user_cache_counter = 0
# 用户缓存清理
if user_cleanup_counter >= user_cleanup_cycles:
await self._cleanup_user_cache()
user_cleanup_counter = 0
except asyncio.CancelledError:
break
@@ -110,16 +136,37 @@ class CacheScheduler:
schedule_ranking_refresh_task,
)
# 获取数据库会话
async for session in get_db():
# 使用独立的数据库会话
from app.dependencies.database import with_db
async with with_db() as session:
await schedule_ranking_refresh_task(session, redis)
break # 只需要一次会话
logger.info("Ranking cache refresh completed successfully")
except Exception as e:
logger.error(f"Ranking cache refresh failed: {e}")
async def _warmup_user_cache(self):
"""用户缓存预热"""
try:
await schedule_user_cache_warmup_task()
except Exception as e:
logger.error(f"User cache warmup failed: {e}")
async def _preload_user_cache(self):
"""用户缓存预加载"""
try:
await schedule_user_cache_preload_task()
except Exception as e:
logger.error(f"User cache preload failed: {e}")
async def _cleanup_user_cache(self):
"""用户缓存清理"""
try:
await schedule_user_cache_cleanup_task()
except Exception as e:
logger.error(f"User cache cleanup failed: {e}")
# Beatmap缓存调度器保持向后兼容
class BeatmapsetCacheScheduler(CacheScheduler):

View File

@@ -0,0 +1,120 @@
"""
用户缓存预热任务调度器
"""
from __future__ import annotations
import asyncio
from datetime import UTC, datetime, timedelta
from app.config import settings
from app.database import User
from app.database.score import Score
from app.dependencies.database import get_db, get_redis
from app.log import logger
from app.service.user_cache_service import get_user_cache_service
from sqlmodel import col, func, select
async def schedule_user_cache_preload_task():
"""定时用户缓存预加载任务"""
# 默认启用用户缓存预加载,除非明确禁用
enable_user_cache_preload = getattr(settings, "enable_user_cache_preload", True)
if not enable_user_cache_preload:
return
try:
logger.info("Starting user cache preload task...")
redis = get_redis()
cache_service = get_user_cache_service(redis)
# 使用独立的数据库会话
from app.dependencies.database import with_db
async with with_db() as session:
# 获取最近24小时内活跃的用户提交过成绩的用户
recent_time = datetime.now(UTC) - timedelta(hours=24)
active_user_ids = (
await session.exec(
select(Score.user_id, func.count().label("score_count"))
.where(col(Score.ended_at) >= recent_time)
.group_by(col(Score.user_id))
.order_by(col("score_count").desc())
.limit(settings.user_cache_max_preload_users) # 使用配置中的限制
)
).all()
if active_user_ids:
user_ids = [row[0] for row in active_user_ids]
await cache_service.preload_user_cache(session, user_ids)
logger.info(f"Preloaded cache for {len(user_ids)} active users")
else:
logger.info("No active users found for cache preload")
logger.info("User cache preload task completed successfully")
except Exception as e:
logger.error(f"User cache preload task failed: {e}")
async def schedule_user_cache_warmup_task():
"""定时用户缓存预热任务 - 预加载排行榜前100用户"""
try:
logger.info("Starting user cache warmup task...")
redis = get_redis()
cache_service = get_user_cache_service(redis)
# 使用独立的数据库会话
from app.dependencies.database import with_db
async with with_db() as session:
# 获取全球排行榜前100的用户
from app.database.statistics import UserStatistics
from app.models.score import GameMode
for mode in GameMode:
try:
top_users = (
await session.exec(
select(UserStatistics.user_id)
.where(UserStatistics.mode == mode)
.order_by(col(UserStatistics.pp).desc())
.limit(100)
)
).all()
if top_users:
user_ids = list(top_users)
await cache_service.preload_user_cache(session, user_ids)
logger.info(f"Warmed cache for top 100 users in {mode}")
# 避免过载,稍微延迟
await asyncio.sleep(1)
except Exception as e:
logger.error(f"Failed to warm cache for {mode}: {e}")
continue
logger.info("User cache warmup task completed successfully")
except Exception as e:
logger.error(f"User cache warmup task failed: {e}")
async def schedule_user_cache_cleanup_task():
"""定时用户缓存清理任务"""
try:
logger.info("Starting user cache cleanup task...")
redis = get_redis()
# 清理过期的用户缓存Redis会自动处理TTL这里主要记录统计信息
cache_service = get_user_cache_service(redis)
stats = await cache_service.get_cache_stats()
logger.info(f"User cache stats: {stats}")
logger.info("User cache cleanup task completed successfully")
except Exception as e:
logger.error(f"User cache cleanup task failed: {e}")