【Python自動化任務】讓運維更簡單的7種定時任務實現方式,總有一種適合你的場景

想要看更加舒服的排版、更加準時的推送
關注公衆號“不太靈光的程序員”
每日八點有乾貨推送

有粉絲留言問什麼時候可以寫一個關於自動化任務的文章
準備上!~
感覺有用關注公衆號 “不太靈光的程序員”
每天9:30分析一些Python小知識,爬坑的路與你同行

什麼是自動化任務

自動化任務就是將一系列可以手動去實現的操作的集合,在特定的條件下自動去執行,比較常見的場景:

  • 運行監控報警,監測要素的變化,觸發閾值條件時執行一系列通知郵件、短信的發送,也可以是更復雜的觸發
  • 自動化田間灌溉控制,對土壤含水量、天氣預報、植物生長週期分析需水量來調節灌溉水量
  • 自動駕駛,根據行駛路線、實時路況信息進行分析後實施自動的駕駛
  • 自動化測試,對目標系統有已知的預期,按照配置的流程去驗證每一個功能的結果
  • 定時爬蟲,比如我們需要長期的爬取招聘信息,來進行數據分析,就需要定時的去爬取某段時間的增量信息
  • 還有些統計類問題,比如我們需要對請求的IP進行判斷是否是一個壞的IP(同一個IP下有一定比例的用戶從事違規操作時,判斷爲非法IP),我們可以在用戶請求是實時去計算壞IP的列表,但這當用戶量非常大的時候就不是一個很好的選擇了,我們可以選擇定期執行這個壞IP列表的計算,因爲對於不同用戶的壞IP列表是同一個

以上這些任務都可以認爲是自動化任務,我們今天主要針對其中的定時任務進行討論。

定時任務

定時任務的實現方式又有N多種,我就瞭解的幾種實現舉例說明:

  • Windows操作系統的任務計劃程序
  • Linux操作系統的 Crontab 定時任務
  • Python的 循環+sleep
  • Python的 Timer
  • Python的 schedule
  • Python的 APScheduler
  • Python的 Celery

我以兩個場景來設計實例代碼

  • 每30秒打印一下當前時間
  • 在2020-06-21 00:00:00 (具體時刻)打印一下當前時間

1. Windows任務計劃程序

當前版本win10

  • 右鍵“計算機”->選擇“管理”;
  • 然後就可以打開 “計算機管理”界面;
  • 在界面的左側有點擊“系統工具”->任務計劃程序 ;
  • 可以看到右側有“操作”“任務計劃程序”;
  • 有創建基本任務、創建任務、導入任務;在這裏插入圖片描述
    我以兩個場景來設計實例代碼
  • 每30秒打印一下當前時間
  • 在2020-06-21 00:00:00 (具體時刻)打印一下當前時間

任務1:由於任務計劃程序最小執行週期是“5分鐘”,是在高級設置中設置重複任務間隔,並選擇持續時間“無限制” 無法實現30秒的打印
任務2:設置中選擇一次,開始時間設置2020-06-21 00:00:00 (具體時刻)可以滿足需求

2. Linux Crontab定時任務

Crontab定時任務,在每個任務週期中執行一次特定任務,固無法控制執行次數
時間格式如下:

*    *    *    *    *  program
-    -    -    -    -    -
|    |    |    |    |    |----- 要執行的程序
|    |    |    |    +----- 星期中星期幾 (0 - 7) (星期天 爲0) 默認*
|    |    |    +---------- 月份 (1 - 12) 默認*
|    |    +--------------- 一個月中的第幾天 (1 - 31) 默認*
|    +-------------------- 小時 (0 - 23) 默認*
+------------------------- 分鐘 (0 - 59) 默認*

我以兩個場景來設計實例代碼

  • 每30打印一下當前時間;
  • 在2020-06-21 00:00:00 (具體時刻)打印一下當前時間 ;

任務1:* * * * * echo $(date +%F%n%T)含義每分鐘打印當前時間,最小執行週期每分鐘 無法實現30s的打印
任務2:* * 21 6 * echo $(date +%F%n%T)含義每年的6月21日打印當前時間 無法實現只執行一次的打印

3. Python 循環+sleep

我以兩個場景來設計實例代碼

  • 每30打印一下當前時間;
  • 在2020-06-21 00:00:00 (具體時刻)打印一下當前時間 ;

任務1: 可以實現30s的打印
藉助循環和等待時間來實現任務執行,但是在一個線程組阻塞時不能進行其他操作,可以使用協程優化

import time
import datetime

# 每30秒執行
while 1:
    time.sleep(30)
    print(datetime.datetime.now())

任務2: 可以實現特定時間的打印
每次循環判斷當前時刻和目標是否一致,需要注意執行一次就需要跳出點前時刻的判斷,不然在同一時刻會重複打印很多次

import time
import datetim

# 特定時間執行
while 1:
    now = datetime.datetime.now()
    if now.year == 2020 and now.month == 6 and now.day == 21:
        print(now)
        break
	time.sleep(1)

4. 線程Timer

看下源碼的描述

class Timer(Thread):
    """Call a function after a specified number of seconds:

            t = Timer(30.0, f, args=None, kwargs=None)
            t.start()
            t.cancel()     # stop the timer's action if it's still waiting

    """

在指定的秒數後調用函數
因爲創建的定時是異步執行,所以不存在等待順序執行問題。

  • 創建定時器 Timer(interval, function, args=None, kwargs=None);
  • 取消定時器cancel();
  • 使用線程方式執行start();
  • 等待線程執行結束join(self, timeout=None);
  • 定時器只能執行一次,如果需要重複執行,需要重新添加任務;

我以兩個場景來設計實例代碼

  • 每30打印一下當前時間
  • 在2020-06-21 00:00:00 (具體時刻)打印一下當前時間

任務1: 可以實現30s的打印
雖然是可以實現沒30秒一次的打印,但是定時器只能執行一次,如果需要重複添加任務,創建線程又比較浪費系統資源

from threading import Timer
import datetime
import time

def run_time(seconds, flag=False):
    t = Timer(seconds, run_time, [seconds, True])
    t.start()
    if flag:
        print(datetime.datetime.now())
        time.sleep(3)
    t.join()

run_time(30)

任務2: 實現特定時間的打印
需要計算當前時間到目標時間間的秒數來實現

from threading import Timer
import datetime
import time

def run_time(seconds, flag=False):
    if seconds > 0:
        t = Timer(seconds, run_time, [seconds, True])
        t.start()
        if flag:
            print(datetime.datetime.now())
            time.sleep(3)
        t.join()

def get_seconds(run_date):
    start = datetime.datetime.now()
    end = datetime.datetime.strptime(run_date, "%Y-%m-%d %H:%M:%S")
    if end > start:
        return (end-start).seconds
    else:
        return -1

seconds = get_seconds("2020-06-21 00:00:00")
run_time(seconds)

5. schedule模塊

首先看下源碼的描述

Python job scheduling for humans.

github.com/dbader/schedule

An in-process scheduler for periodic jobs that uses the builder pattern
for configuration. Schedule lets you run Python functions (or any other
callable) periodically at pre-determined intervals using a simple,
human-friendly syntax.

Inspired by Addam Wiggins' article "Rethinking Cron" [1] and the
"clockwork" Ruby module [2][3].

Features:
    - A simple to use API for scheduling jobs.
    - Very lightweight and no external dependencies.
    - Excellent test coverage.
    - Tested on Python 2.7, 3.5 and 3.6

Usage:
    >>> import schedule
    >>> import time

    >>> def job(message='stuff'):
    >>>     print("I'm working on:", message)

    >>> schedule.every(10).minutes.do(job)
    >>> schedule.every(5).to(10).days.do(job)
    >>> schedule.every().hour.do(job, message='things')
    >>> schedule.every().day.at("10:30").do(job)

    >>> while True:
    >>>     schedule.run_pending()
    >>>     time.sleep(1)

任務1: 可以實現30s的打印
可以實現沒30秒一次的打印,還可以同時創建多個定時任務同時去執行,由於執行過程是順序執行,pn休眠2秒,循環任務查詢休眠1秒,會存在實際間隔時間並不是設定的30秒,當工作任務回非常耗時就會影響其他任務的觸發時間

import datetime
import schedule
import time

def pn():
    print(datetime.datetime.now())
    time.sleep(2)

schedule.clear()
schedule.every(30).seconds.do(pn)

while 1:
    schedule.run_pending()
    time.sleep(1)

任務2: 實現特定時間的打印
需要計算當前時間到目標時間間的秒數來實現

import datetime
import schedule
import time

def pn():
    print(datetime.datetime.now())
    time.sleep(2)

def get_seconds(run_date):
    start = datetime.datetime.now()
    end = datetime.datetime.strptime(run_date, "%Y-%m-%d %H:%M:%S")
    if end > start:
        return (end - start).seconds
    else:
        return -1

schedule.clear()
seconds = get_seconds("2020-06-21 00:00:00")
if seconds > 0:
    schedule.every(seconds).seconds.do(pn)

while 1:
    schedule.run_pending()
    time.sleep(1)

6. APScheduler定時任務框架

APScheduler是Python的一個定時任務框架,用於執行週期或者定時任務,
可以基於日期、時間間隔,及類似於Linux上的定時任務crontab類型的定時任務;
該該框架不僅可以添加、刪除定時任務,還可以將任務存儲到數據庫中,實現任務的持久化,使用起來非常方便。

  • triggers(觸發器):觸發器包含調度邏輯,每一個作業有它自己的觸發器;
  • job stores(作業存儲):用來存儲被調度的作業,默認的作業存儲器是簡單地把作業任務保存在內存中,支持存儲到MongoDB,Redis數據庫中;
  • executors(執行器):執行器用來執行定時任務,只是將需要執行的任務放在新的線程或者線程池中運行;
  • schedulers(調度器):調度器是將其它部分聯繫在一起,對使用者提供接口,進行任務添加,設置,刪除。

add_job 創建任務

創建任務支持三種類型觸發器dateintervalcron

date觸發器
  • run_date 具體的日期執行,時間參數run_date,可以是dete/time格式的字符串,當不寫時分秒是 默認是 00:00:00
interval觸發器
  • weeks 參數weeks 每n周後執行,int類型
  • days 參數days每n天后執行,int類型
  • hours: 參數hours每n小時後執行,int類型
  • minutes:參數minutes每n分鐘後執行,int類型
  • seconds: 參數seconds每n秒後執行,int類型
  • datetime|str 參數start_date,可以是dete/time格式的字符串,控制執行循環的時間範圍
  • datetime|str 參數 end_date,可以是dete/time格式的字符串,控制執行循環的時間範圍
cron觸發器

使用方式與 Crontab定時任務類似

  • year int類型 取值範圍1970~9999, 默認*
  • month int類型 取值範圍1~12, 默認1
  • dayint類型 取值範圍1~31, 默認1
  • week 一年中的第幾周,int類型 取值範圍1~53, 默認*
  • day_of_week一週中的第幾天,int類型 取值範圍0~6, 默認*
  • hour int類型 取值範圍0~23, 默認0
  • minute int類型 取值範圍0~59, 默認0
  • second int類型 取值範圍0~59, 默認0

任務1: 可以實現30s的打印
使用interval類型觸發器添加定時任務
可以實現沒30秒一次的打印,還可以同時創建多個定時任務同時去執行,由於執行過程是順序執行,pn休眠3秒,會存在實際間隔時間並不是設定的30秒,當工作任務回非常耗時就會影響其他任務的觸發時間

from apscheduler.schedulers.blocking import BlockingScheduler
import datetime
import time


def pn():
    print(datetime.datetime.now())
    time.sleep(3)

def run_time(seconds):
    scheduler = BlockingScheduler()
    scheduler.add_job(pn, 'interval', seconds=seconds, id=str(time.time()))
    scheduler.start()

run_time(30)

任務2: 可以實現特定時間的打印
使用date類型或cron類型觸發器添加定時任務

from apscheduler.schedulers.blocking import BlockingScheduler
import datetime
import time

def pn():
    print(datetime.datetime.now())
    time.sleep(3)

def run_time():
    scheduler = BlockingScheduler()
    scheduler.add_job(pn, 'cron', year=2020, month=6, day=21, id=str(time.time()))
    scheduler.add_job(pn, 'date', run_date="2020-06-21", id=str(time.time()))
    scheduler.start()

run_time()

7. Celery分佈式任務調度的框架

Celery是實時處理的異步任務調度的框架,需要藉助中間件完成調度任務,也可以很好的用來做定時任務服務。
在這裏插入圖片描述
Celery是一個簡單、靈活且可靠的,處理大量消息的分佈式系統,由三部分組成:

  • 消息中間件(message broker):Celery本身不提供消息服務,依賴於中間件RabbitMQ, Redis等
  • 任務執行單元(worker): 是Celery提供的任務執行的單元,worker併發的運行在分佈式的系統節點中
  • 任務執行結果存儲(task result store): 用來存儲Worker執行的任務的結果,Celery支持以不同方式存儲任務的結果,包括AMQP, Redis等

以上面代碼使用的 python3.7不同
當前版本 python2.7 Celery 3.2

目錄結構

在這裏插入圖片描述

celery_app_init_.py
# -*- coding: utf-8 -*-
# 拒絕隱式引入,如果celery.py和celery模塊名字一樣,避免衝突,需要加上這條語句
# 該代碼中,名字是不一樣的,最好也要不一樣
from __future__ import absolute_import
from celery import Celery

app = Celery('tasks')
app.config_from_object('celery_app.celeryconfig')
celery_app\celeryconfig.py
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from celery.schedules import crontab
from datetime import timedelta

# 使用redis存儲任務隊列
BROKER_URL = 'redis://127.0.0.1:6379/1'
# 使用redis存儲結果
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/2'

# 時區設置
CELERY_TIMEZONE = 'Asia/Shanghai'

# 導入任務所在文件
CELERY_IMPORTS = [
    'celery_app.celery_task.tasks',
]

# 需要執行任務的配置
CELERYBEAT_SCHEDULE = {
    'np-seconds-10': {
        # 具體需要執行的函數
        # 該函數必須要使用@app.task裝飾
        'task': 'celery_app.celery_task.tasks.pn',
        # 定時時間
        # 每30秒分鐘執行一次
        'schedule': timedelta(seconds=30),
        'args': ()
    },
    'np-month-day': {
        'task': 'celery_app.celery_task.tasks.pn',
        # 每年6月21日執行一次
        'schedule': crontab(day_of_month=21, month_of_year=6),
        'args': ()
    },
}

celery_app\celery_task\tasks.py
# -*- coding: utf-8 -*-
from .. import app
import datetime
import time


@app.task
def pn():
    print(datetime.datetime.now())
    time.sleep(3)

我以兩個場景來設計實例代碼

  • 每30打印一下當前時間
  • 在2020-06-21 00:00:00 (具體時刻)打印一下當前時間

任務1: 可以實現30s的打印

    'np-seconds-10': {
        # 具體需要執行的函數
        # 該函數必須要使用@app.task裝飾
        'task': 'celery_app.celery_task.tasks.pn',
        # 定時時間
        # 每30秒分鐘執行一次
        'schedule': timedelta(seconds=30),
        'args': ()
    },

任務2: 不可以實現指定時間的打印
一般指定時間也沒有意思,每年的某個月日的任務比較多
使用方式與 Crontab定時任務類似

    'np-month-day': {
        'task': 'celery_app.celery_task.tasks.pn',
        # 每年6月21日執行一次
        'schedule': crontab(day_of_month=21, month_of_year=6),
        'args': ()
    },

啓動定時任務

由於是分佈式服務,我們需要啓動 中間件,觸發器和處理模塊

start redis

在這裏插入圖片描述
celery_app 統計目錄下執行

發佈任務

celery -A celery_app beat

執行任務

celery worker -A celery_app -l info
在這裏插入圖片描述
在這裏插入圖片描述

總結

簡單總結上面七種定時定點任務實現:

  • Windows任務計劃程序合適重啓服務,定時執行些bat腳本,清理日誌等操作;
  • Linux Crontab定時任務適合執行shell腳本或者一系列Linux命令;
  • 循環+sleep方式適合簡單測試;
  • Timer可以實現定時任務,但是對定點任務來說,需要檢查當前時間點;
  • schedule可以定點定時執行,但是需要在循環中檢測任務,而且存在阻塞;
  • APScheduler框架更加強大,可以直接在裏面添加定點與定時任務;
  • Celery框架穩定,可動態添加任務,與Django、Flask等框架可以完美結合使用,對於大任務量的服務支持比較好。

推薦閱讀:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章