提交
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"""Redis 读缓存(可选基础设施)。
|
||||
|
||||
- REDIS_URL 留空、连接失败或超时:所有操作静默退化为「无缓存」,接口照常直查数据库,
|
||||
且本进程内禁用重试(避免每个请求都陪跑一次连接超时)。
|
||||
- REDIS_URL 留空、连接失败或超时:所有操作静默退化为「无缓存」,接口照常直查数据库。
|
||||
故障后熔断 _RECOVERY_SECONDS(期间所有请求直连,不陪跑连接超时),到期自动放行
|
||||
一次探测——成功即完全恢复,仍失败则重新熔断(Redis 属加速件,坏了不能拖慢接口)。
|
||||
- 失效策略:TTL 自然过期 + 版本号(INCR)作废。自选股增删等写操作只 INCR 版本 key,
|
||||
旧缓存 key 里带着旧版本号,无需 SCAN 批量删除。
|
||||
- 只缓存「读多写少、可容忍短暂陈旧」的聚合数据(股票列表、筛选项等);
|
||||
@@ -13,6 +14,7 @@
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
import time
|
||||
@@ -23,12 +25,13 @@ import redis.asyncio as aioredis
|
||||
from .config import settings
|
||||
|
||||
_pool: aioredis.ConnectionPool | None = None
|
||||
_disabled = False # 一次失败后本进程禁用(Redis 属加速件,坏了不能拖慢接口)
|
||||
_RECOVERY_SECONDS = 60.0 # 熔断时长:期间直连不试 Redis,到期放行一次探测
|
||||
_disabled_until = 0.0 # 熔断截止的 monotonic 时刻;0 = 未熔断
|
||||
|
||||
|
||||
def _client() -> aioredis.Redis | None:
|
||||
global _pool, _disabled
|
||||
if not settings.redis_url or _disabled:
|
||||
global _pool
|
||||
if not settings.redis_url or time.monotonic() < _disabled_until:
|
||||
return None
|
||||
if _pool is None:
|
||||
_pool = aioredis.ConnectionPool.from_url(
|
||||
@@ -43,8 +46,8 @@ def _client() -> aioredis.Redis | None:
|
||||
|
||||
|
||||
def _bail() -> None:
|
||||
global _disabled
|
||||
_disabled = True
|
||||
global _disabled_until
|
||||
_disabled_until = time.monotonic() + _RECOVERY_SECONDS
|
||||
|
||||
|
||||
def digest(*parts: Any) -> str:
|
||||
@@ -84,7 +87,21 @@ def local_set(key: str, raw: str, ttl: int) -> None:
|
||||
_local_store[key] = (time.monotonic() + max(1, min(ttl, 120)), raw)
|
||||
_local_bytes += len(raw) + 64 # 连同 dict/tuple 开销粗略计入
|
||||
while _local_store and (len(_local_store) > _LOCAL_MAX_ENTRIES or _local_bytes > _LOCAL_MAX_BYTES):
|
||||
_local_bytes -= len(_local_store.popitem(last=False)[1][1]) + 64
|
||||
# dict 是插入序:next(iter(...)) 即最旧键(dict.popitem 不支持参数,别写成 OrderedDict 的写法)
|
||||
oldest = next(iter(_local_store))
|
||||
_local_bytes -= len(_local_store.pop(oldest)[1]) + 64
|
||||
|
||||
|
||||
# --- 后台写(fire-and-forget)------------------------------------------------
|
||||
# 读路径拿到响应后异步写 Redis、不阻塞返回。统一入口:任务挂全局集合防 GC,
|
||||
# cache_set 内部自带异常静默(缓存层尽力而为),优雅停机丢最后一次写无害(TTL 兜底)。
|
||||
_bg_tasks: set[asyncio.Task] = set()
|
||||
|
||||
|
||||
def set_bg(key: str, value: Any, ttl: int) -> None:
|
||||
t = asyncio.create_task(cache_set(key, value, ttl))
|
||||
_bg_tasks.add(t)
|
||||
t.add_done_callback(_bg_tasks.discard)
|
||||
|
||||
|
||||
# --- 版本号本地缓存:热请求连 Redis GET ver:xx 都省掉 ------------------------
|
||||
|
||||
Reference in New Issue
Block a user