Celery 是 Python 生态中最成熟的分布式任务队列框架。它把"耗时操作"从 Web 请求里剥离出来,交给后台 worker 异步执行,并支持定时调度、重试、链式编排、跨语言调用。几乎所有规模化的 Python Web 服务(Django / Flask / FastAPI)背后都有它。
目录
- 简介与定位
- 核心概念
- 架构原理
- 安装与依赖
- Broker 与 Backend 选型
- Hello World:第一个任务
- 任务定义详解
- 调用任务
- 配置
- Worker 启动与管理
- 并发模型(prefork / threads / gevent / eventlet / solo)
- 路由、队列与优先级
- 重试、超时与错误处理
- Canvas:任务编排(chain / group / chord / map / starmap / chunks)
- Celery Beat 定时任务
- 结果存储与查询
- Django / FastAPI / Flask 集成
- 监控与可观测性(Flower、Prometheus、Sentry、OpenTelemetry)
- 部署(systemd / Docker / Kubernetes)
- 性能调优
- 安全
- 测试
- 常见问题与排错
- Celery vs RQ / Dramatiq / Arq / Huey
- 命令速查表
- 推荐学习资源
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 周期性发布任务 |
| Queue | broker 上的逻辑队列,可路由不同 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 版本兼容性
| Celery | Python | Django |
|---|---|---|
| 5.4+ | 3.8–3.13 | 3.2 / 4.x / 5.x |
| 5.3 | 3.8–3.12 | 3.2 / 4.x / 5.x |
| 5.2 | 3.7–3.10 | 2.2+ |
| 4.x | 2.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=10 | 10 秒后执行 |
eta=datetime(...) | 在某时间点执行 |
expires=60 | 60 秒内未执行则丢弃 |
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,按队列路由
- 轻量任务、想用 asyncio:
threads或考虑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_shutdown | worker 启动/关闭 |
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_secondscelery_worker_upcelery_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_URL 与 broker_url
Celery 4.0 改名了。新代码统一用小写:broker_url、result_backend、task_*。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 实例 → 改用 redbeat 或 django-celery-beat,并保证只有一个主。
23.10 RabbitMQ Channel 越积越多
老版 Celery 在某些异常下不释放 channel。升级到最新 5.x,并设置:
broker_pool_limit = 10
24. Celery vs RQ / Dramatiq / Arq / Huey
| 维度 | Celery | RQ | Dramatiq | Arq | Huey |
|---|---|---|---|---|---|
| Broker | Redis/RabbitMQ/SQS… | 仅 Redis | Redis/RabbitMQ | 仅 Redis | Redis/SQLite |
| 学习曲线 | 高 | 极低 | 中 | 低 | 低 |
| 生态/插件 | 极丰富 | 一般 | 中 | 少 | 少 |
| Canvas/编排 | ✅ 完整 | 简单链 | middleware | ❌ | 简单 |
| asyncio 原生 | 部分 | ❌ | ❌ | ✅ | ❌ |
| Beat/调度 | ✅ | rq-scheduler | ✅ | ✅ | ✅ |
| 监控 | Flower、各种 exporter | rq-dashboard | dramatiq-dashboard | logfire | minimal |
| 适合规模 | 任意 → 大型 | 小-中 | 中-大 | 小-中(异步重 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 8 | 8 并发 |
celery -A proj worker --pool=gevent -c 1000 | gevent 高并发 |
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 ping | worker 心跳 |
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. 推荐学习资源
- 官方文档:docs.celeryq.dev/
- GitHub:github.com/celery/cele…
- 用户指南:docs.celeryq.dev/en/stable/u…
- 配置项参考:docs.celeryq.dev/en/stable/u…
- Canvas 编排:docs.celeryq.dev/en/stable/u…
- Flower:flower.readthedocs.io/
- redbeat:github.com/sibson/redb…
- django-celery-beat:github.com/celery/djan…
- Awesome Celery:github.com/fjsj/awesom…
- 经典文章 3 Gotchas of Celery:denibertovic.com/posts/celer…
结语:Celery 把"异步任务"这件事抽象到了极致——任意 Python 函数加一行装饰器就能跨进程、跨机器、跨语言执行。但它的强大也带来复杂:选 broker、配并发模型、防重复执行、做监控,每一项都是工程决策。务实路线是从最小可用配置起步(Redis + prefork + Flower),按需引入 Canvas、Beat、redbeat、Sentry,不要一上来就堆全部特性。当你发现 Celery 太重,可以考虑 Dramatiq / Arq;当你发现它能力不够(复杂 DAG),那就该看 Airflow / Prefect 了。