Skip to content

Commit ea6e654

Browse files
perf: Fastapi cache lock
1 parent db28ad8 commit ea6e654

File tree

1 file changed

+39
-51
lines changed

1 file changed

+39
-51
lines changed
Lines changed: 39 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
from fastapi_cache import FastAPICache
22
from fastapi_cache.decorator import cache as original_cache
33
from functools import partial, wraps
4-
from typing import Optional, Set, Any, Dict, Tuple
4+
from typing import Optional, Set, Any, Dict, Tuple, Callable
55
from inspect import Parameter, signature
66
import logging
7+
from contextlib import asynccontextmanager
8+
import asyncio
79

810
logger = logging.getLogger(__name__)
911

12+
# 锁管理
13+
_cache_locks = {}
14+
15+
@asynccontextmanager
16+
async def _get_cache_lock(key: str):
17+
lock = _cache_locks.setdefault(key, asyncio.Lock())
18+
async with lock:
19+
try:
20+
yield
21+
finally:
22+
if key in _cache_locks and not lock.locked():
23+
del _cache_locks[key]
24+
1025
def should_skip_param(param: Parameter) -> bool:
1126
"""判断参数是否应该被忽略(依赖注入参数)"""
1227
return (
@@ -26,9 +41,7 @@ def custom_key_builder(
2641
cacheName: Optional[str] = None,
2742
keyExpression: Optional[str] = None,
2843
) -> str:
29-
"""
30-
完全兼容FastAPICache的键生成器
31-
"""
44+
"""完全兼容FastAPICache的键生成器"""
3245
if cacheName:
3346
base_key = f"{namespace}:{cacheName}:"
3447

@@ -38,7 +51,6 @@ def custom_key_builder(
3851
bound_args = sig.bind_partial(*args, **kwargs)
3952
bound_args.apply_defaults()
4053

41-
4254
if keyExpression.startswith("args["):
4355
import re
4456
match = re.match(r"args\[(\d+)\]", keyExpression)
@@ -47,7 +59,6 @@ def custom_key_builder(
4759
value = bound_args.args[index]
4860
base_key += f"{value}:"
4961
else:
50-
5162
parts = keyExpression.split('.')
5263
value = bound_args.arguments[parts[0]]
5364
for part in parts[1:]:
@@ -58,37 +69,27 @@ def custom_key_builder(
5869
logger.warning(f"Failed to evaluate keyExpression '{keyExpression}': {str(e)}")
5970

6071
return base_key
61-
# 获取函数签名
62-
sig = signature(func)
6372

64-
# 自动识别要跳过的参数
73+
sig = signature(func)
6574
auto_skip_args = {
6675
name for name, param in sig.parameters.items()
6776
if should_skip_param(param)
6877
}
69-
70-
# 合并用户指定的额外跳过参数
7178
skip_args = auto_skip_args.union(additional_skip_args or set())
72-
73-
# 过滤kwargs
7479
filtered_kwargs = {
7580
k: v for k, v in kwargs.items() if k not in skip_args
7681
}
7782

78-
# 过滤args - 将位置参数映射到它们的参数名
7983
bound_args = sig.bind_partial(*args, **kwargs)
8084
bound_args.apply_defaults()
8185

8286
filtered_args = []
8387
for i, (name, value) in enumerate(bound_args.arguments.items()):
84-
# 只处理位置参数 (在args中的参数)
8588
if i < len(args) and name not in skip_args:
8689
filtered_args.append(value)
8790
filtered_args = tuple(filtered_args)
8891

89-
# 获取默认键生成器
9092
default_key_builder = FastAPICache.get_key_builder()
91-
# 调用默认键生成器(严格按照其要求的参数格式)
9293
return default_key_builder(
9394
func=func,
9495
namespace=namespace,
@@ -105,9 +106,7 @@ def cache(
105106
cacheName: Optional[str] = None,
106107
keyExpression: Optional[str] = None,
107108
):
108-
"""
109-
完全兼容的缓存装饰器
110-
"""
109+
"""完全兼容的缓存装饰器"""
111110
def decorator(func):
112111
if key_builder is None:
113112
used_key_builder = partial(
@@ -121,24 +120,23 @@ def decorator(func):
121120

122121
@wraps(func)
123122
async def wrapper(*args, **kwargs):
124-
# 准备键生成器参数
125-
key_builder_args = {
126-
"func": func,
127-
"namespace": namespace,
128-
"args": args,
129-
"kwargs": kwargs
130-
}
131-
132-
# 生成缓存键
133-
cache_key = used_key_builder(**key_builder_args)
134-
logger.debug(f"Generated cache key: {cache_key}")
123+
cache_key = used_key_builder(
124+
func=func,
125+
namespace=namespace or "",
126+
args=args,
127+
kwargs=kwargs
128+
)
135129

136-
# 使用原始缓存装饰器
137-
return await original_cache(
138-
expire=expire,
139-
namespace=namespace,
140-
key_builder=lambda *_, **__: cache_key # 直接使用预生成的key
141-
)(func)(*args, **kwargs)
130+
async with _get_cache_lock(cache_key):
131+
backend = FastAPICache.get_backend()
132+
cached_value = await backend.get(cache_key)
133+
if cached_value is not None:
134+
return cached_value
135+
136+
result = await func(*args, **kwargs)
137+
await backend.set(cache_key, result, expire)
138+
return result
139+
142140
return wrapper
143141
return decorator
144142

@@ -147,17 +145,10 @@ def clear_cache(
147145
cacheName: Optional[str] = None,
148146
keyExpression: Optional[str] = None,
149147
):
150-
"""
151-
清除缓存的装饰器,参数与 @cache 保持一致
152-
使用方式:
153-
@clear_cache(namespace="user", cacheName="info", keyExpression="user_id")
154-
async def update_user(user_id: int):
155-
...
156-
"""
148+
"""清除缓存的装饰器"""
157149
def decorator(func):
158150
@wraps(func)
159151
async def wrapper(*args, **kwargs):
160-
# 1. 生成缓存键(复用 custom_key_builder 逻辑)
161152
cache_key = custom_key_builder(
162153
func=func,
163154
namespace=namespace or "",
@@ -167,13 +158,10 @@ async def wrapper(*args, **kwargs):
167158
keyExpression=keyExpression,
168159
)
169160

170-
logger.debug(f"Clearing cache for key: {cache_key}")
171-
# 2. 清除缓存
172-
if await FastAPICache.get_backend().get(cache_key):
161+
async with _get_cache_lock(cache_key):
173162
await FastAPICache.clear(key=cache_key)
174-
175-
# 3. 执行原函数
176-
return await func(*args, **kwargs)
163+
result = await func(*args, **kwargs)
164+
return result
177165

178166
return wrapper
179167
return decorator

0 commit comments

Comments
 (0)