PAYATHON 2026

Django 项目中 Celery 报 user_options 属性缺失且任务不执行,如何排查?

排障赵师傅

这个异常说明,报错进程把 proyecto.celery 模块当成了 Celery 应用实例。先检查 worker 的启动参数和实际导入的对象,将应用入口明确写成 proyecto.celery:app,并删除手动添加的 app.user_options = {}。

日志中混有不同 Swarm 任务的输出,有的 worker 已经 ready,有的在启动时就报错。Redis 连接成功只能说明其中一个 worker 连接正常,其他副本是否启动成功仍需逐一核对。修复启动异常后,还要继续验证任务是否执行。

为什么添加 app.user_options = {} 没有解决问题?

报错发生在:

ctx.obj.app.user_options.get('worker', [])

这里的 ctx.obj.app 应当是下面这段代码创建的实例:

app = Celery('proyecto')

异常却显示:

AttributeError: module 'proyecto.celery' has no attribute 'user_options'

实际加载的是模块。模块和它内部的 app 是两个不同的对象:

proyecto.celery       Python 模块
proyecto.celery:app   该模块中的 Celery 实例

给实例设置 app.user_options,不会给模块增加同名属性。Celery 实例本身会初始化这个属性,常规 Django 集成无需手动设置。

缺少启动命令、proyecto/__init__.py 和部署配置,目前还无法确定入口为什么会解析成模块。应检查应用入口是否正确、是否有同名变量覆盖,以及容器中的代码是否与预期一致。

先统一 Django 与 Celery 的应用入口

将 proyecto/celery.py 整理为:

import os

from celery import Celery

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

app = Celery("proyecto")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()

删除这两部分:

import django
django.setup()
app.user_options = {}

常规 Celery worker 启动时会通过 Django 集成完成初始化,通常不需要在这里提前调用 django.setup()。提前初始化可能引入循环导入或应用加载顺序问题,但现有日志还不能证明这就是此次异常的原因。

在 proyecto/__init__.py 中导出应用:

from .celery import app as celery_app

__all__ = ("celery_app",)

worker 启动命令明确指定实例:

celery -A proyecto.celery:app worker --loglevel=INFO

Swarm 部署文件中的服务命令可以写成:

services:
  worker:
    image: your-registry/your-project:your-version
    command:
      - celery
      - -A
      - proyecto.celery:app
      - worker
      - --loglevel=INFO

将镜像名称和版本替换为实际值。如果镜像有自定义 ENTRYPOINT,还需确认它如何处理这组参数。

Celery 支持通过模块或包自动寻找应用,因此使用 -A proyecto 或 -A proyecto.celery 本身并不代表命令错误。明确写成 模块:实例,可以减少入口解析的歧义。

在实际报错的容器中检查导入结果

在 worker 容器中,使用与启动 worker 相同的 Python 环境运行:

python - <<'PY'
import importlib

from celery import Celery

module = importlib.import_module("proyecto.celery")
app = module.app

print("module file:", module.__file__)
print("app type:", type(app))
print("is Celery:", isinstance(app, Celery))
print("has user_options:", hasattr(app, "user_options"))
PY

确认以下几点:

  • module.__file__ 指向当前部署的项目文件。
  • module.app 是 Celery 实例。
  • 后续代码没有把 app 重新赋值为其他对象。

如果导入时直接抛出异常,先根据完整 traceback 处理导入错误,尤其要检查 settings.py、proyecto/__init__.py、celery.py 和任务模块之间是否存在循环导入。

也要检查启动脚本及 CELERY_APP 等环境配置,确认它们指向正确的对象。项目根目录应避免出现会遮蔽第三方库的 celery.py 文件或 celery/ 目录;proyecto/celery.py 本身属于正常的项目结构。

Docker Swarm 中要按任务实例核对

日志中的这些前缀对应不同的 Swarm 任务:

mvc_worker.2.n430upc71wra@docker-6
mvc_worker.2.ks7d5niodjfq@docker-4
mvc_worker.1.m5xkx8xasgww@docker-6

一个实例成功连接 Redis 时,另一个实例仍可能因应用入口错误而退出。先在 Swarm 管理节点执行:

docker service ps --no-trunc mvc_worker

docker service inspect mvc_worker \
  --format '{{json .Spec.TaskTemplate.ContainerSpec.Image}}'

docker service inspect mvc_worker \
  --format '{{json .Spec.TaskTemplate.ContainerSpec.Command}} {{json .Spec.TaskTemplate.ContainerSpec.Args}}'

docker service logs --timestamps --tail 200 mvc_worker

核对当前任务的状态和退出原因,检查服务实际配置的镜像与启动参数。进入容器排查时,要在该任务所在的节点操作。

如果代码随镜像发布,修改本地文件后还需重新构建、推送镜像并更新服务。建议使用明确的版本标签或镜像摘要,核对各节点实际运行的镜像,并检查是否有挂载目录覆盖了镜像内的代码。

Warm shutdown 表示 worker 正在温和关闭,可能与服务更新、重启或停止信号有关。判断关闭原因,需要结合该任务前后的日志和 Swarm 状态。

worker 正常启动后,验证任务是否进入同一条队列

如果启动异常已经消失,任务仍不执行,就继续检查生产者和消费者的配置。

核对 Django 实际使用的配置

使用 namespace="CELERY" 时,Django 设置中的配置名应带 CELERY_ 前缀,例如:

CELERY_BROKER_URL = "redis://redis:6379/0"
CELERY_RESULT_BACKEND = "redis://redis:6379/0"

分别在 Django Web 容器和 worker 容器中核对实际加载的 broker 配置,确认它们连接的是同一个 Redis 服务、端口和数据库。结果后端负责保存任务状态与结果,排查任务投递是否一致时,应优先检查 broker。

还要确认没有意外开启:

CELERY_TASK_ALWAYS_EAGER = True

开启后,任务会在调用进程中执行,不会交给 worker。

核对任务注册与队列路由

日志中的 [tasks] 已列出多个 tienda.tasks.*,说明输出这段日志的 worker 注册了这些任务。用以下命令确认当前运行副本的状态:

celery -A proyecto.celery:app inspect ping
celery -A proyecto.celery:app inspect registered
celery -A proyecto.celery:app inspect active_queues

运行这些命令的环境应能访问同一 broker,并加载同一应用配置。如果没有响应,还需检查 worker 是否存活、远程控制是否可用,以及是否发生响应超时。仅凭没有响应,无法认定任务未注册。

当前日志显示 worker 监听 celery 队列。如果 CELERY_TASK_ROUTES 或 apply_async(queue=...) 将任务发往其他队列,就要让 worker 消费对应队列,例如:

celery -A proyecto.celery:app worker \
  --loglevel=INFO \
  -Q celery,purchases

purchases 仅为示例,应替换为实际路由使用的队列名。

从 Web 容器发送一个最小任务

在已加入 INSTALLED_APPS 的 tienda 应用中增加诊断任务:

# tienda/tasks.py
from celery import shared_task


@shared_task
def celery_probe():
    return "ok"

部署后,在 Web 容器中执行:

python manage.py shell
from tienda.tasks import celery_probe

result = celery_probe.delay()
print(result.id)

随后按这个任务 ID 查看 worker 日志:

  • 没有 received:优先检查实际投递的 broker、队列路由和当前消费者。
  • 有 received,随后失败:根据任务执行 traceback 排查业务代码、数据库或外部服务。
  • 出现 succeeded:说明这次测试的投递和执行已走通,再检查具体业务任务的调用条件与参数。

拿到任务 ID 本身不能证明 worker 已执行任务。日志中的 task events: OFF 只表示未启用相应的事件监控,不会阻止任务执行。

现有 traceback 首先指向应用对象加载错误,还没有足够证据将其归因于 Python 或 Celery 的版本兼容性。如果修正入口后仍有异常,再逐个容器核对实际依赖版本及对应的支持范围。

备注:内容仅供参考。