惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News | PayPal Newsroom
P
Proofpoint News Feed
Cyberwarzone
Cyberwarzone
C
Cisco Blogs
SecWiki News
SecWiki News
Know Your Adversary
Know Your Adversary
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Vercel News
Vercel News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
NISL@THU
NISL@THU
WordPress大学
WordPress大学
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Threat Research - Cisco Blogs
AI
AI
Simon Willison's Weblog
Simon Willison's Weblog
Security Archives - TechRepublic
Security Archives - TechRepublic
有赞技术团队
有赞技术团队
L
LINUX DO - 热门话题
Hacker News: Ask HN
Hacker News: Ask HN
V
V2EX
G
GRAHAM CLULEY
TaoSecurity Blog
TaoSecurity Blog
Hugging Face - Blog
Hugging Face - Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
F
Fortinet All Blogs
博客园 - 叶小钗
博客园 - 三生石上(FineUI控件)
云风的 BLOG
云风的 BLOG
Recorded Future
Recorded Future
Latest news
Latest news
The Hacker News
The Hacker News
aimingoo的专栏
aimingoo的专栏
T
Troy Hunt's Blog
S
Schneier on Security
I
Intezer
Google DeepMind News
Google DeepMind News
A
Arctic Wolf
Apple Machine Learning Research
Apple Machine Learning Research
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threatpost
爱范儿
爱范儿
The Register - Security
The Register - Security
S
SegmentFault 最新的问题
Blog — PlanetScale
Blog — PlanetScale
博客园 - 聂微东
宝玉的分享
宝玉的分享
Recent Commits to openclaw:main
Recent Commits to openclaw:main
美团技术团队
B
Blog RSS Feed

博客园 - 一名程序媛呀

Anki插件开发必知必会:钩子函数与右键菜单定制 httpx 传参总报错?这次把 GET、POST、文件上传到响应处理的坑给你一次填平 从卡顿到丝滑:FastAPI 调用外部 API 的正确姿势(httpx 实战) 还在 XHR、Fetch 和 Axios 之间纠结?我踩过的坑,希望你一个都不用碰到 你的REST接口还在“过度投喂”数据吗?——FastAPI + GraphQL实战避坑指南 Uvicorn、Gunicorn 傻傻分不清?FastAPI 生产部署避坑指南 Termux里的二进制和脚本,到底怎么运行才不踩坑?Termux-service 保活妙招! 刚部署的 LibreTranslate 频频翻车?我掏出了 20 年前的 StarDict 词典,用 FastAPI 搭了个本地词典翻译 API 别再用网页翻译看源码了!你的私人翻译神器LibreTranslate,部署避坑指南来了 掏出手机就能搭个 WebDAV 同步服务器?这操作有点香 别只盯着GitBook了!这个文档神器让你的笔记秒变网站 写爬虫时用了代理还被封?Python 代理的那些隐藏坑,我替你踩明白了 FastAPI 身份验证总踩坑?这份 FastAPI Users “避坑指南”请收好 旧手机别扔!用 Termux 搭个私人云盘,比网盘香多了 你的FastAPI又在服务器上“跑不起来”了?来,今天咱把打包这件事彻底聊透 写页面时别再把 Element Plus 整个搬进来啦!Vue3按需加载的坑我帮你踩平了 前端包管理咋选?我从npm叛逃到pnpm的血泪史(附避坑指南) 聊聊 fetch 使用中我踩过的那些坑和正确打开方式 FastApiAdmin 后端接口开发好了,前端管理界面怎么调用与显示? 给 FastApiAdmin 加个“会议纪要”模块,我把后端二次开发的坑踩了个遍 我用了FastApiAdmin后,连夜把踩过的坑都整理出来了 告别 Typora 后的新欢:我把所有笔记迁移到了 Obsidian 这个“第二大脑” 你的Agent API还在裸奔?从认证到沙箱,我用FastAPI搭了几道防线 让 FastAPI Agent 思考不阻塞:手把手教你实现异步任务与后台处理方案 让FastAPI Agent真正记住你:聊聊会话记忆与持久化存储的落地实践 FastAPI Agent 函数调用实战:我让 AI 学会了“自己动手查天气“ 初探:用 FastAPI 搭建你的第一个 AI Agent 接口 FastAPI 少有人提的实用技巧:把 Depends 依赖提到路由层,代码少写60% FastAPI 生产环境静态文件完全指南:从 /favicon.ico 404 到 HSTS 混合内容,一次全根治 用了loguru我才明白,Python日志还能这么写 FastAPI 后台任务:BackgroundTasks 的使用场景与注意事项 FastAPI配置管理避坑指南:从硬编码到 .env 与 pydantic_settings 类,连路由用法都给你捋清楚 FastAPI 文件上传避坑全指南:分块存盘、类型校验与安全兜底 FastAPI + Pydantic 模型终极实战手册:从能跑就行到固若金汤,这些技巧你一定用得上 FastAPI + SQLAlchemy 2.0 通用CRUD操作手册 —— 从同步到异步,一次讲透 FastAPI订单防超卖实战:从数据库锁到Saga分布式事务,这一篇给你理清了 FastAPI 生产环境避坑指南:用 Alembic 管理数据库迁移,别再手动改表结构了! FastAPI服务半夜又挂了?先别急着重启,查查你的数据库连接池“池子”是不是漏了 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 Vue 3 组合式 API 香是香,但从Vue2迁移时你可别像我当初一样踩进这 3 个深坑里 我用fastapi-scaff搭了个项目,两天工期缩到两小时,老板以为我开挂了 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! FastAPI自动生成的API文档太丑?我花了一晚上把它改成了客户愿意付费的样子 告别手写 API 胶水代码:FastAPI 与 Vue 的“契约自动机” OpenAPI 实战 FastAPI + Vue 前后端分离实战:我的项目结构“避坑指南” FastAPI里玩转Redis和数据库的正确姿势,别让异步任务把你坑哭了! FastAPI + Celery 实战:异步任务的坑与解法,我帮你踩了一遍 FastAPI子应用挂载:别再让root_path坑你一夜 FastAPI项目半夜报警吵醒你?聊聊告警这事儿怎么搞! 别再数据线了!用FastAPI 5分钟搭个局域网文件+剪贴板神器 FastAPI单元测试实战:别等上线被喷才后悔,TestClient用对了真香! FastAPI状态共享秘籍:别再让中间件、依赖和路由“各自为政”了! FastAPI实战:WebSocket vs Socket.IO,这回真给我整明白了! 从0到1,FastAPI + PostgreSQL + Tortoise ORM 实战避坑指南 FastAPI + PostgreSQL 实战:给应用装上“缓存”和“日志”翅膀 FastAPI + PostgreSQL 实战:从入门到不踩坑,一次讲透
FastAPI + Celery 实战:异步任务里调用 Redis 和数据库的全解析,及生产级组织方案
一名程序媛呀 · 2026-04-02 · via 博客园 - 一名程序媛呀

Celery 的任务是在独立的工作进程(worker)中执行的,和 FastAPI 的请求进程是分离的。如果你在 API 路由里通过依赖项注入创建了一个数据库会话,然后把这个会话对象作为参数传给 Celery 任务,会发生什么?

首先,数据库会话(比如 SQLAlchemy 的 Session)通常不是可序列化的,根本传不到 worker 那边。就算你用了某些技巧把它序列化了,worker 那边拿到后,这个会话对应的底层数据库连接可能早已在原始进程中关闭,或者根本不存在。结果就是各种稀奇古怪的报错。

Redis 连接同理,如果你把连接池里“借”出来的连接直接传给 Celery 任务,序列化后到了 worker 端,完全无法使用。

我们需要在 Celery 任务函数内部,重新创建所需的资源(比如新的数据库 Session,新的 Redis 连接),并在任务执行完毕后,确保这些资源被正确关闭或归还

🛠️ 实战:生产级别的组织方案

好,理论前面已经说完了,更多的细节可以看上篇FastAPI里玩转Redis和数据库的正确姿势,别让异步任务把你坑哭了!,这里就直接上重构的代码。

我会展示一个我目前在用的、相对成熟的方案,它基于 Celery 和 SQLAlchemy,并配合 Redis 做状态缓存。

📁 第一步:目录结构

project/
├── app/
│   ├── api/              # 路由层
│   ├── core/             # 核心配置
│   │   ├── database.py   # 数据库引擎、SessionLocal 工厂
│   │   ├── redis_client.py # Redis 连接池
│   │   └── celery_app.py   # Celery 应用实例
│   ├── models/           # 数据库模型
│   ├── schemas/          # Pydantic 模型
│   └── tasks/            # Celery 任务模块 ✨
│       ├── __init__.py
│       └── user_tasks.py
└── ...

💾 第二步:Celery 应用配置

core/celery_app.py 中创建 Celery 实例,并配置 broker 和 backend(通常用 Redis)。

# core/celery_app.py
from celery import Celery
import os

celery_app = Celery(
    "worker",
    broker=os.getenv("CELERY_BROKER_URL", "redis://localhost:6379/0"),
    backend=os.getenv("CELERY_RESULT_BACKEND", "redis://localhost:6379/0"),
    include=["app.tasks.user_tasks"]  # 自动发现任务模块
)

celery_app.conf.update(
    task_serializer="json",
    accept_content=["json"],
    result_serializer="json",
    timezone="Asia/Shanghai",
    enable_utc=True,
    task_track_started=True,
    task_time_limit=30 * 60,      # 任务超时时间
    task_soft_time_limit=25 * 60,
)
  

🔌 第三步:资源管理上下文管理器(重点!)

core/database.pycore/redis_client.py 中定义好工厂和上下文管理器,供 Celery 任务独立使用。

# core/database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from contextlib import contextmanager

engine = create_engine(
    "mysql+pymysql://user:pass@localhost/db",
    pool_size=10,          # 连接池大小
    max_overflow=20,       # 超出 pool_size 最大连接数
    pool_pre_ping=True,    # 自动重连检测
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

@contextmanager
def get_db_session() -> Session:
    """每个 Celery 任务独立创建一个数据库会话,用完即关"""
    db = SessionLocal()
    try:
        yield db
        db.commit()
    except Exception:
        db.rollback()
        raise
    finally:
        db.close()
  
# core/redis_client.py
import redis
from contextlib import contextmanager

redis_pool = redis.ConnectionPool(
    host="localhost",
    port=6379,
    db=0,
    max_connections=20,
    decode_responses=True,
)

@contextmanager
def get_redis_conn():
    """每个 Celery 任务独立获取一个 Redis 连接,用完归还"""
    conn = redis.Redis(connection_pool=redis_pool)
    try:
        yield conn
    finally:
        # 如果使用了连接池,关闭连接只是归还给池子
        conn.close()
  

看到没?这里的关键就是 get_db_sessionget_redis_conn 这两个上下文管理器。它们确保了每一个独立的 Celery 任务,都拥有一个属于自己的、生命周期完整的资源。

🚀 第四步:编写具体的 Celery 任务

现在,我们可以在 tasks/user_tasks.py 里编写具体的业务逻辑了。使用 @celery_app.task 装饰器定义任务。

# tasks/user_tasks.py
from app.core.celery_app import celery_app
from app.core.database import get_db_session
from app.core.redis_client import get_redis_conn
from app.models.user import User
import time

@celery_app.task(name="process_avatar")
def process_avatar(user_id: int, image_path: str):
    """
    处理用户头像的异步任务
    """
    # 1. 更新数据库状态:processing
    with get_db_session() as db:
        user = db.query(User).filter(User.id == user_id).first()
        if not user:
            raise ValueError(f"User {user_id} not found")
        user.avatar_status = "processing"
        db.commit()
    
    # 2. 模拟耗时的图片处理(可替换为实际图片处理库)
    time.sleep(3)
    # 假设处理成功,生成缩略图路径
    thumbnail_path = f"/thumbnails/{user_id}.jpg"
    
    # 3. 更新 Redis 状态
    with get_redis_conn() as redis:
        redis.set(f"user:{user_id}:avatar:status", "completed")
        redis.set(f"user:{user_id}:avatar:thumbnail", thumbnail_path)
    
    # 4. 再次更新数据库最终状态
    with get_db_session() as db:
        user.avatar_status = "completed"
        user.avatar_thumbnail = thumbnail_path
        db.commit()
    
    return {"status": "success", "user_id": user_id, "thumbnail": thumbnail_path}
  

📞 第五步:在 FastAPI 路由中调用 Celery 任务

# api/user.py
from fastapi import APIRouter, UploadFile, File
from app.tasks.user_tasks import process_avatar
import uuid

router = APIRouter()

@router.post("/upload_avatar")
async def upload_avatar(user_id: int, file: UploadFile = File(...)):
    # 保存文件到临时目录(省略)
    temp_path = f"/tmp/{uuid.uuid4()}.jpg"
    # 将文件写入磁盘...
    
    # 调用 Celery 任务,只传递必要的业务数据
    task = process_avatar.delay(user_id, temp_path)
    
    return {"task_id": task.id, "status": "queued"}
  

💡 进阶思考与踩坑预警

- 关于错误重试:

Celery 自带重试机制。你可以在任务上配置 @celery_app.task(bind=True, autoretry_for=(Exception,), retry_backoff=True, max_retries=3),让任务在遇到异常时自动重试,这对处理数据库暂时性连接问题很有帮助。

- 关于任务幂等性:

设计任务时尽量保证幂等,因为重试可能导致同一个任务被执行多次。比如更新数据库状态时,可以用 UPDATE ... WHERE status = 'processing' 这样的条件来避免重复执行。

好了,今天的内容就到这里。同上篇文件一样, Celery 任务里的资源管理,核心就是一个“职责分离”的原则。

老规矩,觉得有用的话,点赞、关注、转发走一波,让更多小伙伴看到。有任何想法和问题,评论区留言,一起交流,一起进步!!😉