一個(gè)簡單的需求,即定時(shí)啟動(dòng)python腳本,這種需求很常見,比如定時(shí)啟動(dòng)一段程序?qū)Ψ?wù)器狀態(tài)進(jìn)行收集,寫到文件中,方便運(yùn)維后期審計(jì),查看服務(wù)器占用高峰時(shí)間段,從而判斷出公司產(chǎn)品在該時(shí)間段較多人使用,或定時(shí)清除其他程序的日志,釋放線上服務(wù)器的空間,這塊常見的架構(gòu)是有個(gè)轉(zhuǎn)存程序,將日志通過nginx文件服務(wù)掛起,然后該程序請(qǐng)求這種文件,將其存儲(chǔ)在數(shù)據(jù)服務(wù)器中,而線上服務(wù)器的日志就不需要了(游戲日志通常比較大,所以轉(zhuǎn)存程序也需要設(shè)計(jì)一下)。
本章主要來實(shí)現(xiàn)一下定時(shí)啟動(dòng)python的需求,當(dāng)然,定時(shí)啟動(dòng)其他任何程序也都一樣。
Python threading模塊
一開始,為了省事,直接使用python的threading模塊,threading模塊下有個(gè)Timer模塊,它可以實(shí)現(xiàn)定時(shí)啟動(dòng)python程序的需求,用法如下:
from threading import Timer
def timedTask():
? ?print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
? ?main() #主題程序邏輯
? ?global timer
? ?timer = Timer(300,timedTask)
? ?timer.start()
if __name__ == '__main__':
? ?timedTask()
值得一提的是,timer需要使用global timer,據(jù)說嘗試運(yùn)行時(shí),會(huì)釋放無需使用的占用資源。
實(shí)現(xiàn)方法很簡單,即創(chuàng)建Timer()實(shí)例,傳入兩個(gè)參數(shù),分別是時(shí)間間隔(單位為秒)與定時(shí)任務(wù)本身,構(gòu)成一個(gè)死遞歸(因?yàn)闆]有逃出條件),然后就是調(diào)用Timer實(shí)例的start()方法。
不推薦,雖然網(wǎng)上博客說使用global timer會(huì)釋放無用資源,但實(shí)際沒有考證,這種寫法在服務(wù)器上跑起來的程序通常一天就斷,我周日啟動(dòng)該程序,周一來公司看,對(duì)應(yīng)的python程序掛了。
APScheduler
APScheduler是Python用于執(zhí)行定時(shí)操作的第三方框架,作為一個(gè)框架,它就有它對(duì)應(yīng)的各種概念,沒必要搞那么復(fù)雜,學(xué)習(xí)成本有點(diǎn)高,放棄
Linux crontab
最總還是轉(zhuǎn)到了Linux的crontab服務(wù),該服務(wù)主要就是用于實(shí)現(xiàn)定時(shí)任務(wù)的,其語法如下:
# .---------------- minute (0 - 59)
# | ?.------------- hour (0 - 23)
# | ?| ?.---------- day of month (1 - 31)
# | ?| ?| ?.------- month (1 - 12) OR jan,feb,mar,apr ...
# | ?| ?| ?| ?.---- day of week (0 - 6) (Sunday=0 or 7) ?OR
#sun,mon,tue,wed,thu,fri,sat
# | ?| ?| ?| ?|
# * ?* ?* ?* ?* ?command to be executed
minute:代表一小時(shí)內(nèi)的第幾分,范圍 0-59。
hour:代表一天中的第幾小時(shí),范圍 0-23。
mday:代表一個(gè)月中的第幾天,范圍 1-31。
month:代表一年中第幾個(gè)月,范圍 1-12。
wday:代表星期幾,范圍 0-7 (0及7都是星期天)。
who:要使用什么身份執(zhí)行該指令,當(dāng)您使用 crontab -e 時(shí),不必加此字段。
command:所要執(zhí)行的指令。
crontab服務(wù)狀態(tài)
sudo service crond start ? ? #啟動(dòng)服務(wù)
sudo service crond stop ? ? ?#關(guān)閉服務(wù)
sudo service crond restart ? #重啟服務(wù)
sudo service crond reload ? ?#重新載入配置
sudo service crond status ? ?#查看服務(wù)狀態(tài)
查看定時(shí)任務(wù)
crontab -l
到這里,關(guān)于crontab常見的文件就是叫你使用 crontab-e來編寫對(duì)應(yīng)crontab配置文件,配置內(nèi)容的語法如上,例子如下:
# 每天早上6點(diǎn)
0 6 * * * echo "Good morning." >> /tmp/test.txt //注意單純echo,從屏幕上看不到任何輸出,因?yàn)閏ron把任何輸出都email到root的信箱了。
# 每兩個(gè)小時(shí)
0 */2 * * * echo "Have a break now." >> /tmp/test.txt ?
# 晚上11點(diǎn)到早上8點(diǎn)之間每兩個(gè)小時(shí)和早上八點(diǎn)
0 23-7/2,8 * * * echo "Have a good dream" >> /tmp/test.txt
但這邊不會(huì)這樣操作,這種寫法并不適合于真正的工作中,就是一個(gè)Toy,我希望的是全自動(dòng)化,這里通過shell腳本來實(shí)現(xiàn)自動(dòng)添加crontab任務(wù)。
shell腳本代碼如下:
#! /bin/bash
cd LogSys/
work_path=/x1_game/agent_flask/LogSys/
if [ ! -n "$1" ]; then
? ?echo "Usages: sh startLog.sh [start|stop]"
? ?exit 0
fi
if [ "$1" = start ] ;then
? ?if [ ! -x logs ] ;then
? ? ? ?mkdir logs
? ?fi
? ?if [ -f logs/logsys.ini ] ;then
? ? ? ?echo "logsys task is in crontab, can not add the task to crontab agent!"
? ? ? ?exit 0
? ?else
? ? ? ?echo $(date "+%Y_%m_%d") >> logs/logsys.ini
? ? ? ?chmod 777 start.sh
? ? ? ?echo "* * * * * ${work_path}start.sh start >> ${work_path}logs/cron.log 2>&1" >> /var/spool/cron/root
? ? ? ?echo "add LogSys task to crontab"
? ?fi
elif [ "$1" = stop ] ;then
? ?if [ -f logs/logsys.ini ] ;then
? ? ? ?rm -rf logs/logsys.ini
? ? ? ?rm -rf /var/spool/cron/root
? ? ? ?echo "Stop success and remove the logsys.ini"
? ?else
? ? ? ?echo "logsys is not running"
? ?fi
fi
這是我使用的完整shell腳本,這里自動(dòng)添加crontab任務(wù)的命令只有一行,就是 echo"* * * * * ${work_path}start.sh start >> ${work_path}logs/cron.log 2>&1">>/var/spool/cron/root,這個(gè)命令會(huì)每分鐘都會(huì)調(diào)用start.sh腳本,而start.sh腳本中啟動(dòng)了python,幾個(gè)坑需要注意,crontab中請(qǐng)使用絕對(duì)路徑,因?yàn)閏rontab啟動(dòng)程序時(shí),相對(duì)路徑所對(duì)應(yīng)的坐標(biāo)系其實(shí)與你手動(dòng)啟動(dòng)該腳本時(shí)是不同的,使用絕對(duì)路徑省事,這里還將star.sh腳本的輸出內(nèi)容都重定向到對(duì)應(yīng)的日志文件中。
為什么不直接通過crontab啟動(dòng)python程序呢?而是要再繞一層,通過shell腳本來啟動(dòng),這其實(shí)也是一個(gè)坑,除非你是單python文件,不然通常都使用shell腳本的形式啟動(dòng)python,而不在直接使用crontab來啟動(dòng),這同樣是因?yàn)閏rontab啟動(dòng)的任務(wù)相對(duì)路徑的坐標(biāo)系改變了,多文件的python項(xiàng)目相互引入文件時(shí),使用的坐標(biāo)系與crontab啟動(dòng)時(shí)不同,導(dǎo)致crontab直接啟動(dòng)python項(xiàng)目會(huì)失敗,所以技巧就在于,通過shell腳本來啟動(dòng)python程序,在啟動(dòng)前,通過cd命令進(jìn)入python項(xiàng)目對(duì)應(yīng)的目錄,這樣就將啟動(dòng)時(shí)的相對(duì)路徑的坐標(biāo)系改成與python的一致了
具體可以看一下我的start.sh腳本,代碼如下:
#! /bin/bash
currTime=$(date "+%Y_%m_%d")
logfile=${currTime}_logsys.log
if [ ! -n "$1" ]; then
? ?echo "Usages: sh start.sh [start|stop]"
? ?exit 0
fi
if [ ! -x logs ] ;then
? ?mkdir logs
fi
pid=`ps -ef | grep log2mysql | grep -v grep|awk '{print $2}'`
if [ "$1" = start ] ;then
? ?if [ -n "$pid" ] ;then
? ? ? ?echo "log2mysql is running, can not start"
? ? ? ?exit 0
? ?fi
? ?cd /x1_game/agent_flask/LogSys/
? ?nohup /usr/local/bin/python -u log2mysql.py >> logs/$logfile 2>&1 &
? ?pid=`ps -ef | grep log2mysql | grep -v grep|awk '{print $2}'`
? ?echo "start log2mysql, pid is:"$pid
elif [ "$1" = stop ] ;then
? ?if [ -n "$pid" ] ;then
? ? ? ?echo "kill log2mysql, pid is: "$pid
? ? ? ?kill -9 $pid
? ?else
? ? ? ?echo "log2mysql is not running "$pid
? ?fi
fi
通過python啟動(dòng)任務(wù)的關(guān)鍵命令在于
cd /x1_game/agent_flask/LogSys/
nohup /usr/local/bin/python -u log2mysql.py >> logs/$logfile 2>&1 &
首先會(huì)進(jìn)入要啟動(dòng)python項(xiàng)目的所在目錄,然后再通過python啟動(dòng)對(duì)應(yīng)的py文件,這里使用python解釋器同樣要使用全路徑,因?yàn)榫€上系統(tǒng)中存在多個(gè)python,因?yàn)樵損ython程序是耗時(shí)程序,所以我希望它在后臺(tái)運(yùn)行,所以使用了 nohup與 &關(guān)鍵字,將其放在后臺(tái)去運(yùn)行。
題外話:centos系統(tǒng)中的yum是依賴python的,具體到centos6,其yum依賴系統(tǒng)本身就存在的python2.6,但開發(fā)環(huán)境通常要使用python2.7,此時(shí)最好不要?jiǎng)h除系統(tǒng)中自帶的python2.6,如果你直接刪除,會(huì)導(dǎo)致yum使用不了,此時(shí)就需要修改一下yum對(duì)應(yīng)文件中的python指向,最好的方法就是直接安裝python2.7,然后在/usr/bin下創(chuàng)建對(duì)應(yīng)的軟連接來使用。
小結(jié)
python程序員在工作中其實(shí)不能只會(huì)python,因?yàn)閜ython雖然強(qiáng)大,但也會(huì)有其缺陷,所以什么好用,用什么才是對(duì)的,還有python是一種語言,不要被語言局限。