首頁 >php教程 >php手册 >PHP实现程序单例运行

PHP实现程序单例运行

WBOY
WBOY原創
2016-06-13 09:26:12999瀏覽

PHP实现程序单例运行

 

一、场景描述:

最近我们一块业务,需要不断的监听一个目录的变化,如果目录中有文件,则启动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=&#39;/tmp/&#39;){
        if(empty($filename)) throw new JetException(&#39;filename cannot be empty...&#39;);
        $this->filename = $filename;
        $this->pid_dir = $pid_dir;
        $this->pid_file = $this->pid_dir . DIRECTORY_SEPARATOR . substr(basename($this->filename), 0, -4) . &#39;.pid&#39;;
    }
 
    /**
     * 单例模式启动接口
     * @throws JetException
     */
    public function single(){
        $this->check_pcntl();
        if(file_exists($this->pid_file)) {
            throw new Exception(&#39;the process is already running...&#39;);
        }
        $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, &#39;w&#39;);
        if(!$fp){
            throw new Exception(&#39;cannot create pid file...&#39;);
        }
        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(&#39;pcntl_signal&#39;)) {
            $message = &#39;PHP does not appear to be compiled with the PCNTL extension.  This is neccesary for daemonization&#39;;
            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(&#39;gc_enable&#39;)) {
            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();
    }
}



 

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn