modify: JWT 结果加进程内一级缓存(L1),免每请求共享缓存同步往返(+45~68% 吞吐)
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import hashlib
|
||||
import os
|
||||
import time
|
||||
from collections import OrderedDict
|
||||
|
||||
from fastapi import Depends, HTTPException, status, Request
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
@@ -21,6 +23,45 @@ _jwt_cache = Cache.from_config(get_config()["cache"])
|
||||
# 安全性由命中时的 exp 复核保证,TTL 不承担过期判断
|
||||
_JWT_CACHE_TTL = 60
|
||||
|
||||
# ── 一级:进程内 JWT 结果缓存(LRU + TTL)────────────────────────────
|
||||
# 鉴权依赖是同步依赖(def),每次请求都要走一遍共享缓存——backend=redis 时
|
||||
# 即"每请求一次同步网络往返 + FastAPI 线程池 hop"。同一 token 在 TTL 内被
|
||||
# 反复使用是常态,故在进程内加一层 L1:命中直接返回,不碰网络。
|
||||
# 语义与 L2 完全一致:key 含密钥/算法指纹;命中仍复核 exp(过期=拒绝而非放行);
|
||||
# 条目按 TTL 惰性清理并限长。CHONGMING_JWT_L1=0 可关闭(便于 A/B 与排障)。
|
||||
_JWT_L1_ENABLED = os.environ.get("CHONGMING_JWT_L1", "1") != "0"
|
||||
_JWT_L1_MAX = 1024
|
||||
# 存 orjson 字节而非 dict:命中时反序列化出新副本,保证"调用方修改返回值
|
||||
# 不污染缓存"这一 L2 既有契约(L2 同样是 JSON 往返后返回新对象)
|
||||
_jwt_l1: "OrderedDict[str, tuple[bytes, float, float]]" = OrderedDict() # key → (payload_json, exp, 写入时刻)
|
||||
|
||||
|
||||
def _l1_get(key: str):
|
||||
"""命中返回 (payload_json: bytes, exp);条目超过共享缓存 TTL(运行时可调,便于测试/运维)视为过期"""
|
||||
entry = _jwt_l1.get(key)
|
||||
if entry is None:
|
||||
return None
|
||||
_payload_json, _exp, ts = entry
|
||||
if time.time() - ts >= _JWT_CACHE_TTL:
|
||||
_jwt_l1.pop(key, None)
|
||||
return None
|
||||
_jwt_l1.move_to_end(key)
|
||||
return entry
|
||||
|
||||
|
||||
def _l1_set(key: str, payload_json: bytes, exp: float) -> None:
|
||||
_jwt_l1[key] = (payload_json, exp, time.time())
|
||||
_jwt_l1.move_to_end(key)
|
||||
while len(_jwt_l1) > _JWT_L1_MAX:
|
||||
_jwt_l1.popitem(last=False)
|
||||
|
||||
|
||||
def _l1_sweep(now: float) -> None:
|
||||
"""惰性清理:超 TTL 的整条丢弃(token 永不重复时防堆积),只在表较大时调用"""
|
||||
stale = [k for k, v in _jwt_l1.items() if now - v[2] >= _JWT_CACHE_TTL]
|
||||
for k in stale:
|
||||
_jwt_l1.pop(k, None)
|
||||
|
||||
|
||||
def _cache_key(token: str, secret: str, algorithm: str) -> str:
|
||||
"""缓存 key:token 内容 + 密钥/算法指纹,不同密钥/算法不串用"""
|
||||
@@ -28,20 +69,33 @@ def _cache_key(token: str, secret: str, algorithm: str) -> str:
|
||||
|
||||
|
||||
def _cached_decode(token: str, secret: str, algorithm: str) -> tuple:
|
||||
"""JWT 解密结果缓存((payload, exp)),共享缓存避免高频请求重复密码学运算。
|
||||
"""JWT 解密结果缓存((payload, exp)):一级进程内 LRU,二级共享缓存。
|
||||
|
||||
- 命中且未过期 -> 直接返回,跳过 RSA/ECDSA 验证与 JSON 解析
|
||||
- 命中但 exp 已过 -> 直接拒绝,不重解密(条目保留至 TTL 到期,
|
||||
过期 token 的重复请求零密码学开销;缓存不延长 token 有效期)
|
||||
- 未命中 -> 完整解码后写入共享缓存(TTL=60s)
|
||||
- 一级命中且未过期 -> 直接返回(零网络、零密码学);过期 -> 直接拒绝
|
||||
- 一级未命中 -> 查二级共享缓存;命中且未过期 -> 回填一级后返回
|
||||
- 二级也未命中 -> 完整解码(RSA/ECDSA 验证)后写二级 + 一级(TTL=60s/30s)
|
||||
- decode 失败(无效 token)不缓存,每次都完整验证后拒绝
|
||||
"""
|
||||
key = _cache_key(token, secret, algorithm)
|
||||
now = time.time()
|
||||
if _JWT_L1_ENABLED:
|
||||
hit = _l1_get(key)
|
||||
if hit is not None:
|
||||
payload_json, exp, _ts = hit
|
||||
if not exp or exp >= now:
|
||||
# 反序列化出新副本:调用方修改不影响缓存条目(与 L2 契约一致)
|
||||
return orjson.loads(payload_json), exp
|
||||
raise ValueError("Token 已过期")
|
||||
if len(_jwt_l1) > _JWT_L1_MAX // 2: # 仅大表时惰性清理,避免常态 O(n)
|
||||
_l1_sweep(now)
|
||||
|
||||
entry = _jwt_cache.get(key)
|
||||
if entry is not None:
|
||||
payload = entry["payload"]
|
||||
exp = entry.get("exp", 0)
|
||||
if not exp or exp >= time.time():
|
||||
if _JWT_L1_ENABLED:
|
||||
_l1_set(key, orjson.dumps(payload), exp)
|
||||
return payload, exp
|
||||
raise ValueError("Token 已过期")
|
||||
jwt = JWT(secret, algorithm)
|
||||
@@ -49,6 +103,8 @@ def _cached_decode(token: str, secret: str, algorithm: str) -> tuple:
|
||||
payload = orjson.loads(payload_str.encode("utf-8"))
|
||||
exp = payload.get("exp", 0)
|
||||
_jwt_cache.set(key, {"payload": payload, "exp": exp}, expire=_JWT_CACHE_TTL)
|
||||
if _JWT_L1_ENABLED:
|
||||
_l1_set(key, orjson.dumps(payload), exp)
|
||||
return payload, exp
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user