我有一个需要在我的某个Web应用程序后台运行的函数。
我实现了一个自定义AppConfig,如下所示:
class MyAppConfig(AppConfig):
run_already = False
def ready(self):
from .tasks import update_products
if "manage.py" not in sys.argv and not self.run_already:
self.run_already = True
update_products()
但是,此命令正在执行两次(update_products()调用)
As stated in the documentation:
在通常的初始化过程中,只调用ready方法 曾经是Django。但在某些极端情况下,特别是在测试中 正在摆弄已安装的应用程序,准备就绪可能会更多 不止一次。在这种情况下,要么写出幂等方法,要么放一个 在AppConfig类上标记以防止重新运行应该的代码 只执行一次。
我觉得我正在按照文档说的去做。是什么给了什么?
答案 0 :(得分:7)
正如answer所述,如果您正在使用Django上的python manage.py runserver
命令运行您的应用,您的应用将运行两次:一次验证您的模型,另一次验证一个运行你的应用程序。
您可以将此选项--noreload
更改为runserver命令。
答案 1 :(得分:2)
在heroku上,gunicorn是由一名以上的炮手开始的。将WEB_CONCURRENCY
设置为1
:
heroku config:set WEB_CONCURRENCY=1
答案 2 :(得分:0)
没有旗帜在班级上有效。 Django在两个独立的进程上运行两次。两个独立进程上的类级变量彼此不可见。使用数据库表中的标志,如此代码中所示(SchedulerUtils是由我编写的类,使用方法go()启动backgroud apscheduler调度程序。模型使用表scheduler_schedulerinfo中的一行,因此您必须在此之前插入此行: “INSERT INTO scheduler_schedulerinfo(started)values(0);”):
################################## APPS.PY
import os
from django.apps import AppConfig
from apscheduler.schedulers.background import BlockingScheduler, BackgroundScheduler
from scheduler.utils import SchedulerUtils
class SchedulerConfig(AppConfig):
name = 'scheduler'
def ready(self):
startScheduler = True
pid = os.getpid()
#check i'm on heroku
if (os.environ.get("DYNO")):
# i'm on heroku, here runs twice
print("[%s] DYNO ENV exists, i'm on heroku" % pid)
from scheduler.models import SchedulerInfo
schedInfo = SchedulerInfo.objects.all().first()
if (schedInfo.started == 0):
print("[%s] Scheduler not started, starting.... " % pid)
startScheduler = True
# set flag to 1
SchedulerInfo.objects.all().update(started = 1)
else:
print("[%s] Scheduler already running, not starting." % pid)
startScheduler = False # already running
# reset to 0 for next time
SchedulerInfo.objects.all().update(started = 0)
# PRINT FLAG VALUE
from scheduler.models import SchedulerInfo
schedInfo = SchedulerInfo.objects.all().first()
print("[%s] Value of flag schedulerinfo.started: %d" % (pid, schedInfo.started))
if (startScheduler):
su = SchedulerUtils()
su.go()
##################################### MODELS.PY
from django.db import models
class SchedulerInfo(models.Model):
started = models.IntegerField(default=0)
答案 3 :(得分:0)
另一个解决方案可能是检查gunicorn pids如下:
import os
from django.apps import AppConfig
import psutil
class SchedulerConfig(AppConfig):
name = 'scheduler'
# I want to start ths scheduler only once,
# if WEB_CONCURRENCY is set and is greater than 1
# start the scheduler if the pid of this gunicorn is the same of the
# maximum pid of all gunicorn processes
def ready(self):
startScheduler = True
#check WEB_CONCURRENCY exists and is more than 1
web_concurrency = os.environ.get("WEB_CONCURRENCY")
if (web_concurrency):
mypid = os.getpid()
print("[%s] WEB_CONCURRENCY exists and is set to %s" % (mypid, web_concurrency))
gunicorn_workers = int(web_concurrency)
if (gunicorn_workers > 1):
maxPid = self.getMaxRunningGunicornPid()
if (maxPid == mypid):
startScheduler = True
else:
startScheduler = False
if (startScheduler):
print("[%s] WILL START SCHEDULER", mypid)
else:
print("[%s] WILL NOT START SCHEDULER", mypid)
def getMaxRunningGunicornPid(self):
running_pids = psutil.pids()
maxPid = -1
for pid in running_pids:
proc = psutil.Process(pid)
proc_name = proc.name()
if (proc_name == "gunicorn"):
if (maxPid < pid):
maxPid = pid
print("Max Gunicorn PID: %s", maxPid)
return maxPid
答案 4 :(得分:0)
发现AppConfig被触发了两次,这导致调度程序通过这种设置被初始化了两次。而是像这样在url.py中实例化调度程序-
urlpatterns = [
path('api/v1/', include(router.urls)),
path('api/v1/login/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/v1/login/refresh/', jwt_views.TokenRefreshView.as_view(), name='token_refresh'),
path('api/v1/', include('rest_registration.api.urls'))
]
scheduler = BackgroundScheduler()
scheduler.add_job(task.run, trigger='cron', hour=settings.TASK_RUNNER_HOURS, minute=settings.TASK_RUNNER_MINUTES, max_instances=1)
scheduler.start()
这样,调度程序仅实例化一次。问题已解决。