標籤:php 指令碼 單例
原創文章,轉載請註明出處:http://huyanping.sinaapp.com/?p=222
Jenner
一、情境描述:
最近我們一塊業務,需要不斷的監聽一個目錄的變化,如果目錄中有檔案,則啟動PHP指令碼處理掉。最初的方案是使用crontab執行sh指令碼,指令碼大概如下:
SOK=`ps -ef |grep /www/sender.sh | grep -v grep|wc -l`if [[ "$SOK" < "2" ]];then for f in `ls /www/queue`; do php /www/logsender.php /www/queue/$f done
實際運行中出現了異常:ps -ef | grep xxx的方式,可能無法正確的判斷進程是否正在執行,if條件永遠都不會成立,使得PHP指令碼永遠不會執行。經過考慮後,決定建立一個獨立於其他模組的,能夠實現進程單例啟動並執行類,解決這個問題。
二、方案設計
1、通過PID檔案實現進程單例
2、程式啟動、退出自動建立、刪除PID檔案,做到不需要業務代碼考慮PID檔案刪除
3、盡量保證代碼獨立性,不影響業務代碼
三、原理
1、啟動建立PID檔案
2、綁定程式退出、被殺死等訊號量,用於刪除PID檔案
3、添加解構函式,對象被銷毀時,刪除PID檔案
四、遇到的問題
程式正常退出時,無法捕獲到訊號量,不知道是不是訊號量選錯了,ctrl+c等訊號是正常的,如果可以解決捕獲程式正常退出時的訊號量,則可以替代解構函式方案,更加穩定
五、代碼
<?php/** * Created by PhpStorm. * User: huyanping * Date: 14-8-13 * Time: 下午2:25 * * 實現程式單例運行,調用方式: * declare(ticks = 1);//注意:一定要在外部調用檔案中首部調用該聲明,否則程式會無法監聽到訊號量 * $single = new DaemonSingle(__FILE__); * $single->single(); * */ class DaemonSingle { //PID檔案路徑 private $pid_dir; //PID檔案名稱 private $filename; //PID檔案完整路徑名稱 private $pid_file; /** * 建構函式 * @param $filename * @param string $pid_dir */ public function __construct($filename, $pid_dir='/tmp/'){ if(empty($filename)) throw new JetException('filename cannot be empty...'); $this->filename = $filename; $this->pid_dir = $pid_dir; $this->pid_file = $this->pid_dir . DIRECTORY_SEPARATOR . substr(basename($this->filename), 0, -4) . '.pid'; } /** * 單例模式啟動介面 * @throws JetException */ public function single(){ $this->check_pcntl(); if(file_exists($this->pid_file)) { throw new Exception('the process is already running...'); } $this->create_pid_file(); } /** * @throws JetException */ private function create_pid_file() { if (!is_dir($this->pid_dir)) { mkdir($this->pid_dir); } $fp = fopen($this->pid_file, 'w'); if(!$fp){ throw new Exception('cannot create pid file...'); } fwrite($fp, posix_getpid()); fclose($fp); $this->pid_create = true; } /** * 環境檢查 * @throws Exception */ public function check_pcntl() { // Make sure PHP has support for pcntl if (!function_exists('pcntl_signal')) { $message = 'PHP does not appear to be compiled with the PCNTL extension. This is neccesary for daemonization'; throw new Exception($message); } //訊號處理 pcntl_signal(SIGTERM, array(&$this, "signal_handler")); pcntl_signal(SIGINT, array(&$this, "signal_handler")); pcntl_signal(SIGQUIT, array(&$this, "signal_handler")); // Enable PHP 5.3 garbage collection if (function_exists('gc_enable')) { gc_enable(); $this->gc_enabled = gc_enabled(); } } /** * 訊號處理函數,程式異常退出時,安全刪除PID檔案 * @param $signal */ public function signal_handler($signal) { switch ($signal) { case SIGINT : case SIGQUIT: case SIGTERM:{ self::safe_quit(); break; } } } /** * 安全退出,刪除PID檔案 */ public function safe_quit() { if (file_exists($this->pid_file)) { $pid = intval(posix_getpid()); $file_pid = intval(file_get_contents($this->pid_file)); if($pid == $file_pid){ unlink($this->pid_file); } } posix_kill(0, SIGKILL); exit(0); } /** * 解構函式,刪除PID檔案 */ public function __destruct(){ $this->safe_quit(); }}