共享数据库的两个Django项目django-celery-beat周期任务冲突解决咨询
问题描述
两个共享同一PostgreSQL数据库的Django项目,均已部署Celery和django-celery-beat应用。当前出现问题:A项目的Celery Worker会收到B项目的周期任务,判定为未注册任务并标记为REJECTED。
推测原因是两个项目的django-celery-beat共享了数据库中的同一张调度表——两个项目为容器化部署,各自拥有独立Docker网络、不同端口的Redis服务器,且已配置专属默认队列名称。
设想每个项目需拥有独立的django-celery-beat实例,是否可通过AppConfig实现,再通过migrate生成专属表?预期配置如下:
项目A的settings.py:
INSTALLED_APPS = [ ... 'django_celery_beat_project_a', ... ]
项目B的settings.py:
INSTALLED_APPS = [ ... 'django_celery_beat_project_b', ... ]
附错误日志:
project-a-celery_worker-1 | 2022-09-09T11:35:15.974463294Z Received unregistered task of type 'trading.tasks.bulk_trade'. project-a-celery_worker-1 | 2022-09-09T11:35:15.974621504Z The message has been ignored and discarded. project-a-celery_worker-1 | 2022-09-09T11:35:15.974655749Z project-a-celery_worker-1 | 2022-09-09T11:35:15.974677303Z Did you remember to import the module containing this task? project-a-celery_worker-1 | 2022-09-09T11:35:15.974699464Z Or maybe you're using relative imports? project-a-celery_worker-1 | 2022-09-09T11:35:15.974721029Z project-a-celery_worker-1 | 2022-09-09T11:35:15.974741804Z Please see project-a-celery_worker-1 | 2022-09-09T11:35:15.974782954Z http://docs.celeryq.org/en/latest/internals/protocol.html project-a-celery_worker-1 | 2022-09-09T11:35:15.974815238Z for more information. project-a-celery_worker-1 | 2022-09-09T11:35:15.974839132Z project-a-celery_worker-1 | 2022-09-09T11:35:15.974897495Z The full contents of the message body was: project-a-celery_worker-1 | 2022-09-09T11:35:15.974938229Z b'[[], {}, {"callbacks": null, "errbacks": null, "chain": null, "chord": null}]' (77b) project-a-celery_worker-1 | 2022-09-09T11:35:15.974982482Z project-a-celery_worker-1 | 2022-09-09T11:35:15.975032349Z The full contents of the message headers: project-a-celery_worker-1 | 2022-09-09T11:35:15.975090058Z {'lang': 'py', 'task': 'trading.tasks.bulk_trade', 'id': 'd86620c2-04ab-4b59-bb2e-77685389f8d5', 'shadow': None, 'eta': None, 'expires': None, 'group': None, 'group_index': None, 'retries': 0, 'timelimit': [None, None], 'root_id': 'd86620c2-04ab-4b59-bb2e-77685389f8d5', 'parent_id': None, 'argsrepr': '()', 'kwargsrepr': '{}', 'origin': 'gen9@03298c28290e', 'ignore_result': False} project-a-celery_worker-1 | 2022-09-09T11:35:15.975125751Z project-a-celery_worker-1 | 2022-09-09T11:35:15.975194550Z The delivery info for this task is: project-a-celery_worker-1 | 2022-09-09T11:35:15.975257110Z {'exchange': '', 'routing_key': 'project-a_queue_1'} project-a-celery_worker-1 | 2022-09-09T11:35:15.981241271Z exc_info=(<class 'KeyError'>, KeyError('trading.tasks.bulk_trade'), <traceback object at 0x7fe49ca1b8c0>) project-a-celery_worker-1 | 2022-09-09T11:35:15.996976839Z [error ] task_not_found [django_structlog.celery.receivers] message=<Message object at 0x7fe49cb6e680 with details {'state': 'REJECTED', 'content_type': 'application/json', 'delivery_tag': '43925569-3d7a-4272-9629-2e08581f6579', 'body_length': 77, 'properties': {'correlation_id': 'd86620c2-04ab-4b59-bb2e-77685389f8d5'}, 'delivery_info': {'exchange': '', 'routing_key': 'project-a_queue_1'}}>
解决方案
方案1:自定义AppConfig实现独立表(你的思路落地)
该方案通过复制django-celery-beat源码并修改,为每个项目生成专属表:
- 复制django_celery_beat源码到项目目录,分别重命名为
django_celery_beat_project_a(项目A)和django_celery_beat_project_b(项目B) - 打开每个副本的
models.py,给所有模型的Meta类添加db_table属性,指定专属表名,示例:class PeriodicTask(models.Model): # 原有字段保留 class Meta: db_table = 'project_a_periodic_task' unique_together = ('name',) - 修改每个副本的
apps.py,更新AppConfig的名称:from django.apps import AppConfig class DjangoCeleryBeatProjectAConfig(AppConfig): name = 'django_celery_beat_project_a' verbose_name = 'Django Celery Beat (Project A)' - 在项目的
settings.py中,将原INSTALLED_APPS里的django_celery_beat替换为自定义应用名 - 分别在两个项目中执行
python manage.py makemigrations和python manage.py migrate,生成专属表
方案2:使用原生表前缀配置(推荐)
django-celery-beat原生支持通过配置项给所有表添加前缀,无需复制源码,更简洁易维护:
- 项目A的settings.py添加:
CELERY_BEAT_DB_TABLE_PREFIX = 'project_a_' - 项目B的settings.py添加:
CELERY_BEAT_DB_TABLE_PREFIX = 'project_b_' - 分别在两个项目中执行
python manage.py migrate,系统会自动生成带前缀的独立表(如project_a_periodictask、project_b_crontabschedule等)
方案3:队列隔离+Worker专属监听(补充)
结合队列配置,确保周期任务只发送到对应项目的队列,Worker只监听自身队列:
- 在每个项目的settings.py中配置任务路由:
CELERY_TASK_ROUTES = { 'trading.tasks.*': {'queue': 'project_a_queue_1'}, # 项目A配置 # 项目B对应配置为自己的队列 } - 启动Worker时明确指定监听的队列:
# 项目A启动命令 celery -A project_a worker -l info -Q project_a_queue_1 # 项目B启动命令 celery -A project_b worker -l info -Q project_b_queue_1
该方案可与方案2结合,彻底隔离两个项目的周期任务调度流程。
内容的提问来源于stack exchange,提问作者Florent
相关产品推荐
相关产品推荐

