前言
關於如何實現與控制php常駐進程,不管是google還是baidu上進行搜索,都沒有感覺看起來賞心悅目的解答,於是決定自己動手總結下。
有同學會問了,整這個干甚?簡單的說就是,可以讓一個php腳本一直處於運行的狀態。從而實現將項目中某些耗時操作異步化,進隊列后由php腳本取出再執行。
有同學又會問了,直接在服務器直接命令“php test.php &”,不就可以實現了?那么這樣做的話有三點還需要考慮:1.如何保證此進程的穩定性(掛了怎么辦)2.如果想開10個進程,手動去執行10回好像不怎么友好。3.關閉的話需要手動殺死進程?
有同學又會問了,這個不是在crontab加一個定時任務從而一直去執行不就好了,而且保證了穩定性。准確地說這也是可以的,但是有三點需要考慮:1.每回新增隊列都要加一回crontab真的好嗎。2.還是開10的進程問題(同上)。3.那要是關閉的話還要去備注crontab任務?
所以本文所設計的方案主要解決以下幾個問題:
1.如果實現php的常駐?(不依賴第三方php擴展)
2.如何保證進程的穩定性(誰來守護的問題)?
3.如何方便的管理php進程的關閉與重啟?
4.如何方便的管理php進程的並發數?
5.如何進行對php進程的監控?
正文
demo文件描述
cron_demo
1.cron_watchdog.sh:用於添加以及調起php進程
2.cron_watchdogd.sh:用於監控cron_watchdog.sh,保證其一直在運行
3.cron_zombie_alert.sh:用於進程文件的掃描監控,與錯誤通知。
privdata/cron_demo
cron_count.ini:用於控制php進程的並發數
cron_switch.ini:用於控制php進程的開關
cron_status/ :用於存在標記php進程的pid,以供cron_zombie_alert.sh掃描
cron_kill.log:用於記錄cron_zombie_alert.sh掃描到並且kill的僵屍進程。
由於是一看就懂系列,故具體文件解析會附帶上詳細的解讀。
cron_watchdog.sh的實現與解讀
代碼實現
#!/bin/bash #該腳本需在bash版本>=4中執行 #輸出當前地址 CRON_DIR=$(cd $(dirname "$0"); pwd) #執行進程監控腳本的命令 zombie_alert_cmd="/bin/sh $CRON_DIR/cron_zombie_alert.sh& > /dev/null" #獲取php進程並發數配置 CRON_COUNT_INI=/www/privdata/cron_demo/config/cron_count.ini echo $CRON_COUNT_INI #此類寫法需要bash版本>4的支持。(如果mac默認3.x,所以不支持) declare -A deamon_map #key 為cron_count里的key value為命令腳本地址 deamon_map["test"]="$CRON_DIR/test.php" while true; do #循環執行deamon_map里的命令 for deamon_count_key in "${!deamon_map[@]}" ; do echo $deamon_count_key #計算出配置文件里面php進程的並發數 SUM=`grep "^$deamon_count_key *=" "$CRON_COUNT_INI" | awk '{print $3}'` #若在cron_count.ini中不存在,則默認賦值隊列並發數1 if ! (echo $SUM | egrep -q '^[0-9]+$'); then SUM=1 fi php_script="${deamon_map["$deamon_count_key"]}" #計算當前運行中的php進程數目 proc=`/bin/ps xaww | grep -v " grep" | grep "$php_script" |wc -l` current_count=$proc #若小於進程的配置數,則進行調起 if [ $current_count -lt "$SUM" ];then need_to_open_count=`expr $SUM - $current_count` while [ $need_to_open_count -gt 0 ] do php "$php_script" & (( need_to_open_count-- )) done fi done #php進程的監控與消息通知 eval "$zombie_alert_cmd" sleep 1 done
shell關鍵點解讀
1.ps xaww:
a 顯示終端上地所有進程,包括其他用戶地進程
x 顯示沒有控制終端地進程
ww 避免詳細參數被截斷;
2.${!arr[@]} 用於返回數組array的所有下標
3.deamon_map[“test”]=”$CRON_DIR/test.php”
用於添加cron任務,test為腳本在cron_count.ini里面的標號(這里設置為php腳本文件名),test.php為所需要執行的php腳本。
解決問題
1.進程添加問題,只需在此處添加一行配置即可。
2.進程並發管理問題,只需在cron_count.ini配置即可。
cron_watchdogd.sh的實現與解讀
代碼實現
#!/bin/sh CRON_DIR=$(cd $(dirname "$0"); pwd) cmd="/bin/sh $CRON_DIR/cron_watchdog.sh& > /dev/null" #檢測cron_watchdog.sh是否在執行 proc=`/bin/ps xaww | grep -v " grep" | grep -- "cron_watchdog.sh"` #根據返回結果進行判斷腳本是否執行 if test -z "$proc" then #若不執行,那么就調起命令執行 eval "$cmd" fi
此外需要在/etc/crontab中添加以下命令,以保證此腳本定時都會去檢測一回。
*/1 * * * * root sh /www/cron_demo/cron_watchdogd.sh > /dev/null 2>&1
shell關鍵點解讀
1.grep -v:逆向輸出. 打印不匹配模式的行
2.test –z 字符串:測試字符串的長度是否為零
3.eval
此命令會執行兩回后續的語句,在這里,第一回先翻譯"$cmd"成"/bin/sh $CRON_DIR/cron_watchdog.sh& > /dev/null",第二回在進行執行這個命令
解決問題
1.cron_watchdog.sh腳本的監控,保證其不死掉
cron_zombie_alert.sh的實現與解讀
代碼實現
#!/bin/sh PID_LOG=/www/privdata/cron_demo/cron_status/ KILL_LOG=/www/privdata/cron_demo/log/cron_kill.log #用於檢測30分鍾未進行更新的進程文件。 Minute=30 #PID_LOG cd "$PID_LOG" if [ "$?" == 0 ];then #若進程文件30分鍾沒進行更新則認為已經僵死,需要kill並報警 for pid in `find ./ -mmin +"$Minute"| grep -v /$ | awk -F '/' '{print $2}'` do if [ "$pid" != '' ];then NOW=`date +%Y-%m-%d_%H:%M` HOSTNAME=`hostname` nl=' ' PROCESS=`ps p$pid fuh` if [ "$PROCESS" != '' ];then PSTACK=`pstack $pid` #將pid進程信息輸出到tmp.out文件,若2秒之后還在運行再kill此進程 TMP=`timeout 2 strace -p $pid -o tmp.out` STRACE=`cat tmp.out` rm tmp.out fi #組織報警消息 message="$NOW $HOSTNAME zombie process id $pid $nl$PROCESS$nl$PSTACK$nl$STRACE--" echo "$message">>"$KILL_LOG" #kill "$pid" cd "$PID_LOG" #同時刪除進程文件 rm -r "$pid" #進行郵件或者其他的形式將message的內容同步出去 fi done fi
shell關鍵點解讀
1.timeout:
timeout [選項] 數字[后綴] 命令 [參數]...
或:timeout [選項]
運行指定命令,如果在指定時間后仍在運行則殺死該進程。
后綴"s"代表秒(默認值),"m"代表分,"h"代表小時,"d"代表天。
2.strace:
-f :除了跟蹤當前進程外,還跟蹤其子進程。
-o file :將輸出信息寫到文件file中,而不是顯示到標准錯誤輸出(stderr)。
-p pid :綁定到一個由pid對應的正在運行的進程。此參數常用來調試后台進程。
解決問題
1.cron_status底下進程文件的管理。
2.處理php進程僵死的情況。發出相應報警信息。
簡單的例子
用於測試的php腳本片段。主要實現功能:每隔10秒輸出數字到tset_cron.log日志。
<?php define('ROOT_PATH', dirname(__FILE__)); //一些初始化操作,常量定義以及公共函數的引入 include(ROOT_PATH . '/cron_init.php'); $log_file = LOG_DIR . '/test_cron.log'; //獲取進程pid $pid_status_log = CRON_STATUS_DIR . '/' . getmypid(); $i = 0; while (true) { // 將其放入cron_status中以供檢測 file_put_contents($pid_status_log, 1); //從cron_switch.ini中來判斷此進程是否開啟,這個函數后面會單獨解析 $cron_flag = cron_switch(TEST_CRON_KEY); //若關閉則刪掉進程文件 if (!$cron_flag) { unlink($pid_status_log); exit(); } // 可從隊列中取出數據進行處理 // 這里作為例子,以記錄一個日志好了 $msg = $i ."\n"; file_put_contents($log_file, $msg, FILE_APPEND); sleep(10); $i++; }
cron_switch函數
function cron_switch($task = 'all') { //解析cron_switch.ini文件 $cron_switch = parse_ini_file(CRON_SWITCH_FILE); if ($task == 'all') { return $cron_switch; } else { $task = str_replace(KEY_PREFIX, '', $task); $current_cron_switch = $cron_switch [$task]; //第二個判斷用於重啟進程之用。只要將cron_switch的數字設置大一點則認為重啟,設置為0代表關閉,1代表開啟。 if($GLOBALS['current_cron_switch'] && $GLOBALS['current_cron_switch']<$current_cron_switch){ //若小於當前配置文件 $current_cron_switch = 0; } if($current_cron_switch){ //全局標記當前的cron_switch值,用於當作重啟的標志 $GLOBALS['current_cron_switch'] = $current_cron_switch; } return $current_cron_switch; } }
開始運行啦
加好定時任務后發現:
日志每隔10s輸出一個數字了。此時將此php進程kill掉,馬上發現,有重現開始跑了。
此時:
現在試試進程數設置為2(cron_count.ini設置“test = 2”),會發現以上命令后,進程數會變為2個。(這里不截圖了,因為很晚了)
代碼demo
轉載地址:https://blog.csdn.net/u011957758/article/details/52519748