Linux 下监控文件自动实现swoole framework热更新

闽哲
2023-12-01

swoole是以cli运行的,然后长驻内存的。整个生命周期只有在启动的时间可以一次执行RINT过程, 之后所有的请求都在第三步以内完成。(这也是swoole更快的原因之一),这样的话,相关的php脚本如果被执行了一次,就永久性的长驻内存了,更新代码就没有效果了。如果想让代码生效就要重启swoole服务,这种做法是比较粗暴和繁琐。如何才能实现自动检测代码文件?代码自动生效?

在网上查到说使用runkit扩展,经过我测试发现不生效(可能我配置的不对)。
后来找了一个可行的方法,进过整理修改,测试成功。具体步骤如下:

安装inotify扩展

pecl install inotify

在php.ini 最下添加

extension=inotify.so

用于监控文件

创建PHP文件

在 swoole/ToolKit 目录下创建
AutoReload.php

<?php
namespace Swoole\ToolKit;

class NotFound extends \Exception
{

}

class AutoReload
{
    /**
     * @var resource
     */
    protected $inotify;
    protected $pid;

    protected $reloadFileTypes = array('.php' => true);
    protected $watchFiles      = array();
    protected $afterNSeconds   = 1;

    /**
     * 正在reload
     */
    protected $reloading = false;

    protected $events;

    /**
     * 根目录
     * @var array
     */
    protected $rootDirs = array();

    public function putLog($log)
    {
        $_log = "[" . date('Y-m-d H:i:s') . "]\t" . $log . "\n";
        echo $_log;
    }

    /**
     * @param $serverPid
     * @throws NotFound
     */
    public function __construct($serverPid)
    {
        $this->pid = $serverPid;
        if (posix_kill($serverPid, 0) === false) {
            throw new NotFound("Process#$serverPid not found.");
        }

        $this->inotify = inotify_init();
        $this->events  = IN_MODIFY | IN_DELETE | IN_CREATE | IN_MOVE;

        swoole_event_add($this->inotify, function ($ifd) {
            $events = inotify_read($this->inotify);
            if (!$events) {
                return;
            }
            // var_dump($events);
            foreach ($events as $ev) {
                if ($ev['mask'] == IN_IGNORED) {
                    continue;
                } else if ($ev['mask'] == IN_CREATE or $ev['mask'] == IN_DELETE or $ev['mask'] == IN_MODIFY or $ev['mask'] == IN_MOVED_TO or $ev['mask'] == IN_MOVED_FROM) {
                    $fileType = strrchr($ev['name'], '.');
                    //非重启类型
                    if (!isset($this->reloadFileTypes[$fileType])) {
                        continue;
                    }
                }
                //正在reload,不再接受任何事件,冻结1秒
                if (!$this->reloading) {
                    $this->putLog("after 1 seconds reload the server");
                    //有事件发生了,进行重启
                    swoole_timer_after($this->afterNSeconds * 1000, array($this, 'reload'));
                    $this->reloading = true;
                }
            }
        });
    }

    public function reload()
    {
        $this->putLog("reloading");
        //向主进程发送信号
        posix_kill($this->pid, SIGUSR1);
        //清理所有监听
        $this->clearWatch();
        //重新监听
        foreach ($this->rootDirs as $root) {
            $this->watch($root);
        }
        //继续进行reload
        $this->reloading = false;
    }

    /**
     * 添加文件类型
     * @param $type
     */
    public function addFileType($type)
    {
        $type                               = trim($type, '.');
        $this->reloadFileTypes['.' . $type] = true;
    }

    /**
     * 添加事件
     * @param $inotifyEvent
     */
    public function addEvent($inotifyEvent)
    {
        $this->events |= $inotifyEvent;
    }

    /**
     * 清理所有inotify监听
     */
    public function clearWatch()
    {
        foreach ($this->watchFiles as $wd) {
            inotify_rm_watch($this->inotify, $wd);
        }
        $this->watchFiles = array();
    }

    /**
     * @param $dir
     * @param bool $root
     * @return bool
     * @throws NotFound
     */
    public function watch($dir, $root = true)
    {
        //目录不存在
        if (!is_dir($dir)) {
            throw new NotFound("[$dir] is not a directory.");
        }
        //避免重复监听
        if (isset($this->watchFiles[$dir])) {
            return false;
        }
        //根目录
        if ($root) {
            $this->rootDirs[] = $dir;
        }

        $wd                     = inotify_add_watch($this->inotify, $dir, $this->events);
        $this->watchFiles[$dir] = $wd;

        $files = scandir($dir);
        foreach ($files as $f) {
            if ($f == '.' or $f == '..') {
                continue;
            }
            $path = $dir . '/' . $f;
            //递归目录
            if (is_dir($path)) {
                $this->watch($path, false);
            }
            //检测文件类型
            $fileType = strrchr($f, '.');
            if (isset($this->reloadFileTypes[$fileType])) {
                $wd                      = inotify_add_watch($this->inotify, $path, $this->events);
                $this->watchFiles[$path] = $wd;
            }
        }
        return true;
    }

    public function run()
    {
        swoole_event_wait();
    }
}

在 swoole 文件夹下创建
daemon.php

<?php
require __DIR__ . '/ToolKit/AutoReload.php';
//存放SWOOLE服务的PID
$file = __DIR__ . '/../../examples/app_server.pid';
if (file_exists($file)) {
    $pid = file_get_contents($file);
    $kit = new Swoole\ToolKit\AutoReload((int)$pid);
    $kit->watch(__DIR__ . '/../../');//监控的目录
    $kit->run();
}

创建启动脚本

vim swoole.sh

#!/bin/bash

apppidpath="/var/www/examples/app_server.pid"
pid="/tmp/swoole.pid" #进程锁定
if [ -e $pid ]
then
        exit 0
fi
echo $$ > ${pid}

       if [ -f ${apppidpath} ]
        then
            nohup php /var/www/libs/Swoole/daemon.php  </dev/null &>/dev/null &           
        echo "deamon is ok!"
        else
            echo "swoole is stopped!"
        fi

#删除锁定文件
rm $pid -rf

chmod +x swoole.sh

启动监控程序

./swoole.sh

至此,Linux监控文件自动实现swoole framework热更新部署完成,大家可以修改代码看看是否生效。
如果在使用过程中出现什么问题,欢迎批评指正。如有好的建议,也希望能给予回复,再次感谢。

 类似资料: