From ce131b2867922c3884e80f82bacca8d7a8343bb6 Mon Sep 17 00:00:00 2001 From: Sakurasan <26715255+Sakurasan@users.noreply.github.com> Date: Mon, 10 Aug 2026 13:47:21 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=83=AD=E7=82=B9=E7=A9=BF=E9=80=8F?= =?UTF-8?q?=E7=9B=98=E4=B8=AD=E7=BC=93=E5=AD=98=E8=BF=87=E6=9C=9F=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=90=8C=E6=AD=A5=E9=87=8D=E5=BB=BA=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E6=98=BE=E7=A4=BA=E4=B8=8A=E4=B8=AA=E4=BA=A4=E6=98=93?= =?UTF-8?q?=E6=97=A5=E6=97=A7=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fetch_theme_graph 原用 stale-while-revalidate:缓存过期先返回旧数据、 后台异步重建。缓存里若是上个交易日数据,过期后用户一直看到旧图, 重建完成前不会更新。 - 交易时段缓存过期/无缓存 → 同步重建(加锁去重),绝不返回旧数据 - 非交易时段过期 → 保留 stale-while-revalidate(行情无实时变化,秒开无害) 实测盘中同步重建约 7s,可接受。 Co-Authored-By: Claude --- backend/services/themes.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/backend/services/themes.py b/backend/services/themes.py index 9665535..e9380b8 100644 --- a/backend/services/themes.py +++ b/backend/services/themes.py @@ -399,10 +399,11 @@ async def _rebuild_task(cache_key: str, sort_field: int, top_n: int, lock: async async def fetch_theme_graph(sort_field: int = 1, top_n: int = 30, limit: int = 1000) -> dict: - """获取热点穿透图数据(盘中 60s 缓存 + stale-while-revalidate) + """获取热点穿透图数据(盘中 60s 缓存,盘中过期同步重建,非盘中 stale-while-revalidate) - 缓存命中且未过期 → 直接返回;已过期 → 返回旧数据并后台异步重建(秒开); - 无缓存 → 同步构建(并发下加锁去重)。返回前按 limit 裁剪 stocks。 + 缓存新鲜 → 直接返回; + 非交易时段过期 → 返回旧数据并后台异步重建(秒开,非盘中行情无实时变化,旧值可接受); + 交易时段过期 / 无缓存 → 同步重建(加锁去重),绝不返回上个交易日的旧图。 Args: sort_field: 题材排序 1=涨幅, 4=热度 @@ -412,20 +413,21 @@ async def fetch_theme_graph(sort_field: int = 1, top_n: int = 30, limit: int = 1 cache_key = f"theme_graph:{sort_field}:{top_n}" data, expires_at = _get_graph_cache(cache_key) - if data is not None: - # 有缓存:新鲜直接返回;过期返回旧数据并后台刷新 - if not (expires_at and expires_at > time.time()): - _spawn_rebuild(cache_key, sort_field, top_n) + if data is not None and expires_at and expires_at > time.time(): + # 缓存新鲜 → 直接返回 return _trim_graph_result(data, limit) - # 无缓存:同步构建(并发下加锁去重) + # 非交易时段过期:行情无实时变化,先返回旧值(秒开),后台异步重建 + if data is not None and not _is_trading_time(): + _spawn_rebuild(cache_key, sort_field, top_n) + return _trim_graph_result(data, limit) + + # 交易时段过期 / 无缓存:同步重建,加锁去重 lock = _REBUILD_LOCKS.setdefault(cache_key, asyncio.Lock()) async with lock: data, expires_at = _get_graph_cache(cache_key) - if data is not None: - # 等待锁期间已被其他请求写入 - if not (expires_at and expires_at > time.time()): - _spawn_rebuild(cache_key, sort_field, top_n) + if data is not None and expires_at and expires_at > time.time(): + # 等待锁期间已被其他请求刷新 return _trim_graph_result(data, limit) data = await _build_theme_graph(sort_field, top_n) _set_graph_cache(cache_key, data)