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

相关推荐
2401_8877245019 小时前
CSS如何设置文字溢出显示省略号_利用text-overflowellipsis
jvm·数据库·python
m0_7478545219 小时前
golang如何实现应用启动耗时分析_golang应用启动耗时分析实现思路
jvm·数据库·python
解救女汉子19 小时前
如何截断SQL小数位数_使用TRUNCATE函数控制精度
jvm·数据库·python
2301_8038756119 小时前
如何用 objectStore.get 根据主键 ID 获取数据库单条数据
jvm·数据库·python
耿雨飞19 小时前
Python 后端开发技术博客专栏 | 第 06 篇 描述符与属性管理 -- 理解 Python 属性访问的底层机制
开发语言·python
weixin_4585801219 小时前
如何修改AWR保留时间_将默认8天保留期延长至30天的设置
jvm·数据库·python
耿雨飞20 小时前
Python 后端开发技术博客专栏 | 第 08 篇 上下文管理器与类型系统 -- 资源管理与代码健壮性
开发语言·python
qq_6543669820 小时前
C#怎么实现OAuth2.0授权_C#如何对接第三方快捷登录【核心】
jvm·数据库·python
justjinji20 小时前
如何用 CSS 变量配合 JS setProperty 实现动态换肤功能
jvm·数据库·python
老王以为20 小时前
前端重生之 - 前端视角下的 Python
前端·后端·python