2017年5月

php守护进程

PHP实现守护进程

<?php
declare(ticks = 1);// 必须声明,不然pcntl_signal不起作用

abstract class Daemon 
{
    protected $pidFile = null;
    protected $running = true;

    public function __construct()
    {
        if (php_sapi_name() != 'cli') {
            $this->log('not cli environment', true);
        }

        global $argc, $argv;

        $this->argc = $argc;
        $this->argv = $argv;
    }

    public function dispatcher()
    {
        switch ($this->argv[1]) {
            case 'start':
                $this->start();
                break;
            case 'stop':
                $this->stop();
                break;
            case 'restart':
                $this->restart();
                break;
            case 'wait':
                $this->wait();
                break;
            default:
                $this->def();
        }
    }
    
    public function wait()
    {
        // 发送一个暂停信号
        posix_kill($this->getPid(), SIGINT);
    }

    public function restart()
    {
        // 发送一个重启信号
        posix_kill($this->getPid(), SIGUSR1);
    }   


    public function def()
    {
        echo "i don't know what to do";
        exit(0);
    }

    public function start()
    {
        // 使php变成守护进程(放到后台执行)
        $pid = pcntl_fork();
        if ($pid < 0) {
            $this->log('pcntl_fork fail', true);
        } elseif ($pid) {
            exit(0); // 退出父进程
        }

        if (posix_setsid() < 0) {
            $this->log('setsid fail', true);
        }
        $curPid = posix_getpid();
        if (!$this->savePid($curPid)) {
            $this->log('save pid fail', true);
        }

        // 注册信号处理函数
        $this->registerSignHandler();
        $this->run();
    }

    public function registerSignHandler()
    {   
        pcntl_signal(SIGINT, array($this, 'sigHandler'));
        pcntl_signal(SIGUSR1,array($this, 'sigHandler'));
        pcntl_signal(SIGTERM,array($this, 'sigHandler'));
    }

    public function sigHandler($signo)
    {
        switch ($signo) {
            case SIGTERM:
                $this->stop();
                break;
            case SIGUSR1:
                $this->running = true;
                break;
            case SIGINT:
                $this->running = false;
                break;
            default:
                echo "handle all other signals...\n";
        }
    }

    public function getPidFile()
    {
        return $this->argv[0] . '.pid';
    }

    public function getPid()
    {
        if (is_file($this->getPidFile())) {
            return file_get_contents($this->getPidFile());
        }
        $this->log('pid file no found', true);
    }

    public function savePid($curPid, $path = null)
    {
        if (is_null($path)) {
            $path = $this->getPidFile();
        }
        return file_put_contents($path, $curPid, LOCK_EX);
    }

    public function stop()
    {
        $pidFile = $this->getPidFile();
        if (file_exists($pidFile)) {
            $pid = $this->getPid();
            posix_kill($pid, SIGTERM);            
            unlink($this->getPidFile());
        }
    }

    public function log($msg, $shutdown)
    {
        $this->logQueue[] = $msg;
        if ($shutdown == true) {
            $this->logRender();
            exit(-1);
        }
    }

    public function logRender()
    {
        print_r($this->logQueue);
    }

    abstract public function run();
}

class EmailDaemon extends Daemon
{
    public function run()
    {
        while (true) {
            while ($this->running) {
                echo 'sending email...';
                echo PHP_EOL;
                sleep(1);
            }  
        }
    }
}

$daemon = new EmailDaemon();
$daemon->dispatcher();

php实现守护进程的两种方式

https://blog.csdn.net/zhang197093/article/details/52226349

进程以某个用户运行, 比如www

class Process {

    public static function runAs($name) {
        $userInfo = posix_getpwnam($name);
        if ($userInfo) {
            $gid = $userInfo['gid'];
            $uid = $userInfo['uid'];
            return posix_setegid($gid) && posix_seteuid($uid);
        }
        return false;
    }

}

daemonize方式

function daemonize()
{
    $pid = pcntl_fork();
    if ($pid == -1) {
        die("fork(1) failed!\n");
    } elseif ($pid > 0) {
        //让由用户启动的进程退出
        exit(0);
    }
    //建立一个有别于终端的新session以脱离终端
    posix_setsid();
    $pid = pcntl_fork();
    if ($pid == -1) {
        die("fork(2) failed!\n");
    } elseif ($pid > 0) {
        //父进程退出, 剩下子进程成为最终的独立进程
        exit(0);
    }
}
daemonize();
sleep(1000);

利用httpsqs实现消息队列

  • 安装
ulimit -SHn 65535

wget http://httpsqs.googlecode.com/files/libevent-2.0.12-stable.tar.gz
tar zxvf libevent-2.0.12-stable.tar.gz
cd libevent-2.0.12-stable/
./configure --prefix=/usr/local/libevent-2.0.12-stable/
make
make install
cd ../

wget http://httpsqs.googlecode.com/files/tokyocabinet-1.4.47.tar.gz
tar zxvf tokyocabinet-1.4.47.tar.gz
cd tokyocabinet-1.4.47/
./configure --prefix=/usr/local/tokyocabinet-1.4.47/
#注:在32位Linux操作系统上编译Tokyo cabinet,请使用./configure --enable-off64代替./configure,可以使数据库文件突破2GB的限制。
#./configure --enable-off64 --prefix=/usr/local/tokyocabinet-1.4.47/
make
make install
cd ../

wget http://httpsqs.googlecode.com/files/httpsqs-1.7.tar.gz
tar zxvf httpsqs-1.7.tar.gz
cd httpsqs-1.7/
make
make install
cd ../
  • httpsqs命令帮助说明
-l <ip_addr> 监听的IP地址,默认值为 0.0.0.0 
-p <num> 监听的TCP端口(默认值:1218)
-x <path> 数据库目录,目录不存在会自动创建(例如:/opt/httpsqs/data)
-t <second> HTTP请求的超时时间(默认值:3)
-s <second> 同步内存缓冲区内容到磁盘的间隔秒数(默认值:5)
-c <num> 内存中缓存的最大非叶子节点数(默认值:1024)
-m <size> 数据库内存缓存大小,单位:MB(默认值:100)
-i <file> 保存进程PID到文件中(默认值:/tmp/httpsqs.pid)
-a <auth> 访问HTTPSQS的验证密码(例如:mypass123)
-d 以守护进程运行
-h 显示这个帮助
  • 运行
ulimit -SHn 65535 
httpsqs -d -p 1218 -x /data0/queue
  • 客户端操作和类库
http://zyan.cc/httpsqs/
  • 生产环境架构