在我们实际工作与学习中我们经常使用定时任务, 例如:
1 > 定点备份数据库, 凌晨对重要数据进行备份;
2 > 定时生生成 token;
.....
下面我们来介绍 Python 中常用的定时任务实现:
1 > 循环等待;
2 > 使用 Timer 模块;
3>sched 模块;
4 > 定时框架: APScheduler
在开始之前我们设定一个任务 (这样不用依赖外部环境):
1: 定时或者定点监测 CPU 与内存使用率;
2: 将时间, CPU, 内存使用情况保存到日志文件;
我们先来实现系统监测功能:
准备工作: 安装 psutil, 命令:
pip install psutil
功能实现
- #psutil: 获取系统信息模块, 可以获取 CPU, 内存, 磁盘等的使用情况
- import psutil
- import time
- import datetime
- #logfile: 监测信息写入文件
- def MonitorSystem(logfile = None):
- #获取 CPU 使用情况
- cpuper = psutil.cpu_percent()
- #获取内存使用情况: 系统内存大小, 使用内存, 有效内存, 内存使用率
- mem = psutil.virtual_memory()
- #内存使用率
- memper = mem.percent
- #获取当前时间
- now = datetime.datetime.now()
- ts = now.strftime('%Y-%m-%d %H:%M:%S')
- line = f'{ts} cpu:{cpuper}%, mem:{memper}%'
- print(line)
- if logfile:
- logfile.write(line)
代码运行结果:
2019-03-21 14:23:41 CPU:0.6%, mem:77.2%
接下来我们要实现定时监测, 比如 3s 监测一下系统资源使用情况.
最简单使用方式: sleep
这种方式最简单, 直接使用 while+sleep 就可以实现:
- def loopMonitor():
- while True:
- MonitorSystem()
- #2s 检查一次
- time.sleep(3)
- loopMonitor()
输出结果:
- 2019-03-21 14:28:42 CPU:1.5%, mem:77.6%
- 2019-03-21 14:28:45 CPU:1.6%, mem:77.6%
- 2019-03-21 14:28:48 CPU:1.4%, mem:77.6%
- 2019-03-21 14:28:51 CPU:1.4%, mem:77.6%
- 2019-03-21 14:28:54 CPU:1.3%, mem:77.6%
这种方式存在问题: 只能处理单个定时任务.
又来了新任务: 需要每秒监测网络收发字节, 代码实现如下:
- def MonitorNetWork(logfile = None):
- #获取网络收信息
- netinfo = psutil.net_io_counters()
- #获取当前时间
- now = datetime.datetime.now()
- ts = now.strftime('%Y-%m-%d %H:%M:%S')
- line = f'{ts} bytessent={netinfo.bytes_sent}, bytesrecv={netinfo.bytes_recv}'
- print(line)
- if logfile:
- logfile.write(line)
- MonitorNetWork()
代码执行结果:
2019-03-21 14:47:21 bytessent=169752183, bytesrecv=1107900973
如果我们同时在 while 循环中监测两个任务会有等待问题, 不能每秒监测网络情况.
Timer 实现方式
timer 最基本理解就是定时器, 我们可以启动多个定时任务, 这些定时器任务是异步执行, 所以不存在等待顺序执行问题.
先来看 Timer 的基本使用:
导入: from threading import Timer
主要方法:
Timer 方法 | 说明 |
---|---|
Timer(interval, function, args=None, kwargs=None) | 创建定时器 |
cancel() | 取消定时器 |
start() | 使用线程方式执行 |
join(self, timeout=None) | 等待线程执行结束 |
定时器只能执行一次, 如果需要重复执行, 需要重新添加任务;
我们先来看基本使用:
- from threading import Timer
- # 记录当前时间
- print(datetime.datetime.now())
- #3S 执行一次
- sTimer = Timer(3, MonitorSystem)
- #1S 执行一次
- nTimer = Timer(1, MonitorNetWork)
- # 使用线程方式执行
- sTimer.start()
- nTimer.start()
- # 等待结束
- sTimer.join()
- nTimer.join()
- # 记录结束时间
- print(datetime.datetime.now())
输出结果:
- 2019-03-21 15:13:36.739798
- 2019-03-21 15:13:37 bytessent=171337324, bytesrecv=1109002349
- 2019-03-21 15:13:39 CPU:1.4%, mem:93.2%
- 2019-03-21 15:13:39.745187
可以看到, 花费时间为 3S, 但是我们想要做的是每秒监控网络状态; 如何处理.
Timer 只能执行一次, 所以执行完成之后需要再次添加任务, 我们对代码进行修改:
- from threading import Timer
- import psutil
- import time
- import datetime
- def MonitorSystem(logfile = None):
- cpuper = psutil.cpu_percent()
- mem = psutil.virtual_memory()
- memper = mem.percent
- now = datetime.datetime.now()
- ts = now.strftime('%Y-%m-%d %H:%M:%S')
- line = f'{ts} cpu:{cpuper}%, mem:{memper}%'
- print(line)
- if logfile:
- logfile.write(line)
- #启动定时器任务, 每三秒执行一次
- Timer(3, MonitorSystem).start()
- def MonitorNetWork(logfile = None):
- netinfo = psutil.net_io_counters()
- now = datetime.datetime.now()
- ts = now.strftime('%Y-%m-%d %H:%M:%S')
- line = f'{ts} bytessent={netinfo.bytes_sent}, bytesrecv={netinfo.bytes_recv}'
- print(line)
- if logfile:
- logfile.write(line)
- #启动定时器任务, 每秒执行一次
- Timer(1, MonitorNetWork).start()
- MonitorSystem()
- MonitorNetWork()
执行结果:
- 2019-03-21 15:18:21 CPU:1.5%, mem:93.2%
- 2019-03-21 15:18:21 bytessent=171376522, bytesrecv=1109124678
- 2019-03-21 15:18:22 bytessent=171382215, bytesrecv=1109128294
- 2019-03-21 15:18:23 bytessent=171384278, bytesrecv=1109129702
- 2019-03-21 15:18:24 CPU:1.9%, mem:93.2%
- 2019-03-21 15:18:24 bytessent=171386341, bytesrecv=1109131110
- 2019-03-21 15:18:25 bytessent=171388527, bytesrecv=1109132600
- 2019-03-21 15:18:26 bytessent=171390590, bytesrecv=1109134008
从时间中可以看到, 这两个任务可以同时进行不存在等待问题.
Timer 的实质是使用线程方式去执行任务, 每次执行完后会销毁, 所以不必担心资源问题.
调度模块: schedule
schedule 是一个第三方轻量级的任务调度模块, 可以按照秒, 分, 小时, 日期或者自定义事件执行时间;
安装方式:
pip install schedule
我们来看一个例子:
- import datetime
- import schedule
- import time
- def func():
- now = datetime.datetime.now()
- ts = now.strftime('%Y-%m-%d %H:%M:%S')
- print('do func time :',ts)
- def func2():
- now = datetime.datetime.now()
- ts = now.strftime('%Y-%m-%d %H:%M:%S')
- print('do func2 time:',ts)
- def tasklist():
- #清空任务
- schedule.clear()
- #创建一个按秒间隔执行任务
- schedule.every(1).seconds.do(func)
- #创建一个按 2 秒间隔执行任务
- schedule.every(2).seconds.do(func2)
- #执行 10S
- for i in range(10):
- schedule.run_pending()
- time.sleep(1)
- tasklist()
执行结果:
- do func time : 2019-03-22 08:51:38
- do func2 time: 2019-03-22 08:51:39
- do func time : 2019-03-22 08:51:39
- do func time : 2019-03-22 08:51:40
- do func2 time: 2019-03-22 08:51:41
- do func time : 2019-03-22 08:51:41
- do func time : 2019-03-22 08:51:42
- do func2 time: 2019-03-22 08:51:43
- do func time : 2019-03-22 08:51:43
- do func time : 2019-03-22 08:51:44
- do func2 time: 2019-03-22 08:51:45
- do func time : 2019-03-22 08:51:45
- do func time : 2019-03-22 08:51:46
执行过程分析:
>1 > 因为老猫在 jupyter 下执行, 所以先将 schedule 任务清空;
>2 > 按时间间在 schedule 中隔添加任务;
>3 > 老猫这里按照秒间隔添加 func, 按照两秒间隔添加 func2;
>4>schedule 添加任务后, 需要查询任务并执行任务;
>5 > 为了防止占用资源, 每秒查询到点任务, 然后顺序执行;
第 5 个顺序执行怎么理解, 我们修改 func 函数, 里面添加 time.sleep(2)
然后只执行 func 工作, 输出结果:
- do func time : 2019-03-22 09:00:59
- do func time : 2019-03-22 09:01:02
- do func time : 2019-03-22 09:01:05
可以看到时间间隔为 3S, 为什么不是 1S?
因为这个按照顺序执行, func 休眠 2S, 循环任务查询休眠 1S, 所以会存在这个问题.
在我们使用这种方式执行任务需要注意这种阻塞现象.
我们看下 schedule 模块常用使用方法:
- #schedule.every(1) 创建 Job, seconds.do(func) 按秒间隔查询并执行
- schedule.every(1).seconds.do(func)
- # 添加任务按分执行
- schedule.every(1).minutes.do(func)
- # 添加任务按天执行
- schedule.every(1).days.do(func)
- # 添加任务按周执行
- schedule.every().weeks.do(func)
- # 添加任务每周 1 执行, 执行时间为下周一这一时刻时间
- schedule.every().monday.do(func)
- # 每周 1,1 点 15 开始执行
- schedule.every().monday.at("12:00").do(job)
这种方式局限性: 如果工作任务回非常耗时就会影响其他任务执行. 我们可以考虑使用并发机制配置这个模块使用.
任务框架 APScheduler
APScheduler 是 Python 的一个定时任务框架, 用于执行周期或者定时任务,
可以基于日期, 时间间隔, 及类似于 Linux 上的定时任务 crontab 类型的定时任务;
该该框架不仅可以添加, 删除定时任务, 还可以将任务存储到数据库中, 实现任务的持久化, 使用起来非常方便.
安装方式: pip install apscheduler
apscheduler 组件及简单说明:
1>triggers(触发器): 触发器包含调度逻辑, 每一个作业有它自己的触发器
2>job stores(作业存储): 用来存储被调度的作业, 默认的作业存储器是简单地把作业任务保存在内存中, 支持存储到 MongoDB,Redis 数据库中
3> executors(执行器): 执行器用来执行定时任务, 只是将需要执行的任务放在新的线程或者线程池中运行
4>schedulers(调度器): 调度器是将其它部分联系在一起, 对使用者提供接口, 进行任务添加, 设置, 删除.
来看一个简单例子:
- import time
- from apscheduler.schedulers.blocking import BlockingScheduler
- def func():
- now = datetime.datetime.now()
- ts = now.strftime('%Y-%m-%d %H:%M:%S')
- print('do func time :',ts)
- def func2():
- #耗时 2S
- now = datetime.datetime.now()
- ts = now.strftime('%Y-%m-%d %H:%M:%S')
- print('do func2 time:',ts)
- time.sleep(2)
- def dojob():
- #创建调度器: BlockingScheduler
- scheduler = BlockingScheduler()
- #添加任务, 时间间隔 2S
- scheduler.add_job(func, 'interval', seconds=2, id='test_job1')
- #添加任务, 时间间隔 5S
- scheduler.add_job(func2, 'interval', seconds=3, id='test_job2')
- scheduler.start()
- dojob()
输出结果:
- do func time : 2019-03-22 10:32:20
- do func2 time: 2019-03-22 10:32:21
- do func time : 2019-03-22 10:32:22
- do func time : 2019-03-22 10:32:24
- do func2 time: 2019-03-22 10:32:24
- do func time : 2019-03-22 10:32:26
输出结果中可以看到: 任务就算是有延时, 也不会影响其他任务执行.
这里我们介绍四中方式去做定时或者定点任务, 我们可以根据自己业务情况去选择使用哪种方式. 使用工作中常用应用场景包括:
1: 定时访问服务器, 判断服务是否异常;
2: 定点同步数据;
3: 定点抓取数据;
4: 定时上线;
......
来源: http://www.bubuko.com/infodetail-2995834.html