任务队列/任务调度:Celery和APScheduler(内含APScheduler实例)

Celery VS APScheduler:

celery: celery是一个专注于实时处理和任务调度的任务队列,任务就是消息(消息队列使用rabbitmq或者redie),消息中的有效载荷中包含要执行任务的全部数据。我们通常将celery作为一个任务队列来使用,但是celery也有定时任务的功能,但是celery无法在flask这样的系统中动态的添加定时任务,而且单独为定时任务功能而搭建celery显得过于重量级。

apscheduler: apscheduler是基于Quartz的一个Python定时任务框架,提供了基于日期、固定时间间隔以及crontab类型的任务,并且可以持久化作业。APScheduler算是在实际项目中最好用的一个工具库,不仅可以在程序中动态的添加和删除定时任务,还支持持久化




消息队列(redis,rabbitmq)

上面的两者都可以看做是任务队列,任务队列是逻辑模型,消息队列是通信模型
任务队列:将抽象的任务发送到执行的worker的组件,用于处理任务。
消息队列MQ:是一种能实现生产者到消费者的单向通信模型,用于通知,传递消息。
任务队列可以需要消息队列来实现,比如redis作为celery的broker。




APScheduler

首先安装pip install apscheduler

APScheduler有四个组件:

  1. triggers: 触发器,用于设定触发任务的条件,触发器包含了调度的逻辑,每个任务都有自己的触发器决定该任务下次运行的时间。
  2. job stores: 任务储存器,用于存放任务,把任务放在内存或者数据库中,一个
  3. executors: 执行器,用于执行任务,可以设定执行模式为单线程或者线程池,任务完毕后,执行器会通知调度器
  4. schedulers: 调度器,上面的三个组件都是参数,使用这三个参数创建调度器实例来运行

调度器的选择:

根据不同的开发需求,选择对应的调度器组件

  1. BlockingScheduler 阻塞式调度器:适用于只跑调度器的程序。
  2. BackgroundScheduler 后台调度器:适用于非阻塞的情况,调度器会在后台独立运行。
  3. AsyncIOScheduler AsyncIO调度器,适用于应用使用AsnycIO的情况。
  4. GeventScheduler Gevent调度器,适用于应用通过Gevent的情况。
  5. TornadoScheduler Tornado调度器,适用于构建Tornado应用。
  6. TwistedScheduler Twisted调度器,适用于构建Twisted应用。
  7. QtScheduler Qt调度器,适用于构建Qt应用。

任务储存器的选择:

如果运行的任务是无状态的,选择默认的任务储存器MemoryJobStore即可。

如果需要程序关闭或者重启的时候,保存任务的状态,那么需要持久化的任务储存器比如SQLAlchemyJobStore配合postgres作为后台数据库。


执行器的选择:

默认的ThreadPoolExecutor线程池执行器方案可以满足大部分需求
如果程序是计算密集型,推荐使用ProcessPoolExecutor进程池执行器使用多核能力,还可以混合使用两种


配置任务触发器:

一共有三种内置的触发器:

  1. date 日期,触发任务运行的具体时间
  2. interval 间隔,触发任务运行的时间间隔
  3. cron 周期,触发任务运行的周期,较复杂,check it in google
from datetime import date
from apscheduler.schedulers.blocking import BlockingScheduler

sched = BlockingScheduler()

def my_job(text):
    print(text)

# date触发器
sched.add_job(my_job, 'date', run_date=date(2009, 11, 6), args=['text'])
# interval触发器
sched.add_job(job_function, 'interval', hours=2)


sched.start()

对于cron,是一个强大的类crontab表达式:

# 任务会在6月、7月、8月、11月和12月的第三个周五,00:00、01:00、02:00和03:00触发
sched.add_job(job_function, 'cron', month='6-8,11-12', day='3rd fri', hour='0-3')


现在创建一个调度器例子:

from apscheduler.schedulers.background import BackgroundScheduler

scheduler = BackgroundScheduler()

# 因为是非阻塞的后台调度器,所以程序会继续向下执行

这样就可以创建了一个后台调度器。这个调度器有一个名称为default的MemoryJobStore(内存任务储存器)和一个名称是default且最大线程是10的ThreadPoolExecutor(线程池执行器)。

然后我们创建一个更复杂的调度器:

from pytz import utc

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.mongodb import MongoDBJobStore
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor


jobstores = {
     
    'mongo': MongoDBJobStore(), # 名字为mongo的MongoDBJobStore
    'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite') # 名字为default的任务储存器
}
executors = {
     
    'default': ThreadPoolExecutor(20), # 最大线程20的线程池
    'processpool': ProcessPoolExecutor(5)
}
job_defaults = {
     
    'coalesce': False,  # 默认为新任务关闭合并模式
    'max_instances': 3  # 设置新任务默认最大实例数为3
}
scheduler = BackgroundScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc)

启动调度器:
启动只需要调用start(),非阻塞调度器都会立即返回,可以继续运行之后的代码,比如添加任务。对于阻塞调度器BlockingScheduler,程序会阻塞在start的位置。并且,调度器启动后就不能修改配置了。

添加任务:
add_job()或者通过装饰器scheduled_job(),需要注意的是,如果是从数据库读取任务,那么必须为每一个任务定义一个明确的ID,并且使用replace_existing=True属性,否则每次重启程序的时候,都会得到一份新的任务拷贝,因为任务的状态不会被保存。

移除任务:
remove_job()参数为任务ID或者任务储存器名字,或者在通过add_job()创建的任务实例上调用remove()方法。第二种方法更好,但是必须是创建任务实例的时候被保存在变量中,如果使用装饰器创建的任务,那么只能选择第一种方法。

scheduler.add_job(myfunc, 'interval', minutes=2, id='my_job_id')
scheduler.remove_job('my_job_id')

暂停和恢复任务:
暂停:
apscheduler.job.Job.pause()
apscheduler.schedulers.base.BaseScheduler.pause_job()
恢复:
apscheduler.job.Job.resume()
apscheduler.schedulers.base.BaseScheduler.resume_job()

获取任务列表:
通过get_jobs()就可以获得一个可修改的任务列表。get_jobs()第二个参数可以指定任务储存器名称,那么就会获得对应任务储存器的任务列表。

修改任务:
通过apscheduler.job.Job.modify()或modify_job(),你可以修改任务当中除了id的任何属性。

关闭调度器:
scheduler.shutdown()
scheduler.shutdown(wait=False)



下面介绍一个使用APScheduler的实例

首先构造一个类继承APScheduler:

class AsyncScheduler(APScheduler):
    def __init__(self, replace_job=False, scheduler=None, app=None):
        self.replace_job = replace_job
        super().__init__(scheduler, app)

    def __add_job(self, job_id, func, args=None, **kw):
        kw['replace_existing'] = self.replace_job
        try:
            if args:
                kw['args'] = args
            ret = self.add_job(job_id, func, **kw)
            reason = 'OK'
        except ConflictingIdError as e:
            ret = None
            reason = str(e)

        return ret, reason

    def add_cron_job(self, job_id, func, args=None, **kw):
        kw['trigger'] = CronTrigger(**kw)
        return self.__add_job(job_id, func, args, **kw)

    def add_interval_job(self, job_id, func, args=None, **kw):
        kw['trigger'] = IntervalTrigger(**kw)
        return self.__add_job(job_id, func, args, **kw)

    def add_once_job(self, job_id, func, args=None, **kw):
        kw['trigger'] = DateTrigger(**kw)
        return self.__add_job(job_id, func, args, **kw)
        
g_scheduler = AsyncScheduler()

然后使用这个APScheduler类:

# 一次性添加异步任务,第一个参数是任务id,第二个参数是func即要完成的具体任务
g_scheduler.add_once_job('manual job', start_sync)

# 添加有间隔的定时任务,参数包含:任务ID,具体任务,定时相关参数
g_scheduler.add_interval_job('auto job', start_sync, args=(1,), weeks=0, days=0, hours=0, minutes=0, seconds=0,start_date=start_date)

# 暂停当前的异步任务,并且从任务调度里面删除该任务
stop_sync()
g_scheduler.delete_job('manual job')  # 根据任务ID删除该任务



Celery

rabbitmq和celery的区别 他们是两个层面的东西,celery是一个分布式的队列,基本工作是管理分配任务到不同的服务器,但是服务器之间如何通信是celery不能解决的,所以rabbitmq作为一个消息队列管理工具被引入到和celery集成,现在crabbitmq在celery中扮演broker的角色,就是消息代理。

celery架构:
任务队列/任务调度:Celery和APScheduler(内含APScheduler实例)_第1张图片
上面的异步任务模块其实也是任务发布者,所以产生任务的方式有两种,一种是发布者发布任务(web应用), 另一种是任务调度按期发布任务(定时任务)

使用的时候其实是从上到下的,broker是一个消息传输的中间件,每当程序调用celery异步任务的时候,会向broker发送消息,然后celery会监听到这个消息,进行任务的执行,至于最下面的backend(数据库,一般使用redis)用于储存这些消息和celery执行的一些消息和结果。

celery是一个自带电池的任务队列
首先celery需要一个发送和接收消息的解决方案,起通常以独立服务形式出现,称之为消息中间人:RabbitMQ,Redis

安装Celery:
pip install celery

应用:
首先需要一个celery实例,这个实例用于开始做任何事情比如创建任务,管理。。它可以被其他的模块导入,创建一个task.py

from celery import Celery
app = Celery('tasks', broker='amqp://guest@localhost//')
@app.task
def add(x, y):
    return x + y

celery的第一个参数是当前模块的名称(必须),第二个参数是中间人关键字参数也就是中间人URL。

运行celery职程服务器:
celery -A tasks worker --loglevel=info

保存结果:
如果我们想要保持追踪任务的状态,celery需要在某个地方储存或者发送这些状态,可以从几个內建的后端选择,

app = Celery('tasks', backend='amqp', broker='amqp://')

后端通过backend参数来指定,如果选择使用配置模块,则通过CELERY_RESULT_BACKEND选项来设置。

配置:
配置可以直接在应用上设置,也可以使用一个独立的配置模块。可以一次性设置对个选项,可以使用update

app.conf.update(
    CELERY_TASK_SERIALIZER='json',
    CELERY_ACCEPT_CONTENT=['json'],  # Ignore other content
    CELERY_RESULT_SERIALIZER='json',
    CELERY_TIMEZONE='Europe/Oslo',
    CELERY_ENABLE_UTC=True,
)

你可能感兴趣的:(后端开发架构/部署/性能优化)