You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django+APScheduler部署Gunicorn多Worker时任务重复执行问题求助

解决Django+Gunicorn多Worker下APScheduler任务重复执行的问题

问题根源

Gunicorn的每个Worker都是独立进程,会各自触发Django AppConfig的ready()方法,导致每个Worker都初始化并启动APScheduler,最终任务被多次执行。

可行解决方案

方案1:TCP Socket绑定互斥(你提到的方案)

利用同一TCP端口只能被单个进程绑定的特性,让只有第一个成功绑定端口的Worker启动调度器,其他Worker绑定失败则跳过。

修改你的调度模块代码:

from apscheduler.schedulers.background import BackgroundScheduler
from RENDER.views import dailypuzzlefunc
import socket

def start():
    # 选择一个本地未被占用的端口(比如9001,可自行修改)
    lock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        # 设置SO_REUSEADDR避免进程退出后端口处于TIME_WAIT状态无法立即复用
        lock_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定本地端口,只有第一个进程能成功
        lock_socket.bind(('127.0.0.1', 9001))
        
        # 绑定成功,启动调度器
        scheduler = BackgroundScheduler()
        scheduler.add_job(dailypuzzlefunc, 'cron', day="*", max_instances=2, id='dailyscheduler')
        scheduler.start()
    except socket.error:
        # 端口已被绑定,说明已有Worker启动了调度器,当前Worker跳过
        pass
    finally:
        # 不需要保持连接,关闭socket
        lock_socket.close()

方案2:文件锁互斥

通过文件锁实现进程间互斥,只有获取到锁的Worker才启动调度器,无需占用端口。

修改调度模块代码:

from apscheduler.schedulers.background import BackgroundScheduler
from RENDER.views import dailypuzzlefunc
import fcntl
import os

def start():
    # 定义锁文件路径(建议放在项目根目录或者临时目录)
    lock_file_path = '/tmp/django_apscheduler_lock'
    lock_file = open(lock_file_path, 'w')
    
    try:
        # 获取排他锁,非阻塞模式
        fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
        
        # 获取锁成功,启动调度器
        scheduler = BackgroundScheduler()
        scheduler.add_job(dailypuzzlefunc, 'cron', day="*", max_instances=2, id='dailyscheduler')
        scheduler.start()
    except BlockingIOError:
        # 锁已被其他进程持有,当前Worker跳过
        pass
    # 不需要手动释放锁,进程退出后锁会自动释放

方案3:基于Django ORM的数据库互斥锁

利用数据库的唯一性约束,创建一个调度器状态表,确保只有一个Worker能初始化调度器,适合依赖数据库的场景。

  1. 先在你的APP下创建模型:
from django.db import models

class SchedulerLock(models.Model):
    # 唯一标识锁的字段,确保只有一条记录
    lock_key = models.CharField(max_length=50, unique=True, default='apscheduler_lock')
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        verbose_name = "调度器锁"
        verbose_name_plural = "调度器锁"
  1. 执行迁移python manage.py makemigrations和python manage.py migrate创建表。

  2. 修改调度模块代码:

from apscheduler.schedulers.background import BackgroundScheduler
from RENDER.views import dailypuzzlefunc
from YOUR_APP.models import SchedulerLock
from django.db import IntegrityError

def start():
    try:
        # 尝试创建锁记录,唯一约束会确保只有第一个进程能成功
        SchedulerLock.objects.create()
        
        # 创建成功,启动调度器
        scheduler = BackgroundScheduler()
        scheduler.add_job(dailypuzzlefunc, 'cron', day="*", max_instances=2, id='dailyscheduler')
        scheduler.start()
    except IntegrityError:
        # 记录已存在,说明已有Worker启动调度器,当前Worker跳过
        pass

注意事项

  • 所有方案都不需要修改Gunicorn配置,保持多Worker模式即可,代码变更后也能正常重载(不需要--preload)。
  • 如果使用Socket或文件锁,需要确保部署环境的端口/文件路径没有被其他服务占用,且进程有读写权限。
  • 数据库锁方案依赖Django ORM,需要确保数据库连接正常,且迁移已执行。

内容的提问来源于stack exchange,提问作者ControltheAI

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 18:35:40