Uwsgi+Django多进程下Apscheduler定时任务动态添加、任务重复执行及解决定时任务中高并发的问题(适用于分布式)

Uwsgi+Django多进程下Apscheduler定时任务动态添加、任务重复执行及解决定时任务中高并发的问题(适用于分布式)

因为uwsgi+django启用多进程的情况下,每一个进程是单独,但是apscheuler定时任务执行器的实例需要在多进程下实现共享,很多人想到共享可能会选择方案:1.存储的方式(各种db、redis),2.队列,共享内存等等方案…存储的方式需要的条件:实例是需要可被序列化的,但是apscheuler定时任务执行器的实例是不可被序列化的,所以第一种方案不可行。队列的方式可以实现,但是使用uwsgi启动,每个进程是需要单独的去维护队列,维护成本较高;共享内存的方式可以完美解决,但是因为定时任务的数量不可预测,容易导致内存溢出。关于这上面的解决方案,我都踩过坑,最后使用了一个分布式中常用的方式,使用redis锁的方式,实现原理如下:
每一个进程中都运行一个定时任务的实例,至于哪些进程执行实例,就通过谁先获得redis锁,谁就执行定时任务,没获得锁的任务不执行。
首先,uwsgi的的配置uwsgi.ini的配置如下(只贴出核心部分):

processes=4   
enable-threads = true

django项目的wsgi文件配置如下:

try:
    from test.regJob import scheduler
    scheduler.start()
except Exception as rel:
    print("scheduler start error:",rel)

scheduler定时任务,上文中的regJob.py:

@contextmanager
def redisLock(name, timeout=240):
	#redis
    cache=get_redis_connection("default")
    today_string = datetime.datetime.now().strftime("%Y-%m-%d")
    key = f"Lock.{name}.{today_string}"
    try:
        lock = cache.set(key, value=1, nx=True, ex=timeout)
        yield lock
    finally:
        print("out Lock.................")
        cache.delete(key) # 释放锁
        print("out Lock OK")
scheduler=BackgroundScheduler()
#定时任务的存储器可以使用:DjangoJobStore(),当然你也可以自定义
scheduler.add_jobstore(DjangoJobStore(), 'default')

def myJob(arginfo):
    with redisLock(arginfo) as lock:
        if lock:
            print("Locking................",lock)
            #此处就是你需要执行的任务
        else:
            print("unLocking...............")
            #这个地方是必须的,因为释放锁的时候是需要加延时的,因为任务多进程中高并发的情况下,可能会出现在其他的进程在开始获取锁的时候,有的进程已经释放了锁,这样就会导致定时任务重复执行
            time.sleep(10)

然后在项目app其他的地方调用scheduler这个实例进行增加任务、删除任务、暂停任务都是可以的

scheduler.add_job(myJob,cron,day_of_week=dayOfWeek, hour=hour, minute=minute, second=second, id=id,args=[arginfo],coalesce=True,misfire_grace_time=3600)
#其中关于coalesce和misfire_grace_time函数请见Apscheduler官方文档说明,再次不赘述

最后:原理就是所有线程都存在scheduler,每个scheduler都是互相隔离的,但是通过redis锁的方式决定,获得到锁的进程中去执行定时任务,而没有获得到锁的进程中就不去执行,并且在添加新任务的时候,在改进程中添加的定时任务如果在执行器没有休眠的时候,那么执行器就会执行该任务,如果休眠,每个进程中重新唤醒的执行器会重新去争夺redis锁来确定执行任务的先后顺序。

另解决一个Apscheduler报错问题,因为在uwsgi是启用的多进程,然后每个进程中都存在一个执行器的实例,在定时任务的数据表django_apscheduler_djangojobexecution中每一个任务其实是有4个实例,并且会报一个get() returned more than one %s – it returned %s的一个报错,其实这个报错的原因是因为:他使用的django的orm的get方法,因为get如果获取到的是多条而不是唯一就会报错,通过查询源码,修改如下:
找到django的包,方法重写:
/home/虚拟环境地址/lib/python3.8/site-packages/django/db/models/query.py
找到

    def get(self, *args, **kwargs):
        """
        Perform the query and return a single object matching the given
        keyword arguments.
        """
        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
        if self.query.can_filter() and not self.query.distinct_fields:
            clone = clone.order_by()
        limit = None
        if not clone.query.select_for_update or connections[clone.db].features.supports_select_for_update_with_limit:
            limit = MAX_GET_RESULTS
            clone.query.set_limits(high=limit)
        num = len(clone)
        #此处num==1改成num>=1就好
        if num >= 1:
            return clone._result_cache[0]
        if not num:
            raise self.model.DoesNotExist(
                "%s matching query does not exist." %
                self.model._meta.object_name
            )
        raise self.model.MultipleObjectsReturned(
            'get() returned more than one %s -- it returned %s!' % (
                self.model._meta.object_name,
                num if not limit or num < limit else 'more than %s' % (limit - 1),
            )
        )
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值