BackgroundTasks 还是 RabbitMQ?你的异步任务到底该选谁?

cmdragon_cn.png cmdragon_cn.png

扫描二维码关注或者微信搜一搜:编程智域 前端至全栈交流与成长

发现1000+提升效率与开发的AI工具和实用程序https://tools.cmdragon.cn/

1. BackgroundTasks 核心机制

1.1 基础用法与实现原理

FastAPI 的 BackgroundTasks 本质是 Starlette 框架的异步任务队列实现,通过以下方式注册任务:

from fastapi import BackgroundTasks, FastAPI

app = FastAPI()

def log_operation(email: str, message: str):
    with open("operation.log", "a") as f:
        f.write(f"{email}: {message}\n")

@app.post("/notify")
async def send_notification(
    email: str, 
    bg_tasks: BackgroundTasks
):
    bg_tasks.add_task(log_operation, email, "notification sent")
    return {"message": "Processing in background"}

技术特点:

  • 主线程响应后顺序执行任务
  • 适用轻量级任务(执行时间 < 3秒)
  • 单进程内任务队列

1.2 应用场景对比

场景类型 适用方案 执行时间 可靠性要求
日志记录 BackgroundTasks <1秒
邮件发送 BackgroundTasks <3秒
图片处理 RabbitMQ + Celery >5秒
数据分析 RabbitMQ 分钟级
graph TD A[客户端请求] --> B[FastAPI路由] B --> C{同步处理} C --> D[立即返回响应] C --> E[创建BackgroundTask] E --> F[Starlette任务队列] F --> G[异步执行任务] G --> H[数据库操作/外部API调用等]

2. RabbitMQ 集成方案

2.1 环境配置

安装依赖:

pip install fastapi==0.109.0 pika==1.3.2 pydantic==2.6.4

2.2 消息队列配置

import pika

class MQConfig:
    HOST = 'localhost'
    PORT = 5672
    QUEUE = 'fastapi_tasks'
    CREDENTIALS = pika.PlainCredentials('user', 'password')

def get_channel():
    connection = pika.BlockingConnection(
        pika.ConnectionParameters(
            host=MQConfig.HOST,
            port=MQConfig.PORT,
            credentials=MQConfig.CREDENTIALS
        )
    )
    return connection.channel()

2.3 任务模型设计

from pydantic import BaseModel
from datetime import datetime

class TaskPayload(BaseModel):
    task_type: str
    content: dict
    created_at: datetime = datetime.now()
    retry_count: int = 0

2.4 生产者实现

from fastapi import APIRouter

task_router = APIRouter()

@task_router.post("/create_task")
def create_task(payload: TaskPayload):
    channel = get_channel()
    channel.queue_declare(queue=MQConfig.QUEUE, durable=True)
    channel.basic_publish(
        exchange='',
        routing_key=MQConfig.QUEUE,
        body=payload.json().encode(),
        properties=pika.BasicProperties(
            delivery_mode=2  # 持久化消息
        )
    )
    return {"status": "queued"}

2.5 消费者实现

import json
import threading

def start_consumer():
    def callback(ch, method, properties, body):
        try:
            task = TaskPayload.parse_raw(body)
            process_task(task)
            ch.basic_ack(delivery_tag=method.delivery_tag)
        except Exception as e:
            handle_failure(task, str(e))

    channel = get_channel()
    channel.basic_qos(prefetch_count=1)
    channel.basic_consume(
        queue=MQConfig.QUEUE,
        on_message_callback=callback
    )
    threading.Thread(target=channel.start_consuming).start()

3. 综合应用案例

图片水印处理系统实现:

# 路由层
@task_router.post("/upload_image")
async def upload_image(
    image: UploadFile, 
    bg_tasks: BackgroundTasks
):
    # 立即响应
    temp_path = save_temp_image(image)
    
    # 创建异步任务
    payload = TaskPayload(
        task_type="watermark",
        content={"path": temp_path}
    )
    
    # 轻量级任务用BackgroundTasks
    bg_tasks.add_task(queue_watermark_task, payload)
    
    return {"status": "processing"}

def queue_watermark_task(payload: TaskPayload):
    # 将任务提交到RabbitMQ
    channel = get_channel()
    channel.basic_publish(
        exchange='',
        routing_key=MQConfig.QUEUE,
        body=payload.json()
    )

4. 课后 Quiz

问题1:当需要保证任务不丢失时,应该配置哪些关键参数?

答案解析

  • 消息持久化:设置 delivery_mode=2
  • 队列声明 durable=True
  • 消费者手动确认(basic_ack)
  • 实现重试机制(通过 retry_count 字段)

问题2:什么情况下必须使用消息队列替代 BackgroundTasks?

参考答案

  1. 任务执行时间超过 5 秒
  2. 需要跨进程/服务器执行
  3. 要求任务持久化和重试能力
  4. 高并发场景下的负载均衡需求

5. 常见报错解决方案

报错1:pika.exceptions.AMQPConnectionError

# 解决方案
检查RabbitMQ服务状态:
sudo systemctl status rabbitmq-server

配置连接参数:
credentials = pika.PlainCredentials('正确用户名', '正确密码')

报错2:ValidationError 422

{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "task_type"],
      "msg": "Field required"
    }
  ]
}

产生原因:

  • 请求体缺少 task_type 字段
  • 字段类型不匹配

解决方法:

  1. 使用 Pydantic 模型校验请求
  2. 启用严格模式:
class TaskPayload(BaseModel):
    model_config = ConfigDict(extra='forbid')

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长,阅读完整的文章:BackgroundTasks 还是 RabbitMQ?你的异步任务到底该选谁?

往期文章归档和免费好用的热门在线工具

往期文章归档
免费好用的热门在线工具
posted @ 2025-08-07 11:16  Amd794  阅读(12)  评论(0)    收藏  举报