缓存优化

This commit is contained in:
stardrophere
2026-03-12 14:17:15 +08:00
parent 3d7d53f96f
commit 37791c7976
5 changed files with 82 additions and 18 deletions
+28 -2
View File
@@ -1,6 +1,7 @@
# app/api/endpoints/events.py
import time
from datetime import timedelta
from typing import List
from typing import Dict, List, Tuple
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
@@ -26,6 +27,10 @@ router = APIRouter()
# 排名轨迹最多返回多少个点,避免长时间跨度下数据过大
MAX_RANKING_POINTS = 30
# --- 轻量级接口缓存配置 ---
_UNIFIED_EVENTS_CACHE: Dict[str, Tuple[float, PaginatedUnifiedEventResponse]] = {}
CACHE_TTL_SECONDS = 60
# ---------------------------
@router.get("/unified", response_model=PaginatedUnifiedEventResponse)
def list_unified_events(
@@ -37,6 +42,17 @@ def list_unified_events(
db: Session = Depends(get_db),
):
"""分页返回统一事件,附带各平台热搜、排名轨迹和标签。"""
# --- 1. 尝试从缓存读取 ---
cache_key = f"{min_hot}:{hours}:{sort_by}:{skip}:{limit}"
current_time = time.time()
if cache_key in _UNIFIED_EVENTS_CACHE:
expire_time, cached_data = _UNIFIED_EVENTS_CACHE[cache_key]
if current_time < expire_time:
return cached_data
# -----------------------
time_limit = utcnow() - timedelta(hours=hours)
# 先查总数,用于前端判断是否还有更多
@@ -149,7 +165,17 @@ def list_unified_events(
)
has_more = (skip + limit) < total
return PaginatedUnifiedEventResponse(total=total, has_more=has_more, data=results)
response = PaginatedUnifiedEventResponse(total=total, has_more=has_more, data=results)
# --- 2. 写入缓存 ---
if len(_UNIFIED_EVENTS_CACHE) > 1000:
# 防止内存无限增长
_UNIFIED_EVENTS_CACHE.clear()
_UNIFIED_EVENTS_CACHE[cache_key] = (current_time + CACHE_TTL_SECONDS, response)
# ------------------
return response
@router.get("/unified/{event_id}", response_model=UnifiedEventResponse)
+35 -2
View File
@@ -1,4 +1,5 @@
from typing import List
import time
from typing import Any, Dict, List, Tuple
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.exc import IntegrityError
@@ -16,6 +17,16 @@ from app.services.matching_service import recommend_events_for_user
router = APIRouter()
# --- 轻量级接口缓存配置 ---
_RECOMMEND_CACHE: Dict[str, Tuple[float, Any]] = {}
CACHE_TTL_SECONDS = 60
def _invalidate_user_cache(user_id: int):
"""清除某个用户的推荐结果缓存(当用户新增或删除关键词时调用)"""
keys_to_delete = [k for k in _RECOMMEND_CACHE.keys() if k.startswith(f"{user_id}:")]
for k in keys_to_delete:
_RECOMMEND_CACHE.pop(k, None)
# ---------------------------
def _ensure_self_access(path_user_id: int, current_user: AppUser) -> None:
"""校验路径 user_id 是否为当前登录用户本人。"""
@@ -79,6 +90,7 @@ def create_user_preference(
)
db.refresh(db_obj)
_invalidate_user_cache(user_id) # 失效推荐缓存
return db_obj
@@ -107,6 +119,7 @@ def delete_user_preference(
db.delete(preference)
db.commit()
_invalidate_user_cache(user_id) # 失效推荐缓存
return None
@@ -127,6 +140,16 @@ def recommend_events(
"""基于用户兴趣词推荐事件(精确匹配 + 语义匹配)。"""
_ensure_self_access(user_id, current_user)
# --- 1. 尝试从缓存读取 ---
cache_key = f"{user_id}:{min_hot}:{hours}:{limit}:{semantic_threshold}:{sort_by}"
current_time = time.time()
if cache_key in _RECOMMEND_CACHE:
expire_time, cached_data = _RECOMMEND_CACHE[cache_key]
if current_time < expire_time:
return cached_data
# -----------------------
matched = recommend_events_for_user(
db,
user_id=user_id,
@@ -155,8 +178,18 @@ def recommend_events(
)
)
return UserPreferenceRecommendationResponse(
response = UserPreferenceRecommendationResponse(
user_id=user_id,
total=len(result_data),
data=result_data,
)
# --- 2. 写入缓存 ---
if len(_RECOMMEND_CACHE) > 2000:
# 防止内存无限增长
_RECOMMEND_CACHE.clear()
_RECOMMEND_CACHE[cache_key] = (current_time + CACHE_TTL_SECONDS, response)
# ------------------
return response
+7 -6
View File
@@ -2,6 +2,7 @@
# 由 APScheduler 每分钟调用,检查当前时刻是否有用户需要接收推送,
# 如匹配则生成摘要邮件并发送,同时写入 DeliveryHistory 防重复。
import logging
import os
from logging.handlers import TimedRotatingFileHandler
from dataclasses import dataclass, field
from datetime import datetime, time as dt_time, timedelta, timezone, tzinfo
@@ -50,17 +51,17 @@ logger.setLevel(logging.INFO)
logger.propagate = False
# 推送时间窗口:实际执行时刻与设定时间的最大容差(分钟)
DELIVERY_WINDOW_MINUTES = 2
DELIVERY_WINDOW_MINUTES = int(os.getenv("DELIVERY_WINDOW_MINUTES", 2))
# 同一用户两次推送之间的最小间隔(分钟)
MIN_PUSH_INTERVAL_MINUTES = 30
MIN_PUSH_INTERVAL_MINUTES = int(os.getenv("MIN_PUSH_INTERVAL_MINUTES", 30))
# 单次推送最多携带的事件数
MAX_EVENTS_PER_PUSH = 12
MAX_EVENTS_PER_PUSH = int(os.getenv("MAX_EVENTS_PER_PUSH", 12))
# 默认模式热度阈值(无关键词或无匹配时使用)
DEFAULT_MODE_HOT_THRESHOLD = 3
DEFAULT_MODE_HOT_THRESHOLD = int(os.getenv("DEFAULT_MODE_HOT_THRESHOLD", 3))
# 默认模式查询时间窗口(小时)
DEFAULT_MODE_HOURS = 48
DEFAULT_MODE_HOURS = int(os.getenv("DEFAULT_MODE_HOURS", 24))
# 用户时区无效时的兜底时区
DEFAULT_FALLBACK_TIMEZONE = "Asia/Shanghai"
DEFAULT_FALLBACK_TIMEZONE = os.getenv("DEFAULT_FALLBACK_TIMEZONE", "Asia/Shanghai")
# ==========================================