原创文章,转载请注明出处:​

作者:​​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();
}
}