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 的版本兼容性。如果修正入口后仍有异常,再逐个容器核对实际依赖版本及对应的支持范围。
备注:内容仅供参考。
