Celery 使用文档

3 阅读22分钟

Celery 是 Python 生态中最成熟的分布式任务队列框架。它把"耗时操作"从 Web 请求里剥离出来,交给后台 worker 异步执行,并支持定时调度、重试、链式编排、跨语言调用。几乎所有规模化的 Python Web 服务(Django / Flask / FastAPI)背后都有它。


目录

  1. 简介与定位
  2. 核心概念
  3. 架构原理
  4. 安装与依赖
  5. Broker 与 Backend 选型
  6. Hello World:第一个任务
  7. 任务定义详解
  8. 调用任务
  9. 配置
  10. Worker 启动与管理
  11. 并发模型(prefork / threads / gevent / eventlet / solo)
  12. 路由、队列与优先级
  13. 重试、超时与错误处理
  14. Canvas:任务编排(chain / group / chord / map / starmap / chunks)
  15. Celery Beat 定时任务
  16. 结果存储与查询
  17. Django / FastAPI / Flask 集成
  18. 监控与可观测性(Flower、Prometheus、Sentry、OpenTelemetry)
  19. 部署(systemd / Docker / Kubernetes)
  20. 性能调优
  21. 安全
  22. 测试
  23. 常见问题与排错
  24. Celery vs RQ / Dramatiq / Arq / Huey
  25. 命令速查表
  26. 推荐学习资源

1. 简介与定位

1.1 Celery 解决什么

把以下场景从同步请求中剥离:

  • 发邮件、推送、短信
  • 图片/视频处理、PDF 生成
  • 调用慢速第三方 API(支付、风控、AI 推理)
  • 报表生成、数据 ETL、批量入库
  • 定时任务(日报、清理过期数据)
  • 长链路工作流(多步串/并联,依赖结果传递)

1.2 Celery 不解决什么

  • 不是消息队列本身:它是"任务队列框架",底层依赖真正的消息队列(Redis / RabbitMQ)。
  • 不是流式数据处理:高吞吐 ETL 用 Kafka + Spark/Flink。
  • 不是工作流编排平台:复杂 DAG / 数据流水线考虑 Airflow / Prefect / Dagster。
  • 不是即时任务:本地几毫秒能跑完的事别绕一圈。

1.3 历史与现状

  • 2009 年由 Ask Solem 创建,经历 4.x → 5.x 重大重构。
  • 当前主流版本 5.4+(截至 2025),Python 3.8+
  • 项目活跃度长期稳定,社区与企业级生态最完善。

2. 核心概念

概念说明
Task(任务)一个被装饰的可调用对象,描述"要做什么"
Worker长驻进程,从队列取任务并执行
Broker消息中间件,存放等待执行的任务(Redis / RabbitMQ)
Result Backend存放任务执行结果(Redis / DB / RPC / S3 / 不存)
Producer / Client调用 task.delay() 把任务塞进 broker 的角色(通常是 Web 进程)
Beat定时调度器,按 cron/interval 周期性发布任务
Queuebroker 上的逻辑队列,可路由不同 worker 消费
Canvas把多个任务组合成 chain / group / chord 的"原语"集合

3. 架构原理

                 ┌────────────┐  enqueue   ┌──────────┐  consume   ┌─────────┐
   Web/API/CLI ──│ Producer   │──────────→ │ Broker   │──────────→ │ Worker  │
                 │ task.delay │            │ (Redis/  │            │ (prefork│
                 └────────────┘            │ RabbitMQ)│            │  pool)  │
                                           └──────────┘            └────┬────┘
                                                                        │ store
                                                                        ↓
                                                                  ┌──────────┐
                                                                  │ Result   │
                                                                  │ Backend  │
                                                                  └──────────┘

   定时调度:    ┌────────────┐
                 │ Celery Beat│ ───周期性 enqueue→ Broker
                 └────────────┘
  • Producer 把任务序列化(默认 JSON,可选 pickle / msgpack)成"消息"塞进 broker。
  • Worker 从 broker 拉消息、反序列化、执行、把结果写 backend。
  • 一切异步、解耦:producer 不知道是哪个 worker 跑的、worker 不知道是谁发的。

4. 安装与依赖

4.1 基础安装

# 使用 uv(推荐)
uv add celery

# 或 pip
pip install celery

4.2 按 broker / backend 安装 extras

uv add 'celery[redis]'                  # Redis 作为 broker + backend
uv add 'celery[rabbitmq]'               # RabbitMQ
uv add 'celery[sqs]'                    # AWS SQS
uv add 'celery[mongodb]'                # MongoDB backend
uv add 'celery[redis,gevent,auth]'      # 多 extras

完整 extras 列表:redis / rabbitmq / sqs / mongodb / couchdb / arangodb / consul / gevent / eventlet / auth / sqlalchemy / librabbitmq / tblib / zstd / pyro / slmq / azureblockblob / s3 等。

4.3 可选生态

uv add flower                           # Web 监控面板
uv add django-celery-beat               # Django 数据库后端调度
uv add django-celery-results            # Django ORM 结果后端
uv add celery-exporter                  # Prometheus exporter

4.4 版本兼容性

CeleryPythonDjango
5.4+3.8–3.133.2 / 4.x / 5.x
5.33.8–3.123.2 / 4.x / 5.x
5.23.7–3.102.2+
4.x2.7 / 3.5+1.11+

5. Broker 与 Backend 选型

5.1 Broker 对比

Broker优点缺点推荐场景
Redis部署简单、速度快、能同时做 backend不是真正的消息队列,极端崩溃下可能丢消息中小项目、对丢消息容忍
RabbitMQ成熟可靠、支持持久化/确认/优先级、AMQP 标准部署运维稍重金融/生产级、不能丢消息
Amazon SQS全托管、按量付费延迟高、不支持优先级、不支持远程控制命令AWS 全家桶
Google Pub/Sub全托管同上GCP 全家桶
Kafka(实验)高吞吐Celery 支持仍不完善已有 Kafka 基础设施

5.2 Result Backend 对比

Backend优点缺点
Redis快、简单、forget 直接删内存数据库,结果不长期保存
RPC(AMQP)不需额外组件一次性消费,只能取一次
PostgreSQL/MySQL持久化、可查询高频写入不友好
MongoDB文档结构灵活引入额外组件
S3 / Azure Blob大结果存储延迟高
Disabled最快完全拿不到结果/状态

5.3 实践建议

  • Web 后端日常用:Redis 同时做 broker + backend,最省心。
  • 不能丢任务:RabbitMQ + Redis(结果)。
  • 不需要结果:禁用 backend(task_ignore_result = True),减少一半 IO。

6. Hello World:第一个任务

6.1 项目结构

proj/
├── celery_app.py
├── tasks.py
└── pyproject.toml

6.2 创建 Celery 实例

celery_app.py

from celery import Celery

app = Celery(
    "proj",
    broker="redis://localhost:6379/0",
    backend="redis://localhost:6379/1",
    include=["proj.tasks"],
)

app.conf.update(
    task_serializer="json",
    result_serializer="json",
    accept_content=["json"],
    timezone="Asia/Shanghai",
    enable_utc=True,
)

6.3 定义任务

tasks.py

from .celery_app import app

@app.task
def add(x: int, y: int) -> int:
    return x + y

@app.task
def send_email(to: str, subject: str, body: str) -> None:
    print(f"sending mail to {to}: {subject}")

6.4 启动 worker

celery -A proj.celery_app worker -l info

6.5 调用任务

from proj.tasks import add

result = add.delay(2, 3)        # 异步发送
print(result.id)                # 任务 ID
print(result.get(timeout=5))    # 阻塞获取结果 → 5

7. 任务定义详解

7.1 装饰器形式

@app.task
def simple(x): ...

@app.task(name="myapp.tasks.add", bind=True, max_retries=3)
def add(self, x, y):
    return x + y

@app.task(
    bind=True,
    autoretry_for=(IOError, TimeoutError),
    retry_backoff=True,
    retry_backoff_max=600,
    retry_jitter=True,
    max_retries=5,
    time_limit=120,           # 硬超时(被 SIGKILL)
    soft_time_limit=100,      # 软超时(抛 SoftTimeLimitExceeded)
    rate_limit="10/s",        # 限流:每秒 10 个
    acks_late=True,           # 执行成功才 ack(更可靠)
    queue="emails",
    priority=5,
)
def send_mail(self, to, body):
    ...

7.2 类形式(自定义基类)

from celery import Task

class DBTask(Task):
    _db = None

    @property
    def db(self):
        if self._db is None:
            self._db = create_db_connection()
        return self._db

    def on_failure(self, exc, task_id, args, kwargs, einfo):
        # 记录日志、发告警
        ...

@app.task(base=DBTask, bind=True)
def insert_row(self, row):
    self.db.insert(row)

7.3 任务命名

显式命名能避免 import 路径变更引发"找不到任务":

@app.task(name="emails.send_welcome")
def f(): ...

7.4 任务返回值

  • 默认返回值会被序列化存入 backend。
  • 大对象、敏感数据建议返回引用(如 S3 key)而不是数据本身。

7.5 自动发现任务

# Django 项目:从所有 INSTALLED_APPS 的 tasks.py 自动加载
app.autodiscover_tasks()

# 自定义模块列表
app.autodiscover_tasks(["proj.module_a", "proj.module_b"])

8. 调用任务

8.1 三种调用方式

add.delay(2, 3)                       # 最常用
add.apply_async(args=[2, 3], countdown=10, queue="default", priority=5)
add(2, 3)                             # 同步直接调用(不走 broker,调试用)

8.2 apply_async 高级参数

参数作用
countdown=1010 秒后执行
eta=datetime(...)在某时间点执行
expires=6060 秒内未执行则丢弃
queue="emails"路由到指定队列
priority=9优先级(0–9,仅 Redis/RabbitMQ)
retry=True, retry_policy={...}发布失败时重试
link=other_task.s()成功后链式调用
link_error=fail_handler.s()失败回调
headers={"trace_id": "..."}自定义消息头(用于追踪)

8.3 Signature(任务签名)

预先打包的"待调用任务",用于编排:

sig = add.s(2, 3)                  # 完整签名
sig = add.si(2, 3)                 # immutable,忽略上游结果
sig = add.signature((2,), {"y": 3}, countdown=10)
sig.delay()                        # 调用

8.4 远程调用(不持有任务源码)

app.send_task("emails.send_welcome", args=["alice@example.com"], queue="emails")

跨服务/跨语言调用时常用——producer 不必 import 任务实现。


9. 配置

9.1 配置方式

# 1. 直接修改
app.conf.task_serializer = "json"

# 2. 字典更新
app.conf.update(broker_url="redis://...", result_backend="redis://...")

# 3. 从对象/模块加载
app.config_from_object("proj.celeryconfig")    # 模块路径
app.config_from_envvar("CELERY_CONFIG_MODULE") # 环境变量

# 4. Django 风格(带前缀)
app.config_from_object("django.conf:settings", namespace="CELERY")
# settings.py 中所有 CELERY_XXX 变量都会被读取

9.2 常用配置项

# ===== Broker =====
broker_url = "redis://localhost:6379/0"
broker_connection_retry_on_startup = True
broker_pool_limit = 10
broker_transport_options = {"visibility_timeout": 3600}

# ===== Backend =====
result_backend = "redis://localhost:6379/1"
result_expires = 3600                 # 结果保留 1 小时
result_extended = True                # 存更多元数据(task_name、args)
task_ignore_result = False            # 全局忽略结果
result_compression = "gzip"

# ===== 任务行为 =====
task_serializer = "json"
result_serializer = "json"
accept_content = ["json"]             # 安全:禁止 pickle
task_acks_late = True                 # worker 完成后才 ack
task_reject_on_worker_lost = True     # worker 崩溃时把任务重新入队
task_track_started = True             # STARTED 状态可见
task_time_limit = 600                 # 全局硬超时
task_soft_time_limit = 540

# ===== Worker =====
worker_concurrency = 4                # prefork 进程数
worker_prefetch_multiplier = 1        # 每次只取 1 个任务(防止积压)
worker_max_tasks_per_child = 1000     # 子进程跑 N 个任务后重启(防内存泄漏)
worker_max_memory_per_child = 200000  # KB,超出则重启
worker_send_task_events = True        # 发送事件给 Flower
worker_disable_rate_limits = False

# ===== 路由 =====
task_default_queue = "default"
task_default_exchange = "default"
task_default_routing_key = "default"
task_routes = {
    "emails.*": {"queue": "emails"},
    "ml.train": {"queue": "gpu"},
}

# ===== 时区 =====
timezone = "Asia/Shanghai"
enable_utc = True

9.3 完整配置参考

官方文档列出了 200+ 配置项:docs.celeryq.dev/en/stable/u…


10. Worker 启动与管理

10.1 基础启动

celery -A proj worker -l info
celery -A proj worker -l info -c 8                        # 8 个并发
celery -A proj worker -l info -Q emails,default           # 只消费指定队列
celery -A proj worker -l info -n worker1@%h               # 自定义节点名
celery -A proj worker -l info --pool=gevent -c 1000       # gevent 高并发
celery -A proj worker -l info --autoscale=10,3            # 弹性 3-10

10.2 启动多个 worker 进程(multi)

celery -A proj multi start w1 w2 w3 -l info \
       --pidfile=/var/run/celery/%n.pid \
       --logfile=/var/log/celery/%n.log

celery -A proj multi restart w1
celery -A proj multi stopwait w1

10.3 远程控制

celery -A proj inspect active             # 正在执行的任务
celery -A proj inspect reserved           # 已取但未执行
celery -A proj inspect scheduled          # 倒计时/eta 任务
celery -A proj inspect stats              # 统计信息
celery -A proj inspect registered         # 已注册任务列表

celery -A proj control rate_limit emails.send 10/m
celery -A proj control shutdown
celery -A proj control add_consumer emails
celery -A proj control cancel_consumer emails
celery -A proj control pool_grow 4
celery -A proj control pool_shrink 2
celery -A proj purge                      # 清空队列(危险)

注意:远程控制依赖 broker 的广播能力,SQS/Pub/Sub 不支持

10.4 优雅关闭

  • SIGTERM:等所有正在执行的任务完成后退出(warm shutdown)。
  • SIGQUIT:立即退出(cold shutdown),未完成任务可能丢失。
  • 第二次 SIGTERM 等同 cold shutdown。

11. 并发模型

11.1 五种 pool 对比

Pool适用场景并发数特点
prefork(默认)CPU 密集 / 一般 IO进程数(通常 = CPU 核数)多进程,最稳,启动慢
threads中等 IO数十多线程,受 GIL 限制
gevent高并发 IO(HTTP/DB)数千协程,需要库支持 monkey patch
eventlet同 gevent数千同 gevent,老牌
solo调试 / 单任务串行1不开并发,主线程跑

11.2 选择指南

  • 大量调用 HTTP/数据库(如调 OpenAI、爬虫)→ gevent / eventlet
  • CPU 计算(图像处理、加密)→ prefork
  • 混合负载:拆成不同 worker,按队列路由
  • 轻量任务、想用 asynciothreads 或考虑 arq / dramatiq

11.3 gevent 示例

uv add gevent
celery -A proj worker --pool=gevent -c 500 -l info

用 gevent 时,所有 IO 库(requests / psycopg2)都需在 worker 启动时 monkey-patch:

# 放在最前面
from gevent import monkey; monkey.patch_all()

11.4 asyncio 任务

Celery 5.0+ 原生支持 async def 任务(基于 thread pool 执行):

@app.task
async def fetch(url):
    async with httpx.AsyncClient() as c:
        r = await c.get(url)
        return r.text

真正的事件循环并发仍需 gevent 或迁移到 arq/Dramatiq。


12. 路由、队列与优先级

12.1 静态路由

app.conf.task_routes = {
    "emails.send":        {"queue": "emails"},
    "emails.*":           {"queue": "emails"},     # 通配
    "ml.train":           {"queue": "gpu", "routing_key": "ml.train"},
    "reports.generate":   {"queue": "heavy"},
}

12.2 调用时动态路由

add.apply_async((1, 2), queue="priority-high")

12.3 启动消费指定队列的 worker

celery -A proj worker -Q emails -c 4 -n emails-worker@%h
celery -A proj worker -Q gpu -c 1 -n gpu-worker@%h --pool=solo
celery -A proj worker -Q default,reports -c 8 -n general@%h

12.4 优先级

Redis:

app.conf.broker_transport_options = {
    "priority_steps": list(range(10)),
    "queue_order_strategy": "priority",
}

add.apply_async((1, 2), priority=9)     # 0 最低,9 最高

RabbitMQ:在队列声明时设置 x-max-priority

12.5 路由器函数(Router)

def route_task(name, args, kwargs, options, task=None, **kw):
    if name.startswith("ml."):
        return {"queue": "gpu"}
    if kwargs.get("user_vip"):
        return {"queue": "vip"}
    return {"queue": "default"}

app.conf.task_routes = (route_task,)

13. 重试、超时与错误处理

13.1 自动重试

@app.task(
    bind=True,
    autoretry_for=(ConnectionError, TimeoutError),
    dont_autoretry_for=(ValueError,),
    retry_backoff=True,             # 指数退避:1, 2, 4, 8...
    retry_backoff_max=600,          # 最长 10 分钟
    retry_jitter=True,              # 加随机抖动防"惊群"
    max_retries=5,
)
def fetch(self, url):
    return requests.get(url, timeout=5).json()

13.2 手动重试

@app.task(bind=True, max_retries=3)
def process(self, payload):
    try:
        ...
    except SomeError as exc:
        raise self.retry(exc=exc, countdown=2 ** self.request.retries)

13.3 超时控制

@app.task(soft_time_limit=30, time_limit=60)
def long_job():
    try:
        slow_call()
    except SoftTimeLimitExceeded:
        cleanup()                   # 软超时可优雅清理
        raise

硬超时通过 SIGKILL 终止整个子进程,清理代码不会运行

13.4 失败回调与信号

@app.task(bind=True)
def f(self):
    ...

f.apply_async(link=success_cb.s(), link_error=fail_cb.s())

# 或全局信号
from celery.signals import task_failure

@task_failure.connect
def on_fail(sender, task_id, exception, args, kwargs, einfo, **kw):
    sentry_sdk.capture_exception(exception)

13.5 重要信号一览

信号触发时机
task_prerun / task_postrun任务执行前/后
task_success成功
task_failure失败
task_retry重试
task_revoked被 revoke
worker_ready / worker_shutdownworker 启动/关闭
before_task_publish / after_task_publish发布消息前/后(producer 端)

13.6 撤销任务

result.revoke()                          # 标记撤销
result.revoke(terminate=True, signal="SIGKILL")  # 立即终止正在执行的
app.control.revoke(task_id, terminate=True)

revoke 信息存在 worker 内存里,重启会丢失。持久化需开启 worker_state_db


14. Canvas:任务编排

Canvas 是 Celery 的"任务组合 DSL",用 Signature 串/并联出复杂工作流。

14.1 chain:串行

from celery import chain

# 上游结果作为下游第一个参数
workflow = chain(fetch.s(url), parse.s(), save.s())
workflow.delay()

# 也可以用 | 操作符
(fetch.s(url) | parse.s() | save.s()).delay()

14.2 group:并行

from celery import group

job = group(fetch.s(u) for u in urls)
result = job.apply_async()
result.get()                           # 列表结果

14.3 chord:并行 + 汇总

from celery import chord

# 所有 fetch 完成后调用 summarize
chord((fetch.s(u) for u in urls), summarize.s()).delay()

chord 需要 result backend 支持(Redis/DB),用来收集结果。

14.4 map / starmap

add.map([(2, 3), (4, 5)])            # 等价 group
add.starmap([(2, 3), (4, 5)])         # 解包参数

14.5 chunks:批处理

add.chunks(zip(range(100), range(100)), 10).apply_async()
# 每 10 个分一批,减少消息数量

14.6 复杂组合

workflow = chain(
    group(fetch.s(u) for u in urls),
    merge.s(),
    chord(
        group(transform.s(i) for i in range(10)),
        save.s(),
    ),
)

14.7 immutable signature(.si

下游不接收上游结果时使用,避免参数错位:

chain(prepare.s(), notify.si("done")).delay()

15. Celery Beat 定时任务

15.1 配置定时调度

from celery.schedules import crontab, solar

app.conf.beat_schedule = {
    "every-30-seconds": {
        "task": "tasks.heartbeat",
        "schedule": 30.0,
        "args": (),
    },
    "daily-report": {
        "task": "reports.generate_daily",
        "schedule": crontab(hour=2, minute=0),
        "kwargs": {"format": "pdf"},
        "options": {"queue": "reports"},
    },
    "weekly-cleanup": {
        "task": "maint.cleanup",
        "schedule": crontab(day_of_week="sun", hour=3, minute=0),
    },
    "every-monday-morning": {
        "task": "reports.weekly",
        "schedule": crontab(hour=8, minute=0, day_of_week=1),
    },
    "sunset-task": {
        "task": "iot.lights_off",
        "schedule": solar("sunset", -37.81, 144.96),
    },
}

15.2 启动 Beat

celery -A proj beat -l info
celery -A proj beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler

15.3 重要原则

  • 整个集群只能有一个 Beat 实例(否则任务会被多次发布)。
  • 默认 scheduler 把状态写到本地文件 celerybeat-schedule,重启后从文件恢复。
  • 多副本部署时使用:
    • django-celery-beat(数据库存调度,可在管理后台改)
    • redbeat(Redis 存调度,支持锁,K8s 友好)

15.4 redbeat(推荐 K8s 场景)

uv add celery-redbeat
app.conf.beat_scheduler = "redbeat.RedBeatScheduler"
app.conf.redbeat_redis_url = "redis://localhost:6379/2"
app.conf.redbeat_lock_key = "redbeat::lock"   # 多副本互斥

可以多个 Beat 实例同时运行,靠 Redis 分布式锁选主。


16. 结果存储与查询

16.1 AsyncResult API

r = add.delay(2, 3)
r.id                      # task id
r.state                   # PENDING / STARTED / SUCCESS / FAILURE / RETRY / REVOKED
r.ready()                 # 是否完成
r.successful()            # 是否成功
r.failed()
r.get(timeout=5)          # 阻塞拿结果
r.get(propagate=False)    # 失败时不抛异常,返回异常对象
r.info / r.result         # 结果或异常
r.traceback               # 失败堆栈
r.forget()                # 删除结果

16.2 凭 ID 取结果

from celery.result import AsyncResult

r = AsyncResult("xxx-task-id", app=app)
print(r.state, r.result)

16.3 自定义状态

@app.task(bind=True)
def upload(self, files):
    for i, f in enumerate(files):
        do_upload(f)
        self.update_state(state="PROGRESS", meta={"current": i, "total": len(files)})

调用方:

r.info  # → {"current": 3, "total": 10}

16.4 结果过期与清理

result_expires = 3600         # 1 小时后自动删除(Redis backend 自动过期)

Database backend 需要 celery beat 周期跑 celery.backend_cleanup


17. Django / FastAPI / Flask 集成

17.1 Django

myproject/celery.py

import os
from celery import Celery

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")

app = Celery("myproject")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()        # 扫描所有 app/tasks.py

myproject/__init__.py

from .celery import app as celery_app
__all__ = ("celery_app",)

settings.py

CELERY_BROKER_URL = "redis://localhost:6379/0"
CELERY_RESULT_BACKEND = "redis://localhost:6379/1"
CELERY_TASK_TRACK_STARTED = True
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"

myapp/tasks.py

from celery import shared_task

@shared_task
def send_welcome_email(user_id: int):
    ...

@shared_task 不绑定具体 app 实例,可在多 Celery app 项目里共享,是 Django 推荐用法。

17.2 Django ORM 安全

@shared_task
def update_user(user_id):
    from django.db import transaction
    with transaction.atomic():
        user = User.objects.select_for_update().get(pk=user_id)
        ...

发送任务时如果在事务里:

from django.db import transaction
transaction.on_commit(lambda: send_welcome_email.delay(user.id))

避免事务还没提交就触发任务读到旧数据。

17.3 FastAPI

# celery_app.py
from celery import Celery
celery_app = Celery("api", broker="redis://...", backend="redis://...")
celery_app.conf.update(task_track_started=True)

# tasks.py
@celery_app.task
def heavy_calc(n): ...

# main.py
from fastapi import FastAPI
from .tasks import heavy_calc

app = FastAPI()

@app.post("/jobs")
async def create_job(n: int):
    r = heavy_calc.delay(n)
    return {"task_id": r.id}

@app.get("/jobs/{task_id}")
async def job_status(task_id: str):
    r = celery_app.AsyncResult(task_id)
    return {"state": r.state, "result": r.result if r.ready() else None}

17.4 Flask

# app.py
from flask import Flask
from celery import Celery

def make_celery(app):
    celery = Celery(app.import_name, broker=app.config["CELERY_BROKER_URL"])
    celery.conf.update(app.config)

    class ContextTask(celery.Task):
        def __call__(self, *args, **kw):
            with app.app_context():
                return self.run(*args, **kw)

    celery.Task = ContextTask
    return celery

flask_app = Flask(__name__)
flask_app.config.from_pyfile("config.py")
celery = make_celery(flask_app)

@celery.task
def my_task(): ...

18. 监控与可观测性

18.1 Flower

Web 监控面板:实时查看任务、worker、队列状态。

uv add flower
celery -A proj flower --port=5555 --basic-auth=admin:secret

打开 http://localhost:5555,可:

  • 查看活跃/计划/失败任务
  • 远程控制 worker(pool 增减、shutdown)
  • 任务调用历史与详情
  • HTTP API:GET /api/tasks / GET /api/workers

18.2 Prometheus

celery-exporter

uv add celery-exporter
celery-exporter --broker-url=redis://localhost:6379/0
# Prometheus 抓 :9540/metrics

关键指标:

  • celery_task_total{state="success"}
  • celery_task_runtime_seconds
  • celery_worker_up
  • celery_queue_length

18.3 Sentry

import sentry_sdk
from sentry_sdk.integrations.celery import CeleryIntegration

sentry_sdk.init(
    dsn=...,
    integrations=[CeleryIntegration(monitor_beat_tasks=True)],
    traces_sample_rate=0.1,
)

可自动捕获任务异常 + 性能追踪 + Cron monitoring。

18.4 OpenTelemetry

uv add opentelemetry-instrumentation-celery
from opentelemetry.instrumentation.celery import CeleryInstrumentor
CeleryInstrumentor().instrument()

trace 会跨 producer ↔ broker ↔ worker 串起来。

18.5 日志最佳实践

import logging
logger = logging.getLogger(__name__)

@app.task(bind=True)
def f(self):
    logger.info("running %s", self.request.id)   # 自动带 task_id 上下文

启动 worker 时用 --logfile=/var/log/celery/%n.log 拆分日志,配合 logrotate。


19. 部署

19.1 systemd

/etc/systemd/system/celery.service

[Unit]
Description=Celery Worker
After=network.target redis.service

[Service]
Type=forking
User=celery
Group=celery
EnvironmentFile=/etc/celery/celery.env
WorkingDirectory=/opt/proj
ExecStart=/opt/proj/.venv/bin/celery multi start w1 -A proj \
    --pidfile=/var/run/celery/%n.pid --logfile=/var/log/celery/%n.log -l info -c 8
ExecStop=/opt/proj/.venv/bin/celery multi stopwait w1 \
    --pidfile=/var/run/celery/%n.pid
ExecReload=/opt/proj/.venv/bin/celery multi restart w1 -A proj \
    --pidfile=/var/run/celery/%n.pid --logfile=/var/log/celery/%n.log -l info
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now celery

19.2 Docker

FROM python:3.12-slim

COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/

WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project

COPY . .
RUN uv sync --frozen --no-dev

ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1

# 在 docker-compose 里以不同 command 启动 worker / beat
CMD ["celery", "-A", "proj", "worker", "-l", "info"]

docker-compose.yml

services:
  redis:
    image: redis:7-alpine
    restart: always

  worker:
    build: .
    command: celery -A proj worker -l info -c 8
    depends_on: [redis]
    environment:
      - CELERY_BROKER_URL=redis://redis:6379/0
      - CELERY_RESULT_BACKEND=redis://redis:6379/1

  beat:
    build: .
    command: celery -A proj beat -l info
    depends_on: [redis]
    deploy:
      replicas: 1                   # 关键:beat 只能 1 个

  flower:
    build: .
    command: celery -A proj flower --port=5555
    ports: ["5555:5555"]
    depends_on: [redis]

19.3 Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata: { name: celery-worker }
spec:
  replicas: 4
  template:
    spec:
      terminationGracePeriodSeconds: 60   # 给 warm shutdown 时间
      containers:
      - name: worker
        image: myorg/proj:latest
        command: ["celery", "-A", "proj", "worker", "-l", "info", "-c", "8"]
        lifecycle:
          preStop:
            exec:
              command: ["celery", "-A", "proj", "control", "shutdown"]
        livenessProbe:
          exec:
            command: ["celery", "-A", "proj", "inspect", "ping", "-d", "celery@$(HOSTNAME)"]
          periodSeconds: 60
          timeoutSeconds: 10
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: celery-beat }
spec:
  replicas: 1                             # 必须 1,或用 redbeat 多副本
  strategy: { type: Recreate }            # 避免重叠
  template:
    spec:
      containers:
      - name: beat
        image: myorg/proj:latest
        command: ["celery", "-A", "proj", "beat", "-l", "info",
                  "--scheduler", "redbeat.RedBeatScheduler"]

KEDA 可基于队列长度自动扩缩 worker:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata: { name: celery-scaler }
spec:
  scaleTargetRef: { name: celery-worker }
  minReplicaCount: 2
  maxReplicaCount: 50
  triggers:
    - type: redis
      metadata:
        address: redis:6379
        listName: celery
        listLength: "20"

20. 性能调优

20.1 prefetch 与公平派发

默认 worker 一次预取 4 个任务,导致:长任务挤占短任务、负载不均、worker 重启后任务重发。

生产环境推荐:

worker_prefetch_multiplier = 1
task_acks_late = True

代价:吞吐略降,但任务分发更公平、可靠。

20.2 限流

@app.task(rate_limit="100/m")          # 每分钟 100
@app.task(rate_limit="10/s")

只对单个 worker 生效,分布式限流要用 Redis 锁或 celery-singleton

20.3 防重复执行(idempotency)

Celery 至少投递一次(at-least-once),任务必须幂等。

实现方式:

from redis import Redis
import hashlib

r = Redis()

@app.task(bind=True)
def process(self, payload):
    key = "job:" + hashlib.md5(payload.encode()).hexdigest()
    if not r.set(key, "1", nx=True, ex=3600):
        return "duplicate"           # 已处理过
    do_work(payload)

或使用 celery-once 包,在调用前去重。

20.4 单飞(singleton)

uv add celery-singleton
from celery_singleton import Singleton

@app.task(base=Singleton)
def sync_user(user_id): ...

同一参数同时只允许一个实例运行。

20.5 大消息体优化

任务参数大会让 broker 撑爆。建议:

  • 参数只传 ID,让任务自行从 DB / S3 拉数据。
  • 启用消息压缩:task_compression = "gzip"
  • 改用 msgpack:task_serializer = "msgpack"(需安装 msgpack)。

20.6 数据库连接

每个 prefork 子进程会持有独立连接,注意:

from celery.signals import worker_process_init, worker_process_shutdown

@worker_process_init.connect
def init_worker(**kw):
    # 每个子进程初始化连接池
    db.connect()

@worker_process_shutdown.connect
def shutdown_worker(**kw):
    db.close()

或在 Django 中:默认 CONN_MAX_AGE 处理。

20.7 worker 内存控制

worker_max_tasks_per_child = 1000        # 跑 1000 任务后子进程重启
worker_max_memory_per_child = 200_000    # KB

防止内存泄漏积累。


21. 安全

21.1 禁用 pickle

pickle 反序列化 = 任意代码执行。生产环境永远只允许 JSON/msgpack:

task_serializer = "json"
result_serializer = "json"
accept_content = ["json"]                # 拒绝 pickle 消息

21.2 broker 认证 + TLS

broker_url = "rediss://:password@redis.internal:6380/0?ssl_cert_reqs=required"
broker_use_ssl = {
    "ssl_cert_reqs": ssl.CERT_REQUIRED,
    "ssl_ca_certs": "/etc/ssl/ca.pem",
}

RabbitMQ:amqps://user:pass@host:5671//

21.3 消息签名

uv add cryptography
task_serializer = "auth"
security_key = "/etc/celery/private.pem"
security_certificate = "/etc/celery/cert.pem"
security_cert_store = "/etc/celery/certs/*.pem"

防止恶意 producer 注入任务(适合多团队共用 broker 的场景)。

21.4 不要让任务接收用户原始数据

调用任务前在 Web 层校验、清洗参数,避免任务里再做信任假设。


22. 测试

22.1 同步执行(最简单)

app.conf.task_always_eager = True       # 跳过 broker,直接同步执行
app.conf.task_eager_propagates = True   # 异常透传

测试时调用 add.delay(2, 3) 等价于 add(2, 3)适合单元测试,不能验证序列化与并发问题。

22.2 pytest-celery

uv add --dev pytest-celery
@pytest.fixture
def celery_config():
    return {"broker_url": "memory://", "result_backend": "cache+memory://"}

def test_add(celery_app, celery_worker):
    assert add.delay(2, 3).get(timeout=5) == 5

22.3 集成测试用真 Redis

@pytest.fixture(scope="session")
def celery_app():
    app.conf.update(broker_url="redis://localhost:6379/15", result_backend="redis://localhost:6379/15")
    return app

testcontainers 起一次性 Redis:

from testcontainers.redis import RedisContainer

@pytest.fixture(scope="session")
def redis():
    with RedisContainer() as r:
        yield r.get_client_url()

23. 常见问题与排错

23.1 任务没被执行

排查:

celery -A proj inspect ping             # worker 在线吗
celery -A proj inspect registered       # 任务名是否注册
celery -A proj inspect active_queues    # worker 在消费哪些队列?任务路由对吗?
redis-cli LLEN celery                    # 队列里堆积多少

常见原因:worker 启动时没 import 任务模块;调用方与 worker 任务名不一致;路由到了没人消费的队列。

23.2 Received unregistered task of type ...

Worker 没加载任务定义。

# celery_app.py
app = Celery("proj", include=["proj.tasks", "proj.emails.tasks"])
# 或
app.autodiscover_tasks(["proj"])

23.3 任务结果一直 PENDING

PENDING 在 Celery 里是"未知",可能:

  • broker / backend 配错(producer 与 worker 用了不同 backend)
  • 任务从未被发出或已被丢弃
  • backend 被禁用(task_ignore_result=True

23.4 任务执行了两次

at-least-once 投递的正常表现,原因可能:

  • worker 崩溃时未 ack,broker 把任务重投
  • visibility timeout(SQS/Redis)小于任务执行时间

应对:让任务幂等;增大 visibility_timeout

broker_transport_options = {"visibility_timeout": 43200}    # 12h

23.5 内存泄漏 / RSS 越来越大

worker_max_tasks_per_child = 1000
worker_max_memory_per_child = 300000     # KB

定期重启子进程释放内存。

23.6 WorkerLostError / 子进程被 OOM kill

说明任务把内存撑爆。看 dmesg、降低并发、拆任务、加内存。

23.7 BROKER_URLbroker_url

Celery 4.0 改名了。新代码统一用小写broker_urlresult_backendtask_*。Django settings 加 CELERY_ 前缀 + 大写并不会被自动识别,必须配合 app.config_from_object("django.conf:settings", namespace="CELERY")

23.8 时区问题

timezone = "Asia/Shanghai"
enable_utc = True            # 内部用 UTC,crontab 按 timezone 解释

23.9 Beat 任务被多次发布

集群部署多个 Beat 实例 → 改用 redbeatdjango-celery-beat,并保证只有一个主。

23.10 RabbitMQ Channel 越积越多

老版 Celery 在某些异常下不释放 channel。升级到最新 5.x,并设置:

broker_pool_limit = 10

24. Celery vs RQ / Dramatiq / Arq / Huey

维度CeleryRQDramatiqArqHuey
BrokerRedis/RabbitMQ/SQS…仅 RedisRedis/RabbitMQ仅 RedisRedis/SQLite
学习曲线极低
生态/插件极丰富一般
Canvas/编排✅ 完整简单链middleware简单
asyncio 原生部分
Beat/调度rq-scheduler
监控Flower、各种 exporterrq-dashboarddramatiq-dashboardlogfireminimal
适合规模任意 → 大型小-中中-大小-中(异步重 IO)
维护活跃度极高

选型建议

  • 大型生产、复杂工作流、需要厂商集成:Celery
  • 简单后台任务、追求最低复杂度:RQ
  • 想要 Celery 的能力但更好的 API:Dramatiq
  • 基于 asyncio 的轻量服务:Arq
  • 嵌入式/单机简单需求:Huey

25. 命令速查表

25.1 Worker / Beat

命令用途
celery -A proj worker -l info启动 worker
celery -A proj worker -Q q1,q2消费指定队列
celery -A proj worker -c 88 并发
celery -A proj worker --pool=gevent -c 1000gevent 高并发
celery -A proj worker --autoscale=10,3弹性伸缩
celery -A proj beat -l info启动定时调度器
celery -A proj multi start w1 w2启动多个 worker
celery -A proj multi stopwait w1优雅关闭

25.2 远程控制 / 检查

命令用途
celery -A proj inspect pingworker 心跳
celery -A proj inspect active正在执行的任务
celery -A proj inspect registered已注册任务
celery -A proj inspect stats统计信息
celery -A proj inspect active_queues当前消费的队列
celery -A proj control shutdown关闭 worker
celery -A proj control rate_limit task.name 10/s动态限流
celery -A proj control add_consumer queue_name加消费队列
celery -A proj purge -Q queue_name清空队列(慎)

25.3 工具与调试

命令用途
celery -A proj events事件流 TUI
celery -A proj flower启动 Flower
celery -A proj shell带 app 上下文的 IPython
celery -A proj report收集环境信息(提 issue 用)
celery -A proj call task.name --args='[1,2]'命令行调用
celery -A proj result <task-id>查询结果

25.4 调用代码

写法用途
task.delay(*a, **kw)最简发送
task.apply_async(args, kwargs, **opts)完整选项
task.s(*a, **kw)signature
task.si(*a, **kw)immutable signature
chain(a.s(), b.s())串行
group(a.s(i) for i in xs)并行
chord(group(...), summary.s())并行汇总
result.get(timeout=10)阻塞取结果
result.revoke(terminate=True)撤销/终止

26. 推荐学习资源


结语:Celery 把"异步任务"这件事抽象到了极致——任意 Python 函数加一行装饰器就能跨进程、跨机器、跨语言执行。但它的强大也带来复杂:选 broker、配并发模型、防重复执行、做监控,每一项都是工程决策。务实路线是从最小可用配置起步(Redis + prefork + Flower),按需引入 Canvas、Beat、redbeat、Sentry,不要一上来就堆全部特性。当你发现 Celery 太重,可以考虑 Dramatiq / Arq;当你发现它能力不够(复杂 DAG),那就该看 Airflow / Prefect 了。