celery: celery是一个专注于实时处理和任务调度的任务队列,任务就是消息(消息队列使用rabbitmq或者redie),消息中的有效载荷中包含要执行任务的全部数据。我们通常将celery作为一个任务队列来使用,但是celery也有定时任务的功能,但是celery无法在flask这样的系统中动态的添加定时任务,而且单独为定时任务功能而搭建celery显得过于重量级。
apscheduler: apscheduler是基于Quartz的一个Python定时任务框架,提供了基于日期、固定时间间隔以及crontab类型的任务,并且可以持久化作业。APScheduler算是在实际项目中最好用的一个工具库,不仅可以在程序中动态的添加和删除定时任务,还支持持久化
上面的两者都可以看做是任务队列,任务队列是逻辑模型,消息队列是通信模型:
任务队列:将抽象的任务发送到执行的worker的组件,用于处理任务。
消息队列MQ:是一种能实现生产者到消费者的单向通信模型,用于通知,传递消息。
任务队列可以需要消息队列来实现,比如redis作为celery的broker。
首先安装pip install apscheduler
APScheduler有四个组件:
根据不同的开发需求,选择对应的调度器组件
如果运行的任务是无状态的,选择默认的任务储存器MemoryJobStore
即可。
如果需要程序关闭或者重启的时候,保存任务的状态,那么需要持久化的任务储存器比如SQLAlchemyJobStore
配合postgres作为后台数据库。
默认的ThreadPoolExecutor线程池执行器方案可以满足大部分需求
如果程序是计算密集型,推荐使用ProcessPoolExecutor进程池执行器使用多核能力,还可以混合使用两种
一共有三种内置的触发器:
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:
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删除该任务
rabbitmq和celery的区别 他们是两个层面的东西,celery是一个分布式的队列,基本工作是管理分配任务到不同的服务器,但是服务器之间如何通信是celery不能解决的,所以rabbitmq作为一个消息队列管理工具被引入到和celery集成,现在crabbitmq在celery中扮演broker的角色,就是消息代理。
celery架构:
上面的异步任务模块其实也是任务发布者,所以产生任务的方式有两种,一种是发布者发布任务(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,
)