一、场景描写叙述:
近期我们一块业务。须要不断的监听一个文件夹的变化。假设文件夹中有文件,则启动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();
}
}