django-crontab 是轻量定时任务的优选方案,适用于低频、无高并发场景,依赖系统 crond 调度 management commands,需正确配置环境变量、路径及日志,并避免误用内存型定时器。

用 django-crontab 替代 Celery 做轻量定时任务
直接结论:如果任务频率不高(如每小时/每天执行一次)、不涉及高并发或复杂依赖,django-crontab 是比 Celery 更轻、更易部署的方案。它不引入消息队列和 worker 进程,靠系统 crond 调度 manage.py crontab 命令触发 Django 环境下的 Python 函数。
常见错误是把 django-crontab 当成“纯 Python 定时器”——它本质是 Django 和系统 crontab 的胶水层,必须确保部署机上 crond 服务运行且有权限写入用户 crontab。
- 安装后必须运行
python manage.py crontab add才会真正写入系统 crontab;删任务要用crontab remove,不能手动删 crontab 行 - 任务函数必须定义在
management/commands/下(如myapp/management/commands/send_daily_report.py),否则crontab add扫描不到 - 环境变量问题:Django settings 加载依赖
DJANGO_SETTINGS_MODULE,推荐在 crontab 条目里显式设置,例如:*/5 * * * * cd /path/to/project && DJANGO_SETTINGS_MODULE=myproject.settings python manage.py send_daily_report
自定义 management command + 系统 crontab 手动配置
当 django-crontab 的自动管理不可控(比如容器环境无 root 权限改 crontab),就退回到最原始但最稳的方式:写一个 Command 类,再手动把调度规则加进用户 crontab。
关键点在于让 command 可被 shell 环境干净调用:
- command 类必须继承
BaseCommand,且实现handle(self, *args, **options);不要在__init__或模块顶层做耗时初始化 - 避免硬编码路径,用
settings.BASE_DIR拼接文件路径;数据库操作要捕获OperationalError(比如 PostgreSQL 连接池满) - 调试时用
python manage.py my_command --verbosity=2查看输出;生产 crontab 中建议重定向日志:0 2 * * * cd /app && /usr/local/bin/python manage.py send_daily_report >> /var/log/django/cron.log 2>&1
用 APScheduler 在 Django 启动时内嵌调度器
APScheduler 可以跑在 Django 的 WSGI 进程里,适合开发机或单实例部署,但要注意它不是为多进程 Web 服务器(如 gunicorn 多 worker)设计的——每个 worker 都会起一份 scheduler,导致任务重复执行。
若坚持用,必须满足两个条件:
- 只在主进程启动 scheduler:检查
os.environ.get('GUNICORN_CMD_ARGS')或用psutil.Process().parent().pid == 1判断是否为 worker 子进程 - 用
SQLAlchemyJobStore或RedisJobStore替换默认内存 store,否则重启后任务丢失;Django 自带数据库可复用,但需额外装apscheduler[sqlalchemy] - 别把 scheduler 加到
apps.py的ready()里——Django admin、shell、test 都会触发,造成干扰;建议放在wsgi.py底部,加if os.environ.get('RUN_MAIN') == 'true'保护
为什么不用 threading.Timer 或 asyncio.create_task?
因为它们生命周期绑定到当前请求或当前线程,无法跨请求持久存在,也不受 Django 生命周期管理。你写的 Timer 在下一次 HTTP 请求进来前可能已被 GC,或者在 gunicorn reload 时直接消失。
典型翻车场景:
- 在视图函数里 start 一个
threading.Timer,期望 5 分钟后发邮件——结果用户关掉页面、worker 被 kill、Django reload,timer 就断了 - 用
asyncio.create_task在 ASGI app 中调度,但没 await、没加到 event loop 全局引用,task 被静默丢弃 - 所有这类“内存中定时器”都不具备故障恢复能力:进程崩溃后任务不会自动重试,也无法查看历史执行状态
真正的定时任务需要外部可观测性、持久化存储和独立调度平面——哪怕只是系统 crond,也比 Python 内存里的对象可靠得多。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











