一、場景描述:
最近我們一塊業務,需要不斷的監聽一個目錄的變化,如果目錄中有文件,則啓動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();
}
}