前言
由于最近项目组要做爬虫需要一些自动化的工作,一直没时间,今天在此做个记录,给需要的小伙伴~ 在我们的日常工作自动化测试当中,几乎超过一半的功能都需要利用定时的任务来推动触发,例如在我们项目中有一个定时监控模块,根据自己设置的频率定时跑测试用例,定时检测是否存在线上紧急任务等等,这些都涉及到了有关定时任务的问题。
很多情况下,大多数人会选择window的任务计划程序,但如果程序不在window平台下运行,就不能定时启动了;当然也可利用time模块的time.sleep()方法使程序休眠来达到定时任务的目的,但定时任务多了,代码可能看起来不太那么友好且有很大的局限性,因此,此时的 Apscheduler 框架是你的不二选择。
简介
Apscheduler基于Quartz的一个python定时任务框架,实现Quart的所有功能,相关的接口调用起来比较方便,目前其提供了基于日期、固定时间间隔以及corntab类型的任务,并且同时可进行持久化任务;同时它提供了多种不同的调用器,方便开发者根据自己的需求进行使用,也方便与数据库等第三方的外部持久化储存机制进行协同工作,非常强大。
官网文档地址:Advanced Python Scheduler — APScheduler 3.9.0.post1.post1 documentation
安装包位置: https://pypi.python.org/pypi/APScheduler/
pip安装:pip install apscheduler
基本原理
主要是利用python的 threading Event 和 Lock锁来写的。
scheduler在主循环(main_loop)中, 反复检查是否有需要执行的任务,完成任务的检查函数为 _process_jobs
主要有那个几个步骤:
1、 询问储存的每个 jobStore
,是否有到期要执行的任务。
2、 due_jobs
不为空,则计算这些jobs中每个job需要运行的时间点,时间一到就提交给submit作任务调度。
3、在主循环中,如果不间断地调用,而实际上没有要执行的job,这会造成资源浪费。因此在程序中,如果每次掉用 _process_jobs
后,进行了预先判断,判断下一次要执行的job(离现在最近的)还要多长时间,作为返回值告诉main_loop, 这时主循环就可以去睡一觉,等大约这么长时间后再唤醒,执行下一次 _process_jobs
。
配置调度器
Apscheduler框架提供了许多调度器的配置方法,既可以使用配置字典,也可以直接传递配置参数给调度器使用; 同时支持先初始化调度器,添加完作业任务后,再来配置调度器等。
上面的代码生成一个默认的调度器,默认使用名为 default 的 MemoryJobStore,以及使用默认名为 default 的 ThreadPoolExecutor ,最大线程数为10 。
下面进行一个复杂的配置,同时使用两个作业存储和两个执行器,在这个配置中,修改默认的配置参数,jobstored指的是job持久化,默认job运行在内存中,可持久化在数据库,指定为mongo的MongoDBJobStore或者是使用sqlite的SQLAlchemyJobStore,同时可指定多种jobstore。
coalesce
:当由于某种原因导致某个job积攒了好几次没有实际运行(比如说系统挂了5分钟后恢复,有一个任务是每分钟跑一次的,按道理说这5分钟内本来是“计划”运行5次的,但实际没有执行),如果coalesce为True,下次这个job被submit给executor时,只会执行1次,也就是最后这次,如果为False,那么会执行5次(不一定,因为还有其他条件,看后面misfiregracetime的解释)。
max_instance
:每个job在同一时刻能够运行的最大实例数,默认情况下为1个,可以指定为更大值,这样即使上个job还没运行完同一个job又被调度的话也能够再开一个线程执行。
misfire_grace_time
:单位为秒,假设有这么一种情况,当某一job被调度时刚好线程池都被占满,调度器会选择将该job排队不运行,misfiregracetime参数则是在线程池有可用线程时会比对该job的应调度时间跟当前时间的差值,如果差值<misfiregracetime时,调度器会再次调度该job.反之该job的执行状态为EVENTJOBMISSED了,即错过运行.</misfire。
启动/关闭调度器
使用 start() 方法来启动调度器,其中须注意的是 BlockingScheduler
需要在初始化之后才能执行 start()
,对于其他的调度器,调用 start()
方法都会直接返回,然后可以继续执行后面的初始化操作。同时,调度器启动之后,就不能再更改它的配置了。
在默认情况下,调度器会等所有的作业任务完成后,自动关闭所有的调度器及作业存储。若在使用过程中不想等待,可以将 wait
参数选项设为 False
,则表示直接关闭:
sched.shutdown()
sched.shutdown(wait=False)
调度器监听事件
可以给调度器添加事件监听器,调度器事件只有在某些情况下才会被触发,并且可以携带某些有用的信息。通过给 add_listener()
传递合适的 mask
参数,可以只监听几种特定的事件类型,具体类型可看源码中的 event.exception
或者 event.code
值来做识别判断。
def taskListener(event):
if event.code == ECENT_JOB_EXECUTED:
print('任务已执行完成')
else:
print('任务出现异常')
scheduler.add_listener(my_listener,EVENT_JOB_EXECUTED, ECENT_JOB_ERROR)
APScheduler的job管理
Job是框架承接目前需要执行的工作和任务,我们可以在系统运行过程中进行动态的增加、修改、删除、查询等操作。
1. Job的新增
共有两种方式进行新增job的操作:
一种是基于add_job来动态增加
sched.add_job(job_function, 'cron', day_of_week='mon-fri', hour='0-9', minute="*", second="*/4")
另一种是基于修饰器 scheduled_job 来动态装饰job的实际函数
@sched.scheduled_job('cron', id='my_job_id', day='last sun')
def some_decorated_task():
print("I am printed at 00:00:00 on the last Sunday of every month!")
year (int|str) – 4-digit year
month (int|str) – month (1-12)
day (int|str) – day of the (1-31)
week (int|str) – ISO week (1-53)
day_of_week (int|str) – number or name of weekday (0-6 or mon,tue,wed,thu,fri,sat,sun)
hour (int|str) – hour (0-23)
minute (int|str) – minute (0-59)
second (int|str) – second (0-59)
start_date (datetime|str) – earliest possible date/time to trigger on (inclusive)
end_date (datetime|str) – latest possible date/time to trigger on (inclusive)
timezone (datetime.tzinfo|str) – time zone to use for the date/time calculations (defaults to scheduler timezone)
2. 移除作业
基于job id来动态移除特定的job.
job = scheduler.add_job(myfunc, 'interval', minutes=2)
job.remove()
Same, using an explicit job ID:
scheduler.add_job(myfunc, 'interval', minutes=2, id='my_job_id')
scheduler.remove_job('my_job_id')
3. 暂停和恢复作业
#暂停作业
apscheduler.job.Job.pause()
apscheduler.schedulers.base.BaseScheduler.pause_job()
#恢复作业:
apscheduler.job.Job.resume()
apscheduler.schedulers.base.BaseScheduler.resume_job()
4. 获得job列表
获得调度作业的列表,可以使用 get_jobs()
来完成,它会返回所有的job实例。或者使用 print_jobs()
来输出所有格式化的作业列表。
5. 修改作业 job
可以通过apscheduler.job.Job.modify()
或者 modify_job()
来动态修改job的属性信息,除了job id无法修改之外,都是可以修改的。
job.modify(max_instances=6, name='Alternate name')
另外我们也可以通过apscheduler.job.Job.reschedule()
or reschedule_job()
动态重新设置trigger,示例如下:
scheduler.reschedule_job('my_job_id', trigger='cron', minute='*/5')
6. 关闭调度器
默认情况下调度器会等待所有正在运行的作业完成后,关闭所有的调度器和作业存储。如果你不想等待,可以将wait选项设置为False。
scheduler.shutdown()
scheduler.shutdown(wait=False)
APScheduler的代码示例
使用装饰器来展示一个调度的使用
from apscheduler.schedulers.blocking import BlockingScheduler
sched = BlockingScheduler()
@sched.scheduled_job('interval', seconds=3)
def timed_job():
print('This job is run every three minutes.')
@sched.scheduled_job('cron', day_of_week='mon-fri', hour='0-9', minute='30-59', second='*/3')
def scheduled_job():
print('This job is run every weekday at 5pm.')
print('before the start funciton')
sched.start()
print("let us figure out the situation")
'''
代码说明:
在这段代码中,使用了当前进程中共享计算资源的BlockingScheduler,共使用了2个调度器,其中一个是间隔3秒的执行。
另外一个调度器是模仿cron来执行的,在周一到周五其间,每天的0点到9点直接,在30分到59分之间执行,执行频次为3秒。
'''
基于正常代码的示例如下
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.schedulers.blocking import BlockingScheduler
import datetime
import time
import logging
def job_function():
print "Hello World" + " " + str(datetime.datetime.now())
if __name__ == '__main__':
log = logging.getLogger('apscheduler.executors.default')
log.setLevel(logging.INFO) # DEBUG
fmt = logging.Formatter('%(levelname)s:%(name)s:%(message)s')
h = logging.StreamHandler() # 新增logging日志
h.setFormatter(fmt)
log.addHandler(h)
print('start to do it')
sched = BlockingScheduler()
# Schedules job_function to be run on the third Friday
# of June, July, August, November and December at 00:00, 01:00, 02:00 and 03:00
sched.add_job(job_function, 'cron', day_of_week='mon-fri', hour='0-9', minute="*",
second="*/4")
sched.start()
总结
Apscheduler是一个非常强大且易用的类库,可以方便我们快速的搭建一些强大的定时任务或者定时监控类的调度系统,在实际工作中非常有用,提供了多种不同的调度器,方便开发者根据自己的实际需要进行使用;同时也提供了不同的存储机制,可以方便与Redis,数据库等第三方的外部持久化机制进行协同工作,总之功能非常强大和易用。
这里简单列一下常用的若干调度器:
- BlockingScheduler:仅可用在当前你的进程之内,与当前的进行共享计算资源
- BackgroundScheduler: 在后台运行调度,不影响当前的系统计算运行
- AsyncIOScheduler: 如果当前系统中使用了async module,则需要使用异步的调度器
- GeventScheduler: 如果使用了gevent,则需要使用该调度
- TornadoScheduler: 如果使用了Tornado, 则使用当前的调度器
- TwistedScheduler:Twister应用的调度器
- QtScheduler: Qt的调度器
由此可知,在APscheduler的调度器中,是与底层的实现机制紧密相关的,需要依据当前的计算模型来动态选择调度器。