一個簡單的需求,即定時啟動python腳本,這種需求很常見,比如定時啟動一段程序對服務器狀態進行收集,寫到文件中,方便運維后期審計,查看服務器占用高峰時間段,從而判斷出公司產品在該時間段較多人使用,或定時清除其他程序的日志,釋放線上服務器的空間,這塊常見的架構是有個轉存程序,將日志通過nginx文件服務掛起,然后該程序請求這種文件,將其存儲在數據服務器中,而線上服務器的日志就不需要了(游戲日志通常比較大,所以轉存程序也需要設計一下)。
本章主要來實現一下定時啟動python的需求,當然,定時啟動其他任何程序也都一樣。
Python threading模塊
一開始,為了省事,直接使用python的threading模塊,threading模塊下有個Timer模塊,它可以實現定時啟動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,據說嘗試運行時,會釋放無需使用的占用資源。
實現方法很簡單,即創建Timer()實例,傳入兩個參數,分別是時間間隔(單位為秒)與定時任務本身,構成一個死遞歸(因為沒有逃出條件),然后就是調用Timer實例的start()方法。
不推薦,雖然網上博客說使用global timer會釋放無用資源,但實際沒有考證,這種寫法在服務器上跑起來的程序通常一天就斷,我周日啟動該程序,周一來公司看,對應的python程序掛了。
APScheduler
APScheduler是Python用于執行定時操作的第三方框架,作為一個框架,它就有它對應的各種概念,沒必要搞那么復雜,學習成本有點高,放棄
Linux crontab
最總還是轉到了Linux的crontab服務,該服務主要就是用于實現定時任務的,其語法如下:
# .---------------- 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:代表一小時內的第幾分,范圍 0-59。
hour:代表一天中的第幾小時,范圍 0-23。
mday:代表一個月中的第幾天,范圍 1-31。
month:代表一年中第幾個月,范圍 1-12。
wday:代表星期幾,范圍 0-7 (0及7都是星期天)。
who:要使用什么身份執行該指令,當您使用 crontab -e 時,不必加此字段。
command:所要執行的指令。
crontab服務狀態
sudo service crond start ? ? #啟動服務
sudo service crond stop ? ? ?#關閉服務
sudo service crond restart ? #重啟服務
sudo service crond reload ? ?#重新載入配置
sudo service crond status ? ?#查看服務狀態
查看定時任務
crontab -l
到這里,關于crontab常見的文件就是叫你使用 crontab-e
來編寫對應crontab配置文件,配置內容的語法如上,例子如下:
# 每天早上6點
0 6 * * * echo "Good morning." >> /tmp/test.txt //注意單純echo,從屏幕上看不到任何輸出,因為cron把任何輸出都email到root的信箱了。
# 每兩個小時
0 */2 * * * echo "Have a break now." >> /tmp/test.txt ?
# 晚上11點到早上8點之間每兩個小時和早上八點
0 23-7/2,8 * * * echo "Have a good dream" >> /tmp/test.txt
但這邊不會這樣操作,這種寫法并不適合于真正的工作中,就是一個Toy,我希望的是全自動化,這里通過shell腳本來實現自動添加crontab任務。
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腳本,這里自動添加crontab任務的命令只有一行,就是 echo"* * * * * ${work_path}start.sh start >> ${work_path}logs/cron.log 2>&1">>/var/spool/cron/root
,這個命令會每分鐘都會調用start.sh腳本,而start.sh腳本中啟動了python,幾個坑需要注意,crontab中請使用絕對路徑,因為crontab啟動程序時,相對路徑所對應的坐標系其實與你手動啟動該腳本時是不同的,使用絕對路徑省事,這里還將star.sh腳本的輸出內容都重定向到對應的日志文件中。
為什么不直接通過crontab啟動python程序呢?而是要再繞一層,通過shell腳本來啟動,這其實也是一個坑,除非你是單python文件,不然通常都使用shell腳本的形式啟動python,而不在直接使用crontab來啟動,這同樣是因為crontab啟動的任務相對路徑的坐標系改變了,多文件的python項目相互引入文件時,使用的坐標系與crontab啟動時不同,導致crontab直接啟動python項目會失敗,所以技巧就在于,通過shell腳本來啟動python程序,在啟動前,通過cd命令進入python項目對應的目錄,這樣就將啟動時的相對路徑的坐標系改成與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啟動任務的關鍵命令在于
cd /x1_game/agent_flask/LogSys/
nohup /usr/local/bin/python -u log2mysql.py >> logs/$logfile 2>&1 &
首先會進入要啟動python項目的所在目錄,然后再通過python啟動對應的py文件,這里使用python解釋器同樣要使用全路徑,因為線上系統中存在多個python,因為該python程序是耗時程序,所以我希望它在后臺運行,所以使用了 nohup
與 &
關鍵字,將其放在后臺去運行。
題外話:centos系統中的yum是依賴python的,具體到centos6,其yum依賴系統本身就存在的python2.6,但開發環境通常要使用python2.7,此時最好不要刪除系統中自帶的python2.6,如果你直接刪除,會導致yum使用不了,此時就需要修改一下yum對應文件中的python指向,最好的方法就是直接安裝python2.7,然后在/usr/bin下創建對應的軟連接來使用。
小結
python程序員在工作中其實不能只會python,因為python雖然強大,但也會有其缺陷,所以什么好用,用什么才是對的,還有python是一種語言,不要被語言局限。