大数据服务<FastApi>:30秒内防止同维度重复下载的功能

1. 项目背景

        在大数据服务接口面临大量数据查询和下载得时候,我们为了控制服务得资源占用,要考虑防止同维度重复下载得功能。

        我这里得背景是 业务方下载大量明细数据得时候,需要控制每个处理人、所属经营主体、所属门店、所查询得时间窗口 得下载频率。

2. 技术背景

我使用的架构是 FastApi

在FastAPI路由处理函数中,需要添加一个30秒内防止同维度重复下载的功能。

我当前的代码已经使用了BackgroundTasks来启动后台任务,但希望在出现重复请求时返回错误并且给出提示,而不是重复执行任务。

原本的代码:

# 生成下载清单接口
@router.post("/xxxx")
async def create_cloth_state_download_list(cloth_state_model: ClothStateConfigModel, background_tasks: BackgroundTasks):
    try:
        # 使用 BackgroundTasks 在后台运行下载任务
        background_tasks.add_task(ttDownloadClothStateTimeService, cloth_state_model)
        return {"code": 200, "message": "下载任务已在后台启动"}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

3. 实现思路

通过 内存缓存+时间戳标记 实现

  1. 维度标识:提取 cloth_state_model 中的关键字段(如经营主体/处理人/时间)作为唯一标识
  2. 内存缓存:用字典缓存最近 30 秒的请求标识和时间戳
  3. 并发安全:使用 asyncio.Lock 防止多线程并发问题
  4. 自动清理:每次请求时自动清理过期缓存

 3.1. 关键设计解析

pydantic模型: 此模型是用户筛选数据的条件维度

class ClothStateConfigModel(BaseModel):
    date_min: str  # 筛选时间
    date_max: str
    is_directly: str
    branch_office: str  # 城市
    shop_name: str
    pageNum: int
    pageSize: int
    apply_operator_name: str  # 处理人 名称
    cloth_type: str  # 衣物大类 20250225加入

公共类声明:

我们把用户筛选的条件存入缓存

# 内存缓存结构:{ "branch_office:xxx|apply_operator_name:yyy|date_min:zzz"|date_max:zzz": 时间戳 }
download_cache = {}
cache_lock = asyncio.Lock()  # 异步锁 安装asyncio
CACHE_TIMEOUT = 30  # 单位:秒
  1. 维度标识生成

    • 通过 generate_cache_key() 提取模型中的关键业务字段(示例为经营主体/处理人/时间)
    • 可根据实际业务需求调整字段组合
    • def generate_cache_key(model: ClothStateConfigModel) -> str:
          """生成唯一缓存标识(根据业务维度自定义)"""
          return f"branch_office:{model.branch_office}|apply_operator_name:{model.apply_operator_name}|date_min:{model.date_min}|date_max:{model.date_max}"
      
  2. 缓存自动清理

    • 每次新请求到达时,清理所有超过30秒的旧缓存
    • 避免内存无限制增长
    •         # 清理过期缓存(每次有新请求时清理)
              expired_keys = [
                  k for k, t in download_cache.items()
                  if current_time - t > CACHE_TIMEOUT
              ]
              for k in expired_keys:
                  del download_cache[k]
      

  3. 异步锁保护

    • 使用 asyncio.Lock 确保多线程下对缓存的修改是原子操作
    • 防止并发导致缓存状态错误
    •     async with cache_lock:  # 加锁保证线程安全
              # 清理过期缓存(每次有新请求时清理)
              expired_keys = [
                  k for k, t in download_cache.items()
                  if current_time - t > CACHE_TIMEOUT
              ]
              for k in expired_keys:
                  del download_cache[k]
      
              # 检查是否已有重复请求
              if cache_key in download_cache:
                  # 计算剩余冷却时间
                  remaining_time = CACHE_TIMEOUT - (current_time - download_cache[cache_key])
                  raise HTTPException(
                      status_code=429,
                      detail=f"30秒内请勿重复请求相同维度下载,剩余等待时间: {int(remaining_time)}秒"
      
                  )
              # 记录新请求
              download_cache[cache_key] = current_time

  4. 精准错误提示

    • 当检测到重复请求时,返回 429 状态码(Too Many Requests)
    • 提示用户剩余等待时间
    •         # 失败时移除缓存
              async with cache_lock:
                  if cache_key in download_cache:
                      del download_cache[cache_key]

4. 拓展模块

  1. Redis缓存
    高并发场景下,可将缓存迁移到 Redis,实现多实例共享和自动过期

    # 示例:使用 Redis 的 EXPIRE 命令 
    
    await redis.setex(cache_key, CACHE_TIMEOUT, current_time)

  2. 日志监控
    记录重复请求日志,用于后续分析

    logger.warning(f"重复请求被拦截: {cache_key}")

  3. 动态超时配置
    通过环境变量动态调整超时时间

    CACHE_TIMEOUT = int(os.getenv("DOWNLOAD_CACHE_TIMEOUT", 30))

通过这种设计,既能有效防止短时间内的重复请求,又能保持接口的响应速度和可维护性。

欢迎大家指出可优化的地方


    5. 完整代码

    import asyncio as asyncio
    from pydantic import BaseModel
    class ClothStateConfigModel(BaseModel):
        date_min: str
        date_max: str
        is_directly: str
        branch_office: str
        shop_name: str
        pageNum: int
        pageSize: int
        apply_operator_name: str  # 处理人 名称
        cloth_type: str  # 衣物大类 20250225加入
    
    # 内存缓存结构:{ "branch_office:xxx|apply_operator_name:yyy|date_min:zzz"|date_max:zzz": 时间戳 }
    download_cache = {}
    cache_lock = asyncio.Lock()  # 异步锁 安装asyncio
    CACHE_TIMEOUT = 30  # 单位:秒
    
    
    def generate_cache_key(model: ClothStateConfigModel) -> str:
        """生成唯一缓存标识(根据业务维度自定义)"""
        return f"branch_office:{model.branch_office}|apply_operator_name:{model.apply_operator_name}|date_min:{model.date_min}|date_max:{model.date_max}"
    
    
    # 生成下载清单接口
    @router.post("/createClothStateDownloadList")
    async def create_cloth_state_download_list(cloth_state_model: ClothStateConfigModel, background_tasks: BackgroundTasks):
        cache_key = generate_cache_key(cloth_state_model)
        current_time = time.time()
    
        async with cache_lock:  # 加锁保证线程安全
            # 清理过期缓存(每次有新请求时清理)
            expired_keys = [
                k for k, t in download_cache.items()
                if current_time - t > CACHE_TIMEOUT
            ]
            for k in expired_keys:
                del download_cache[k]
    
            # 检查是否已有重复请求
            if cache_key in download_cache:
                # 计算剩余冷却时间
                remaining_time = CACHE_TIMEOUT - (current_time - download_cache[cache_key])
                raise HTTPException(
                    status_code=429,
                    detail=f"30秒内请勿重复请求相同维度下载,剩余等待时间: {int(remaining_time)}秒"
    
                )
    
            # 记录新请求
            download_cache[cache_key] = current_time
    
        try:
            # 使用 BackgroundTasks 在后台运行下载任务
            background_tasks.add_task(ttDownloadClothStateTimeService, cloth_state_model)
    
            return {"code": 200, "message": "下载任务已在后台启动"}
        except Exception as e:
            # 失败时移除缓存
            async with cache_lock:
                if cache_key in download_cache:
                    del download_cache[cache_key]
            raise HTTPException(status_code=500, detail=str(e))

    你可能感兴趣的:(python,fastapi,python,big,data,后端)