說(shuō)到定時(shí)任務(wù),你會(huì )想起 linux 自帶的 crontab ,windows 自帶的任務(wù)計劃,都可以實(shí)現守時(shí)任務(wù)。沒(méi)錯,操作系統基本都會(huì )提供定時(shí)任務(wù)的實(shí)現,但是如果你想要更加精細化的控制,或者說(shuō)任務(wù)程序需要跨平臺運行,最好還是自己實(shí)現定時(shí)任務(wù)框架,Python 的 apscheduler 提供了非常豐富而且方便易用的定時(shí)任務(wù)接口。本文介紹如何使用 apscheduler 實(shí)現你的定時(shí)任務(wù)。
apscheduler 使用起來(lái)十分方便。提供了基于日期、固定時(shí)間間隔以及crontab 類(lèi)型的任務(wù),我們可以在主程序的運行過(guò)程中快速增加新作業(yè)或刪除舊作業(yè),如果把作業(yè)存儲在數據庫中,那么作業(yè)的狀態(tài)會(huì )被保存,當調度器重啟時(shí),不必重新添加作業(yè),作業(yè)會(huì )恢復原狀態(tài)繼續執行。apscheduler 可以當作一個(gè)跨平臺的調度工具來(lái)使用,可以做為 linux 系統crontab 工具或 windows 計劃任務(wù)程序的替換。注意,apscheduler 不是一個(gè)守護進(jìn)程或服務(wù),它自身不帶有任何命令行工具。它主要是要在現有的應用程序中運行,也就是說(shuō),apscheduler 為我們提供了構建專(zhuān)用調度器或調度服務(wù)的基礎模塊。
安裝非常簡(jiǎn)單,會(huì )用 pip 的人都知道
pin install apscheduler觸發(fā)器(triggers):觸發(fā)器包含調度邏輯,描述一個(gè)任務(wù)何時(shí)被觸發(fā),按日期或按時(shí)間間隔或按 cronjob 表達式三種方式觸發(fā)。每個(gè)作業(yè)都有它自己的觸發(fā)器,除了初始配置之外,觸發(fā)器是完全無(wú)狀態(tài)的。
作業(yè)存儲器(job stores):作業(yè)存儲器指定了作業(yè)被存放的位置,默認情況下作業(yè)保存在內存,也可將作業(yè)保存在各種數據庫中,當作業(yè)被存放在數據庫中時(shí),它會(huì )被序列化,當被重新加載時(shí)會(huì )反序列化。作業(yè)存儲器充當保存、加載、更新和查找作業(yè)的中間商。在調度器之間不能共享作業(yè)存儲。
執行器(executors):執行器是將指定的作業(yè)(調用函數)提交到線(xiàn)程池或進(jìn)程池中運行,當任務(wù)完成時(shí),執行器通知調度器觸發(fā)相應的事件。
調度器(schedulers):任務(wù)調度器,屬于控制角色,通過(guò)它配置作業(yè)存儲器、執行器和觸發(fā)器,添加、修改和刪除任務(wù)。調度器協(xié)調觸發(fā)器、作業(yè)存儲器、執行器的運行,通常只有一個(gè)調度程序運行在應用程序中,開(kāi)發(fā)人員通常不需要直接處理作業(yè)存儲器、執行器或觸發(fā)器,配置作業(yè)存儲器和執行器是通過(guò)調度器來(lái)完成的。
# -*- coding: utf-8 -*-# Time: 2018/10/13 19:01:30# File Name: ex_interval.pyfrom datetime import datetimeimport osfrom apscheduler.schedulers.blocking import BlockingSchedulerdef tick(): print('Tick! The time is: %s' % datetime.now())if __name__ == '__main__': scheduler = BlockingScheduler() scheduler.add_job(tick, 'interval', seconds=3) print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C ')) try: scheduler.start() except (KeyboardInterrupt, SystemExit): pass說(shuō)明:
第 1 行代碼聲明文件內容以 utf-8 編碼,告訴Python 解釋器以 utf-8 編碼解析源代碼文件。
導入 datetime 模塊,用于打印當前時(shí)間。導入 os 模塊,用于判斷操作系統類(lèi)型。
導入調度器模塊 BlockingScheduler,這是最簡(jiǎn)單的調度器,調用 start 方阻塞當前進(jìn)程,如果你的程序只用于調度,除了調度進(jìn)程外沒(méi)有其他后臺進(jìn)程,那么請用 BlockingScheduler 非常有用,此時(shí)調度進(jìn)程相當于守護進(jìn)程。
定義一個(gè)函數 tick 代表我們要調度的作業(yè)程序。
實(shí)例化一個(gè) BlockingScheduler 類(lèi),不帶參數表明使用默認的作業(yè)存儲器-內存,默認的執行器是線(xiàn)程池執行器,最大并發(fā)線(xiàn)程數默認為 10 個(gè)(另一個(gè)是進(jìn)程池執行器)。
第 11 行添加一個(gè)作業(yè) tick,觸發(fā)器為 interval,每隔 3 秒執行一次,另外的觸發(fā)器為 date,cron。date 按特定時(shí)間點(diǎn)觸發(fā),cron 則按固定的時(shí)間間隔觸發(fā)。
加入捕捉用戶(hù)中斷執行和解釋器退出異常,pass 關(guān)鍵字,表示什么也不做。
執行結果如下所示:
可以看出,每 3 秒打印出了當前時(shí)間。
# -*- coding: utf-8 -*-# Time: 2018/10/13 19:21:09# File Name: ex_cron.pyfrom datetime import datetimeimport osfrom apscheduler.schedulers.blocking import BlockingSchedulerdef tick(): print('Tick! The time is: %s' % datetime.now())if __name__ == '__main__': scheduler = BlockingScheduler() scheduler.add_job(tick, 'cron', hour=19,minute=23) print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C ')) try: scheduler.start() except (KeyboardInterrupt, SystemExit): pass定時(shí) cron 任務(wù)也非常簡(jiǎn)單,直接給觸發(fā)器 trigger 傳入 ‘cron’ 即可。hour =19 ,minute =23 這里表示每天的19:23 分執行任務(wù)。這里可以填寫(xiě)數字,也可以填寫(xiě)字符串
hour =19 , minute =23hour ='19', minute ='23'minute = '*/3' 表示每 5 分鐘執行一次hour ='19-21', minute= '23' 表示 19:23、 20:23、 21:23 各執行一次任務(wù)python 就是這么靈活、易用、可讀。例 2 執行結果如下:
調度器的主循環(huán)其實(shí)就是反復檢查是不是有到時(shí)需要執行的任務(wù),分以下幾步進(jìn)行:
在配置調度器前,我們首先要選取適合我們應用環(huán)境場(chǎng)景的調度器,存儲器和執行器。下面是各調度器的適用場(chǎng)景:
作業(yè)存儲器的選擇有兩種:一是內存,也是默認的配置;二是數據庫。具體選哪一種看我們的應用程序在崩潰時(shí)是否重啟整個(gè)應用程序,如果重啟整個(gè)應用程序,那么作業(yè)會(huì )被重新添加到調度器中,此時(shí)簡(jiǎn)單的選取內存作為作業(yè)存儲器即簡(jiǎn)單又高效。但是,當調度器重啟或應用程序崩潰時(shí)您需要您的作業(yè)從中斷時(shí)恢復正常運行,那么通常我們選擇將作業(yè)存儲在數據庫中,使用哪種數據庫通常取決于為在您的編程環(huán)境中使用了什么數據庫。我們可以自由選擇,PostgreSQL 是推薦的選擇,因為它具有強大的數據完整性保護。
同樣的,執行器的選擇也取決于應用場(chǎng)景。通常默認的 ThreadPoolExecutor 已經(jīng)足夠好。如果作業(yè)負載涉及CPU 密集型操作,那么應該考慮使用 ProcessPoolExecutor,甚至可以同時(shí)使用這兩種執行器,將ProcessPoolExecutor 行器添加為二級執行器。
apscheduler 提供了許多不同的方法來(lái)配置調度器??梢允褂米值?,也可以使用關(guān)鍵字參數傳遞。首先實(shí)例化調度程序,添加作業(yè),然后配置調度器,獲得最大的靈活性。
如果調度程序在應用程序的后臺運行,選擇 BackgroundScheduler,并使用默認的 jobstore 和默認的executor,則以下配置即可:
from apscheduler.schedulers.background import BackgroundSchedulerscheduler = BackgroundScheduler()假如我們想配置更多信息:設置兩個(gè)執行器、兩個(gè)作業(yè)存儲器、調整新作業(yè)的默認值,并設置不同的時(shí)區。下述三個(gè)方法是完全等同的。
配置需求
1 from pytz import utc 2 3 from apscheduler.schedulers.background import BackgroundScheduler 4 from apscheduler.jobstores.mongodb import MongoDBJobStore 5 from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore 6 from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExec utor 7 8 9 jobstores = { 10 'mongo': MongoDBJobStore(), 11 'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite') 12 } 13 executors = { 14 'default': ThreadPoolExecutor(20), 15 'processpool': ProcessPoolExecutor(5) 16 } 17 job_defaults = { 18 'coalesce': False, 19 'max_instances': 3 20 } 21 scheduler = BackgroundScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc) 1 from apscheduler.schedulers.background import BackgroundScheduler 2 scheduler = BackgroundScheduler({ 3 'apscheduler.jobstores.mongo': { 4 'type': 'mongodb' 5 }, 6 'apscheduler.jobstores.default': { 7 'type': 'sqlalchemy', 8 'url': 'sqlite:///jobs.sqlite' 9 }, 10 'apscheduler.executors.default': { 11 'class': 'apscheduler.executors.pool:ThreadPoolExecutor', 12 'max_workers': '20' 13 }, 14 'apscheduler.executors.processpool': { 15 'type': 'processpool', 16 'max_workers': '5' 17 }, 18 'apscheduler.job_defaults.coalesce': 'false', 19 'apscheduler.job_defaults.max_instances': '3', 20 'apscheduler.timezone': 'UTC', 21 }) 1 from pytz import utc 2 from apscheduler.schedulers.background import BackgroundScheduler 3 from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore 4 from apscheduler.executors.pool import ProcessPoolExecutor 5 6 jobstores = { 7 'mongo': {'type': 'mongodb'}, 8 'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite') 9 } 10 executors = { 11 'default': {'type': 'threadpool', 'max_workers': 20}, 12 'processpool': ProcessPoolExecutor(max_workers=5) 13 } 14 job_defaults = { 15 'coalesce': False, 16 'max_instances': 3 17 } 18 scheduler = BackgroundScheduler() 19 20 # .. do something else here, maybe add jobs etc. 21以上涵蓋了大多數情況的調度器配置,在實(shí)際運行時(shí)可以試試不同的配置會(huì )有怎樣不同的效果。
啟動(dòng)調度器前需要先添加作業(yè),有兩種方法向調度器添加作業(yè):一是通過(guò)接口add_job(),二是通過(guò)使用函數裝飾器,其中 add_job() 返回一個(gè)apscheduler.job.Job類(lèi)的實(shí)例,用于后續修改或刪除作業(yè)。
我們可以隨時(shí)在調度器上調度作業(yè)。如果在添加作業(yè)時(shí),調度器還沒(méi)有啟動(dòng),那么任務(wù)將不會(huì )運行,并且第一次運行時(shí)間在調度器啟動(dòng)時(shí)計算。
注意:如果使用的是序列化作業(yè)的執行器或作業(yè)存儲器,那么要求被調用的作業(yè)(函數)必須是全局可訪(fǎng)問(wèn)的,被調用的作業(yè)的參數是可序列化的,作業(yè)存儲器中,只有 MemoryJobStore 不會(huì )序列化作業(yè)。執行器中,只有ProcessPoolExecutor 將序列化作業(yè)。
啟用調度器只需要調用調度器的 start() 方法,下面分別使用不同的作業(yè)存儲器來(lái)舉例說(shuō)明:
1 #coding:utf-8 2 from apscheduler.schedulers.blocking import BlockingScheduler 3 import datetime 4 from apscheduler.jobstores.memory import MemoryJobStore 5 from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor 6 7 def my_job(id='my_job'): 8 print (id,'-->',datetime.datetime.now()) 9 jobstores = { 10 'default': MemoryJobStore() 11 12 } 13 executors = { 14 'default': ThreadPoolExecutor(20), 15 'processpool': ProcessPoolExecutor(10) 16 } 17 job_defaults = { 18 'coalesce': False, 19 'max_instances': 3 20 } 21 scheduler = BlockingScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults) 22 scheduler.add_job(my_job, args=['job_interval',],id='job_interval',trigger='interval', seconds=5,replace_existing=True) 23 scheduler.add_job(my_job, args=['job_cron',],id='job_cron',trigger='cron',month='4-8,11-12',hour='7-11', second='*/10', 24 end_date='2018-05-30') 25 scheduler.add_job(my_job, args=['job_once_now',],id='job_once_now') 26 scheduler.add_job(my_job, args=['job_date_once',],id='job_date_once',trigger='date',run_date='2018-04-05 07:48:05') 27 try: 28 scheduler.start() 29 except SystemExit: 30 print('exit') 31 exit()運行結果如下:
job_once_now --> 2018-04-05 07:48:00.967391job_date_once --> 2018-04-05 07:48:05.005532job_interval --> 2018-04-05 07:48:05.954023job_cron --> 2018-04-05 07:48:10.004431job_interval --> 2018-04-05 07:48:10.942542job_interval --> 2018-04-05 07:48:15.952208job_cron --> 2018-04-05 07:48:20.007123job_interval --> 2018-04-05 07:48:20.952202……上述代碼使用內存作為作業(yè)存儲器,操作比較簡(jiǎn)單,重啟程序相當于第一次運行。
1 #coding:utf-8 2 from apscheduler.schedulers.blocking import BlockingScheduler 3 import datetime 4 from apscheduler.jobstores.memory import MemoryJobStore 5 from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor 6 from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore 7 def my_job(id='my_job'): 8 print (id,'-->',datetime.datetime.now()) 9 jobstores = { 10 'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite') 11 } 12 executors = { 13 'default': ThreadPoolExecutor(20), 14 'processpool': ProcessPoolExecutor(10) 15 } 16 job_defaults = { 17 'coalesce': False, 18 'max_instances': 3 19 } 20 scheduler = BlockingScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults) 21 scheduler.add_job(my_job, args=['job_interval',],id='job_interval',trigger='interval', seconds=5,replace_existing=True) 22 scheduler.add_job(my_job, args=['job_cron',],id='job_cron',trigger='cron',month='4-8,11-12',hour='7-11', second='*/10', 23 end_date='2018-05-30') 24 scheduler.add_job(my_job, args=['job_once_now',],id='job_once_now') 25 scheduler.add_job(my_job, args=['job_date_once',],id='job_date_once',trigger='date',run_date='2018-04-05 07:48:05') 26 try: 27 scheduler.start() 28 except SystemExit: 29 print('exit') 30 exit() 說(shuō)明,在第 6 行、第 10 行代碼修改為數據庫作為作業(yè)存儲器
運行結果如下:
Run time of job "my_job (trigger: date[2018-04-05 07:48:05 CST], next run at: 2018-04-05 07:48:05 CST)" was missed by 0:18:28.898146job_once_now --> 2018-04-05 08:06:34.010194job_interval --> 2018-04-05 08:06:38.445843job_cron --> 2018-04-05 08:06:40.154978job_interval --> 2018-04-05 08:06:43.285941job_interval --> 2018-04-05 08:06:48.334360job_cron --> 2018-04-05 08:06:50.172968job_interval --> 2018-04-05 08:06:53.281743job_interval --> 2018-04-05 08:06:58.309952提示我們有作業(yè)本應在 2018-04-05 07:48:05 運行的作業(yè)沒(méi)有運行,因為現在的時(shí)間為 2018-04-05 08:06:34,錯過(guò)了 0:18:28 的時(shí)間。
如果將上術(shù)代碼第 21-25 行注釋掉,重新運行本程序,則四種類(lèi)型的作業(yè)仍會(huì )運行,結果如下:
Run time of job "my_job (trigger: cron[month='4-8,11-12', hour='7-11', second='*/10'], next run at: 2018-04-05 08:14:40 CST)" was missed by 0:00:23.680603Run time of job "my_job (trigger: cron[month='4-8,11-12', hour='7-11', second='*/10'], next run at: 2018-04-05 08:14:40 CST)" was missed by 0:00:13.681604Run time of job "my_job (trigger: cron[month='4-8,11-12', hour='7-11', second='*/10'], next run at: 2018-04-05 08:14:40 CST)" was missed by 0:00:03.681604……Run time of job "my_job (trigger: interval[0:00:05], next run at: 2018-04-05 08:14:38 CST)" was missed by 0:00:15.687917Run time of job "my_job (trigger: interval[0:00:05], next run at: 2018-04-05 08:14:38 CST)" was missed by 0:00:10.687917Run time of job "my_job (trigger: interval[0:00:05], next run at: 2018-04-05 08:14:38 CST)" was missed by 0:00:05.687917job_interval --> 2018-04-05 08:14:33.821645job_interval --> 2018-04-05 08:14:38.529167job_cron --> 2018-04-05 08:14:40.150080job_interval --> 2018-04-05 08:14:43.296188job_interval --> 2018-04-05 08:14:48.327317作業(yè)仍會(huì )運行,說(shuō)明作業(yè)被添加到數據庫中,程序中斷后重新運行時(shí)會(huì )自動(dòng)從數據庫讀取作業(yè)信息,而不需要重新再添加到調度器中,如果不注釋 21-25 行添加作業(yè)的代碼,則作業(yè)會(huì )重新添加到數據庫中,這樣就有了兩個(gè)同樣的作業(yè),避免出現這種情況可以在 add_job 的參數中增加 replace_existing=True,如
scheduler.add_job(my_job, args=['job_interval',],id='job_interval',trigger='interval',seconds=3,replace_existing=True)如果我們想運行錯過(guò)運行的作業(yè),使用 misfire_grace_time,如
scheduler.add_job(my_job,args = ['job_cron',] ,id='job_cron',trigger='cron',month='4-8,11-12',hour='7-11',second='*/15',coalesce=True,misfire_grace_time=30,replace_existing=True,end_date='2018-05-30')說(shuō)明:misfire_grace_time,假如一個(gè)作業(yè)本來(lái) 08:00 有一次執行,但是由于某種原因沒(méi)有被調度上,現在 08:01 了,這個(gè) 08:00 的運行實(shí)例被提交時(shí),會(huì )檢查它預訂運行的時(shí)間和當下時(shí)間的差值(這里是1分鐘),大于我們設置的 30 秒限制,那么這個(gè)運行實(shí)例不會(huì )被執行。最常見(jiàn)的情形是 scheduler 被 shutdown 后重啟,某個(gè)任務(wù)會(huì )積攢了好幾次沒(méi)執行如 5 次,下次這個(gè)作業(yè)被提交給執行器時(shí),執行 5 次。設置 coalesce=True 后,只會(huì )執行一次。
其他操作如下:
1 scheduler.remove_job(job_id,jobstore=None)#刪除作業(yè) 2 scheduler.remove_all_jobs(jobstore=None)#刪除所有作業(yè) 3 scheduler.pause_job(job_id,jobstore=None)#暫停作業(yè) 4 scheduler.resume_job(job_id,jobstore=None)#恢復作業(yè) 5 scheduler.modify_job(job_id, jobstore=None, **changes)#修改單個(gè)作業(yè)屬性信息 6 scheduler.reschedule_job(job_id, jobstore=None, trigger=None,**trigger_args)#修改單個(gè)作業(yè)的觸發(fā)器并更新下次運行時(shí)間 7 scheduler.print_jobs(jobstore=None, out=sys.stdout)#輸出作業(yè)信息scheduler 的基本應用,在前面已經(jīng)介紹過(guò)了,但仔細思考一下:如果程序有異常拋出會(huì )影響整個(gè)調度任務(wù)嗎?請看下面的代碼,運行一下看看會(huì )發(fā)生什么情況:
1 # coding:utf-8 2 from apscheduler.schedulers.blocking import BlockingScheduler 3 import datetime 4 5 def aps_test(x): 6 print (1/0) 7 print (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), x) 8 9 scheduler = BlockingScheduler() 10 scheduler.add_job(func=aps_test, args=('定時(shí)任務(wù)',), trigger='cron', second='*/5') 11 12 scheduler.start() 運行結果如下:
Job "aps_test (trigger: cron[second='*/5'], next run at: 2018-04-05 12:46:35 CST)" raised an exceptionTraceback (most recent call last): File "C:\Users\xx\AppData\Local\Programs\python\python36\lib\site-packages\apscheduler\executors\base.py", line 125, in run_job retval = job.func(*job.args, **job.kwargs) File "C:/Users/xx/PycharmProjects/mysite/staff/test2.py", line 7, in aps_test print (1/0)ZeroDivisionError: division by zeroJob "aps_test (trigger: cron[second='*/5'], next run at: 2018-04-05 12:46:35 CST)" raised an exceptionTraceback (most recent call last): File "C:\Users\xx\AppData\Local\Programs\python\python36\lib\site-packages\apscheduler\executors\base.py", line 125, in run_job retval = job.func(*job.args, **job.kwargs) File "C:/Users/xx/PycharmProjects/mysite/staff/test2.py", line 7, in aps_test print (1/0)ZeroDivisionError: division by zero可能看出每 5 秒拋出一次報錯信息。任何代碼都可能拋出異常,關(guān)鍵是,發(fā)生導常事件,如何第一時(shí)間知道,這才是我們最關(guān)心的,apscheduler 已經(jīng)為我們想到了這些,提供了事件監聽(tīng)來(lái)解決這一問(wèn)題。
將上述代碼稍做調整,加入日志記錄和事件監聽(tīng),如下所示。
1 # coding:utf-8 2 from apscheduler.schedulers.blocking import BlockingScheduler 3 from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR 4 import datetime 5 import logging 6 7 logging.basicConfig(level=logging.INFO, 8 format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', 9 datefmt='%Y-%m-%d %H:%M:%S', 10 filename='log1.txt', 11 filemode='a') 12 13 14 def aps_test(x): 15 print (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), x) 16 17 18 def date_test(x): 19 print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), x) 20 print (1/0) 21 22 23 def my_listener(event): 24 if event.exception: 25 print ('任務(wù)出錯了?。。。。?!') 26 else: 27 print ('任務(wù)照常運行...') 28 29 scheduler = BlockingScheduler() 30 scheduler.add_job(func=date_test, args=('一次性任務(wù),會(huì )出錯',), next_run_time=datetime.datetime.now() + datetime.timedelta(seconds=15), id='date_task') 31 scheduler.add_job(func=aps_test, args=('循環(huán)任務(wù)',), trigger='interval', seconds=3, id='interval_task') 32 scheduler.add_listener(my_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR) 33 scheduler._logger = logging 34 35 scheduler.start() 說(shuō)明:
第 7-11 行配置日志記錄信息,日志文件在當前路徑,文件名為 “l(fā)og1.txt”。
第 33 行啟用 scheduler 模塊的日記記錄。
第 23-27 定義一個(gè)事件監聽(tīng),出現意外情況打印相關(guān)信息報警。
運行結果如下所示。
2018-04-05 12:59:29 循環(huán)任務(wù)任務(wù)照常運行...2018-04-05 12:59:32 循環(huán)任務(wù)任務(wù)照常運行...2018-04-05 12:59:35 循環(huán)任務(wù)任務(wù)照常運行...2018-04-05 12:59:38 循環(huán)任務(wù)任務(wù)照常運行...2018-04-05 12:59:41 一次性任務(wù),會(huì )出錯任務(wù)出錯了?。。。。?!2018-04-05 12:59:41 循環(huán)任務(wù)任務(wù)照常運行...2018-04-05 12:59:44 循環(huán)任務(wù)任務(wù)照常運行...2018-04-05 12:59:47 循環(huán)任務(wù)任務(wù)照常運行...在生產(chǎn)環(huán)境中,可以把出錯信息換成發(fā)送一封郵件或者發(fā)送一個(gè)短信,這樣定時(shí)任務(wù)出錯就可以立馬就知道。
(完)
聯(lián)系客服