FastAPI + Celery 实战:异步任务的坑与解法,我帮你踩了一遍

你写了个 FastAPI 接口,用户一请求,得等好几秒才返回,是不是急得想摔键盘?😤 别急着换电脑,八成是你把"耗时任务"堵在了接口里。

今天咱们就聊聊怎么用 Celery 这个老牌异步神器,给 FastAPI 提提速,顺便把我当年踩过的那些坑(比如循环导入、worker 启动失败)一次性说清楚。

🎯 核心脉络

▶️ 问题场景:接口里跑耗时任务,用户干等

▶️ 核心原理:Celery 是啥?为什么用它?

▶️ 实战演示:安装、配置、写任务、结合 FastAPI

▶️ 常见坑:循环导入、worker 找不到 app、Redis 连接数爆炸

▶️ 进阶思考:生产环境还要加点啥

💥 一、痛点:你的接口为什么这么慢?

有天你写了个 API,用户传个文件上来,你要做压缩、加水印、还要发邮件通知。一套下来可能 5 秒起步。这时候用户直接卡在"加载中"......

你可能会说:"加个 async def 不就行了?" 哎,单纯的 async/await 只能解决 IO 等待,治不了 CPU 密集型的活 。这时候就需要一个真正的"后台打工仔"------Celery。

🧠 二、Celery 是个啥?用个比喻你就懂了

你把 FastAPI 想象成餐厅的前台点菜员,用户一来,点菜员记下菜单。

但做菜太慢,不能让客人站在前台等。所以 Celery 就是后厨的厨师团队:你丢个任务进去(比如"做一份宫保鸡丁"),厨师慢慢做,做完放传菜口。

前台可以马上给客人一个"你的菜在做啦"的号牌,过会儿凭号牌来取。

中间还得有个**"订单板"** ------那就是 消息队列(Redis 或 RabbitMQ)。前台把任务写上去,厨师从上面拿。是不是瞬间清晰了?

🛠️ 三、实战:从零搭一套 FastAPI + Celery

好,咱们动手。这里千万别学我当初偷懒跳过环境准备,直接 pip install 就跑,后面一堆版本冲突的坑。

📦 1. 安装依赖

打开终端,一条龙装好:

复制代码
uv add fastapi uvicorn celery redis

我用 Redis 做 broker(消息队列),你如果喜欢 RabbitMQ 也行,但 Redis 更轻量,本地调试方便。

🏗️ 2. 目录结构(血的教训)

复制代码
project/
├── app.py # FastAPI 应用
├── tasks.py # Celery 任务定义
├── config.py # 配置(别写死在代码里)
└── pyproject.toml

重要的事说三遍:不要把 Celery 实例和 FastAPI app 写在同一个文件里! 不然循环导入会教你做人。

📝 3. 写配置文件 config.py

复制代码
REDIS_URL = "redis://localhost:6379/0"

🧵 4. 定义 Celery 任务 tasks.py

复制代码
from celery import Celery
from config import REDIS_URL

celery_app = Celery(
    "myapp",
    broker=REDIS_URL,
    backend=REDIS_URL
)

@celery_app.task
def add(a, b):
    import time
    time.sleep(5) # 模拟耗时计算
    return a + b

这里注意:celery_app 是独立的,别引用 FastAPI 的任何东西,否则后面 worker 启动会一脸懵。

🚀 5. FastAPI 中调用任务 app.py

复制代码
from fastapi import FastAPI
from tasks import add

app = FastAPI()

@app.get("/sum")
async def compute(x: int, y: int):
    # 异步提交任务,立刻返回任务ID
    task = add.delay(x, y)
    return {"task_id": task.id, "status": "processing"}

@app.get("/result/{task_id}")
async def get_result(task_id: str):
    from celery.result import AsyncResult
    from tasks import celery_app
    
    task = AsyncResult(task_id, app=celery_app)
    if task.ready():
        return {"status": "completed", "result": task.result}

    return {"status": "pending"}

你看,用户请求 /sum?x=3&y=5 会立刻得到一个 task_id,然后轮询 /result/{task_id} 就能拿到结果。再也不怕接口超时了!

⚠️ 四、你一定会踩的几个坑(我先帮你踩了)

🔁 坑1:循环导入

如果你在 tasks.py 里导入了 FastAPI 的 app,或者在 app.py 里又导入任务...... 恭喜你,获得一个 ImportError。

解法:保持单向依赖,任务里不要依赖 app。

🧑‍🍳 坑2:worker 启动失败

启动命令 celery -A tasks worker --loglevel=info ,注意 -A tasks 是指 tasks.py 里的 celery_app 变量。

官方文档说默认找名为 celery 的变量,但咱们叫 celery_app ,就得显式指定。我当初忘了,报错"KeyError: 'celery'",原地抓狂半小时。

🔌 坑3:Redis 连接数爆炸

Celery 默认会开很多连接,尤其生产环境。记得在配置里加上 broker_pool_limit = 10,否则 Redis 连接数飙升,最后拒绝服务。

🔮 五、进阶思考:生产环境还能怎么玩?

你以为这样就完了?Celery 还有定时任务(beat),比如每天凌晨三点清理数据库。另外,任务失败了怎么办?可以配置重试:

复制代码
@celery_app.task(bind=True, max_retries=3)
def unstable_task(self):
    try:
        # 可能失败的操作
    except Exception as e:
        self.retry(exc=e, countdown=60)

还有监控工具 Flower ,跑起来就能看任务队列、成功失败数量,像逛淘宝一样方便。命令:celery -A tasks flower ,然后浏览器打开 localhost:5555。


好了,今天这篇算是把我用 FastAPI + Celery 从入门到"被坑"再到"平坑"的心里话都倒出来了。你写代码的时候如果遇到其他奇葩报错,欢迎在评论区甩过来,我帮你看看。觉得有用的话,点个关注 或者收藏,免得下次想用翻不到。毕竟... 掉坑的记忆总会模糊,但收藏夹里的文章不会跑。😄

相关推荐
Cooper2542 分钟前
DDC 命令成功但屏幕不动:用 gamma 表重做亮度映射
python·mac
师夷长技以自愈1 小时前
手搓 AI Agent 第 7 篇:菜做砸了别硬炒——错误分类 + 双账本熔断
人工智能·python
用户956256254291 小时前
15 分钟社媒简报 Agent:我只用了搜索和模型两步
python·api
Dumbness1 小时前
关键词回复里写了 {订单号},群机器人整条消息都炸了
python·微信
调接口的阿光1 小时前
调用对话接口时输出被截断怎么办:先看 finish_reason,再决定是续写一次、缩小范围,还是换成更短的格式(Python)
人工智能·python
Cooper252 小时前
单文件 Flask 跑通微信服务器校验:先跑通再谈工程化
python·flask
2501_933923252 小时前
redis:string类型常见命令
数据库·redis·缓存
Cooper252 小时前
三源热榜去重加 AI 摘要:URL 唯一键踩出的三个坑
前端·python
用户4019925568612 小时前
多源异构 B2B 公开需求信号去重与企业实体证据链存储设计(64-bit SimHash 分桶 + Jaccard)
python
黄连木笛2 小时前
不改 Redis Insight 原有代码,把它汉化了:DOM 整段匹配 + XHR 拦截 + asar 重打包
redis·后端