缓存装饰器

@cached 装饰器是 fn_cache 的核心功能,它允许您轻松地为任何函数添加缓存能力。

🎯 基本概念

缓存装饰器的工作原理:

  1. 函数调用拦截:装饰器拦截对被装饰函数的调用

  2. 缓存键生成:根据函数参数生成唯一的缓存键

  3. 缓存查找:检查缓存中是否存在对应的值

  4. 缓存命中:如果存在且未过期,直接返回缓存值

  5. 缓存未命中:如果不存在或已过期,执行原函数并缓存结果

📝 基本用法

最简单的用法

from fn_cache import cached

@cached(ttl_seconds=300)  # 缓存5分钟
def get_user_data(user_id: int):
    # 模拟数据库查询
    return {"user_id": user_id, "name": f"用户_{user_id}"}

异步函数支持

@cached(ttl_seconds=300)
async def fetch_user_data(user_id: int):
    # 模拟异步数据库查询
    await asyncio.sleep(1)
    return {"user_id": user_id, "name": f"用户_{user_id}"}

⚙️ 配置参数

缓存策略配置

TTL 缓存(默认)

from fn_cache import cached, CacheType

@cached(
    cache_type=CacheType.TTL,
    ttl_seconds=600  # 10分钟后过期
)
def get_temporary_data(key: str):
    return f"临时数据_{key}"

LRU 缓存

@cached(
    cache_type=CacheType.LRU,
    max_size=1000  # 最多缓存1000个条目
)
def get_cached_data(key: str):
    return f"数据_{key}"

存储后端配置

内存存储(默认)

from fn_cache import cached, StorageType

@cached(
    storage_type=StorageType.MEMORY,
    ttl_seconds=300
)
def get_memory_cached_data(key: str):
    return f"内存缓存数据_{key}"

Redis 存储

@cached(
    storage_type=StorageType.REDIS,
    ttl_seconds=3600,  # 1小时
    prefix="myapp:"  # 自定义前缀
)
def get_redis_cached_data(key: str):
    return f"Redis缓存数据_{key}"

序列化配置

from fn_cache import cached, SerializerType

# JSON 序列化(默认)
@cached(
    serializer_type=SerializerType.JSON,
    ttl_seconds=300
)
def get_json_data(key: str):
    return {"key": key, "value": f"值_{key}"}

# Pickle 序列化(支持复杂对象)
@cached(
    serializer_type=SerializerType.PICKLE,
    ttl_seconds=300
)
def get_complex_data(key: str):
    return ComplexObject(key)

# MessagePack 序列化(高效)
@cached(
    serializer_type=SerializerType.MESSAGEPACK,
    ttl_seconds=300
)
def get_efficient_data(key: str):
    return {"key": key, "data": [1, 2, 3, 4, 5]}

🔑 缓存键配置

默认缓存键生成

默认情况下,fn_cache 会根据函数名和所有参数生成缓存键:

@cached(ttl_seconds=300)
def get_user_info(user_id: int, include_profile: bool = True):
    return {"user_id": user_id, "profile": include_profile}

# 缓存键格式:fn_cache:get_user_info:123:True

自定义缓存键函数

@cached(
    ttl_seconds=300,
    key_func=lambda *args, **kwargs: f"user:{args[0]}:{kwargs.get('version', 'v1')}"
)
def get_user_data(user_id: int, version: str = "v1"):
    return {"user_id": user_id, "version": version}

# 缓存键格式:user:123:v1

使用缓存键枚举

from fn_cache import CacheKeyEnum

class UserCacheKeys(CacheKeyEnum):
    USER_PROFILE = "user:{user_id}:profile"
    USER_SETTINGS = "user:{user_id}:settings:{setting_type}"

@cached(
    ttl_seconds=300,
    key_func=lambda *args, **kwargs: UserCacheKeys.USER_PROFILE.format(user_id=args[0])
)
def get_user_profile(user_id: int):
    return {"user_id": user_id, "profile": "用户资料"}

🎛️ 高级配置

动态过期时间

def dynamic_ttl(result):
    """根据结果动态计算过期时间"""
    if result.get("is_vip"):
        return 3600  # VIP用户缓存1小时
    else:
        return 300   # 普通用户缓存5分钟

@cached(
    ttl_seconds=300,
    make_expire_sec_func=dynamic_ttl
)
def get_user_info(user_id: int):
    is_vip = user_id % 3 == 0  # 模拟VIP判断
    return {"user_id": user_id, "is_vip": is_vip}

缓存预热

def user_ids_provider():
    """提供需要预加载的用户ID"""
    return [(user_id,) for user_id in [1, 2, 3, 4, 5]]

@cached(
    ttl_seconds=3600,
    preload_provider=user_ids_provider
)
def get_user_name(user_id: int):
    return f"用户_{user_id}"

# 在应用启动时预加载
async def startup():
    from fn_cache import preload_all_caches
    await preload_all_caches()

自定义前缀

@cached(
    ttl_seconds=300,
    prefix="myapp:users:"
)
def get_user_data(user_id: int):
    return {"user_id": user_id, "name": f"用户_{user_id}"}

# 缓存键格式:myapp:users:get_user_data:123

🔧 装饰器方法

装饰后的函数具有一些额外的方法:

@cached(ttl_seconds=300)
def cached_function(x: int):
    return x * 2

# 获取缓存管理器
manager = cached_function.cache

# 清除该函数的缓存
cached_function.cache.clear()

# 获取缓存统计
stats = cached_function.cache.get_statistics()

🚫 缓存控制

跳过缓存读取

@cached(ttl_seconds=300)
def get_data(key: str):
    return f"数据_{key}"

# 正常使用缓存
result1 = get_data("test")

# 跳过缓存读取,强制执行函数
result2 = get_data("test", cache_read=False)

跳过缓存写入

# 跳过缓存写入,不缓存结果
result = get_data("test", cache_write=False)

等待缓存写入

# 异步函数中等待缓存写入完成
result = await get_data("test", wait_for_write=True)

📊 性能监控

启用统计

@cached(
    ttl_seconds=300,
    enable_statistics=True  # 默认启用
)
def monitored_function(x: int):
    return x * x

# 获取统计信息
from fn_cache import get_cache_statistics
stats = get_cache_statistics()

内存监控

@cached(
    ttl_seconds=300,
    enable_memory_monitoring=True  # 默认启用
)
def memory_monitored_function(x: int):
    return x * x

# 启动内存监控
from fn_cache import start_cache_memory_monitoring
start_cache_memory_monitoring(interval_seconds=300)

🔄 版本控制

全局版本控制

from fn_cache import invalidate_all_caches

@cached(ttl_seconds=3600)
def get_config_data():
    return {"version": "1.0", "settings": {...}}

# 使所有缓存失效
await invalidate_all_caches()

用户级版本控制

from fn_cache import UniversalCacheManager

manager = UniversalCacheManager()

# 递增用户版本号
await manager.increment_user_version("user123")

# 使用户的所有缓存失效
await manager.invalidate_user_cache("user123")

🎯 最佳实践

1. 选择合适的缓存策略

  • TTL 缓存:适用于数据有明确过期时间的场景

  • LRU 缓存:适用于内存有限,需要自动淘汰的场景

2. 合理设置缓存时间

# 静态数据 - 长时间缓存
@cached(ttl_seconds=86400)  # 24小时
def get_static_config():
    return {"app_name": "MyApp", "version": "1.0"}

# 动态数据 - 短时间缓存
@cached(ttl_seconds=60)  # 1分钟
def get_user_status(user_id: int):
    return {"user_id": user_id, "online": True}

3. 使用有意义的缓存键

# 好的做法
@cached(
    key_func=lambda *args, **kwargs: f"user:{args[0]}:profile"
)
def get_user_profile(user_id: int):
    return {"user_id": user_id, "profile": "..."}

# 避免的做法
@cached()  # 使用默认键,可能不够清晰
def get_user_profile(user_id: int):
    return {"user_id": user_id, "profile": "..."}

4. 处理缓存异常

@cached(ttl_seconds=300)
def get_data_with_fallback(key: str):
    try:
        # 尝试从缓存获取
        return get_data_from_cache(key)
    except Exception:
        # 缓存失败时的降级处理
        return get_data_from_source(key)

5. 监控缓存性能

# 定期检查缓存命中率
import asyncio
from fn_cache import get_cache_statistics

async def monitor_cache():
    while True:
        stats = get_cache_statistics()
        for cache_id, cache_stats in stats.items():
            hit_rate = cache_stats['hit_rate']
            if hit_rate < 0.5:  # 命中率低于50%
                print(f"警告: {cache_id} 缓存命中率过低: {hit_rate:.2%}")
        await asyncio.sleep(300)  # 每5分钟检查一次

🐛 常见问题

1. 缓存键冲突

问题:不同函数使用相同的缓存键 解决:使用自定义前缀或缓存键函数

@cached(prefix="function1:")
def function1(x: int):
    return x * 2

@cached(prefix="function2:")
def function2(x: int):
    return x * 3

2. 缓存穿透

问题:大量请求查询不存在的数据 解决:使用空值缓存

@cached(ttl_seconds=60)
def get_user_data(user_id: int):
    data = query_database(user_id)
    if data is None:
        # 缓存空值,避免缓存穿透
        return {"user_id": user_id, "exists": False}
    return data

3. 缓存雪崩

问题:大量缓存同时过期 解决:使用随机过期时间

import random

@cached(
    ttl_seconds=lambda: 300 + random.randint(0, 60)  # 300-360秒随机过期
)
def get_data(key: str):
    return f"数据_{key}"

📚 相关链接