我正在尝试在 Elastic Beanstalk 上为我的 Django 应用程序设置一个日常任务。似乎没有一种可接受的方法来设置它,因为芹菜节拍是 Django 中周期性任务的首选解决方案,但对于负载平衡环境来说并不是很好。
我已经看到一些解决方案做了一些事情,例如使用 leader_only=True 设置芹菜节拍,只运行一个实例,但这留下了一个单点故障。我已经看到其他解决方案允许许多芹菜节拍实例并使用锁来确保只有一个任务通过,但是除非重新启动失败的实例,否则这最终不会完全失败吗?我看到的另一个建议是有一个单独的实例来运行芹菜节拍,但这仍然是一个问题,除非它有某种方法在失败时重新启动自己。
这个问题有什么像样的解决方案吗?我宁愿不必照看调度程序,因为很容易不会注意到我的任务直到一段时间后才运行。
如果您使用 redis 作为代理,请考虑安装 RedBeat 作为芹菜节拍调度程序: https://github.com/sibson/redbeat
此调度程序使用 redis 中的锁定来确保只有一个节拍实例在运行。有了这个,您可以在每个节点的工作进程上启用 beat 并删除 leader_only=True
的使用。
celery worker -B -S redbeat.RedBeatScheduler
假设您有带节拍锁的工作器 A 和带节拍锁的工作器 B。如果工作线程 A 死亡,工作线程 B 将在可配置的时间量后尝试获取节拍锁。
我建议制作一个使用 cron 运行的管理命令。
使用这种方法,你有完整的Django ORM,所有方法,等等。将脚本包装在 try/except 中,您可以选择以您希望的任何方式记录失败 - 电子邮件通知、外部日志记录系统(如 Sentry)、直接发送到数据库等。
我用户监督运行 cron,它运行良好。它依赖于久经考验的工具,不会让您失望。
最后,使用数据库单例来跟踪批处理作业是否已运行或当前是否正在运行,并且有多个 Django 实例运行负载平衡,这不是不好的做法,即使您对此感到有点讨厌。数据库是告诉您数据库是否正在处理的非常可靠的方法。
关于cron的一个烦人的事情是它不会导入Django可能需要的环境变量。我用一个简单的Python脚本解决了这个问题。
它在启动时写入 crontab,其中包含所需的环境变量等。此示例适用于 EBS 上的 Ubuntu,但应该是相关的。
#!/usr/bin/env python
# run-cron.py
# sets environment variable crontab fragments and runs cron
import os
from subprocess import call
from master.settings import IS_AWS
# read django's needed environment variables and set them in the appropriate crontab fragment
eRDS_HOSTNAME = os.environ["RDS_HOSTNAME"]
eRDS_DB_NAME = os.environ["RDS_DB_NAME"]
eRDS_PASSWORD = os.environ["RDS_PASSWORD"]
eRDS_USERNAME = os.environ["RDS_USERNAME"]
try:
eAWS_STAGING = os.environ["AWS_STAGING"]
except KeyError:
eAWS_STAGING = None
try:
eAWS_PRODUCTION = os.environ["AWS_PRODUCTION"]
except KeyError:
eAWS_PRODUCTION = None
eRDS_PORT = os.environ["RDS_PORT"]
if IS_AWS:
fto = '/etc/cron.d/stortrac-cron'
else:
fto = 'test_cron_file'
with open(fto,'w+') as file:
file.write('# Auto-generated cron tab that imports needed variables and runs a python script')
file.write('nRDS_HOSTNAME=')
file.write(eRDS_HOSTNAME)
file.write('nRDS_DB_NAME=')
file.write(eRDS_DB_NAME)
file.write('nRDS_PASSWORD=')
file.write(eRDS_PASSWORD)
file.write('nRDS_USERNAME=')
file.write(eRDS_USERNAME)
file.write('nRDS_PORT=')
file.write(eRDS_PORT)
if eAWS_STAGING is not None:
file.write('nAWS_STAGING=')
file.write(eAWS_STAGING)
if eAWS_PRODUCTION is not None:
file.write('nAWS_PRODUCTION=')
file.write(eAWS_PRODUCTION)
file.write('n')
# Process queue of gobs
file.write('n*/8 * * * * root python /code/app/manage.py queue --process-queue')
# Every 5 minutes, double-check thing is done
file.write('n*/5 * * * * root python /code/app/manage.py thing --done')
# Every 4 hours, do this
file.write('n8 */4 * * * root python /code/app/manage.py process_this')
# etc.
file.write('n3 */4 * * * root python /ode/app/manage.py etc --silent')
file.write('nn')
if IS_AWS:
args = ["cron","-f"]
call(args)
在主管会议中:
[program:cron]
command = python /my/directory/runcron.py
autostart = true
autorestart = false