Django uwsgi apscheduler定时任务重复执行问题解决

1、现状问题

为了满足用户动态添加定时任务,因此选择apscheduler模块,apscheduler支持cron指令形式的定时任务,可通过api接口实现动态添加定时任务。
问题:
django uwsig部署apscheduler定时任务重复执行问题,由于uwsgi启动多个worker,导致每个worker执行一次定时任务,各种google、baidu搜索结果都大差不差,自定义的定时任务可解决重复执行问题,但是动态添加定任务重复执行问题始终未能解决!

2、apscheduler部署过程

  • uwsgi.ini文件配置:(只列出关键的配置)
    #由于GIL的存在,uwsgi索性默认不支持多线程,不对GIL进行初始化。但如果希望程序中的线程发挥作用,需要加入
    enable-threads=true
  • -在django项目wsgi.py文件同级目录下:创建apscheduler_start.py文件
    在这里插入图片描述
    该文件中是启动apscheduler定时任务的逻辑:(具体apscheduler使用方法这里不做说明)
    apscheduler_start.py:
import logging
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
from apscheduler.schedulers.background import BackgroundScheduler
from django_apscheduler.jobstores import DjangoJobStore

logger = logging.getLogger('scheduler_log')
logger.setLevel(logging.DEBUG)

jobstores = {
    'default': DjangoJobStore()
}
job_defaults = {
    'coalesce': True,
    'max_instances': 1,
    'misfire_grace_time': 60,
    'replace_existing': True
}

scheduler = BackgroundScheduler(jobstores=jobstores, job_defaults=job_defaults)
# pool_pre_ping为True,即每次从连接池中取连接的时候,都会验证一下与数据库是否连接正常,如果没有连接,那么该连接会被回收。
# engine_options={'pool_pre_ping': True, 'pool_recycle': 100}
scheduler.start()


def listener_event(event):
    job_id = event.job_id
    scheduled_run_time = event.scheduled_run_time.strftime("%Y-%m-%d %H:%M:%S")
    if event.exception:
        logger.error('作业ID:{} 在 {} 执行失败,错误原因:{}'.format(job_id, scheduled_run_time, event.exception.args[0]))


scheduler._logger = logger
# 当任务执行完或任务出错时,listener_event
scheduler.add_listener(listener_event, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)


# 使用main_loop阻塞进程,防止进程挂起
# import time
# while True:
#     time.sleep(60)
#     sig = uwsgi.signal_wait()
  • 通过api请求动态添加定时任务的逻辑代码apscheduler_task.py:
# 引入apscheduler_start 中的 scheduler
from ops.apscheduler_start import scheduler
from django.views import View
from django.utils.decorators import method_decorator

class CrontabTask(View):
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        res = super().dispatch(request, *args, **kwargs)
        return res

    def get(self, request):
        pass
    def post(self, request):
        content = json.loads(request.body.decode())
        try:
            # crontab_time 页面录入crontab指令,eg: */3 * * * * *
            job_time = content['crontab_time']
            crontab_time = job_time.split(' ')
            crontab_obj = ['minute', 'hour', 'day', 'month', 'day_of_week', 'year']
            crontab_dict = dict(zip(crontab_obj, crontab_time))
            # func需要定时执行的函数
            job = scheduler.add_job(func, trigger='cron', second='0', minute=crontab_dict['minute'],
                                    hour=crontab_dict['hour'], day=crontab_dict['day'], month=crontab_dict['month'],
                                    day_of_week=crontab_dict['day_of_week'],
                                    year=crontab_dict.get('year', '*'), args=[_job_id], coalesce=True, max_instances=1, misfire_grace_time=300, replace_existing=True)
        except Exception as e:
            logger.error(e)
        # 此处忽略return response代码
        
    def delete(self, request):
        pass
    def put(self, request):
        pass

此时通过api接口可成功添加定时任务, 但是会存在重复执行问题

3、解决重复执行问题

通过redis_lock锁实现

# 下载redis_lock
# pip install python-redis-lock
import redis_lock

# 使用锁的案列
# conn redis链接对象; lock-key:写入redis的key
lock = redis_lock.Lock(conn, "lock-key")
if lock.acquire(blocking=False):
    print("Got the lock.")
    # 此处为上述动态添加接口中:定时执行func的逻辑
    ...
    # 最后需要手动释放掉锁
    lock.release()
else:
    print("Someone else has the lock.")

4、apscheduler 跟随 uwsgi重启自动重启

  • 踩坑点:由于apscheduler需要手动触发重启,即在页面添加定时任务时,方可触发重启,导致定时任务执行的函数可能存在执行旧代码的情况!!! uwsgi重启时,apscheduler不会跟着重启!要保证uwsgi和apscheduler同步重启!
  • 解决方案:
    django项目中在apscheduler_start.py文件同级目录中__init__.py文件中添加:
import django
django.setup()
from ops.apscheduler_start import scheduler

__all__ = ('scheduler',)

此时重启uwsgi服务,apscheduler会自动重启。
**备注一下:**大部分是在wsgi.py文件中添加的,我尝试过,添加后定时任务会重启,但是django项目访问504,测试过多次,依然不行,不知道是不是方法不对。。。

5、较好的优化解决方案

通过类似celery的方式将django_apscheduler定时任务独立出来单独启动一套服务,设定单进程运行。这种方法解决了重复执行的问题,同时解放了django后台并发限制,并且可以处理即时的异步任务。(此处只有纯描述, 重要点:uwsgi中的mule模块!!!https://uwsgi-docs-zh.readthedocs.io/zh_CN/latest/Mules.html)

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个不知名的奋斗男孩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值