php 利用 Inotify监视程序 用于重启服务器进程

在传统的nginx+php-fpm模式中,每次请求结束后资源都会被释放,下次有新的请求会重新加载文件,所以只要更新了代码即可马上生效,但是在cli命令行模式开发中,开启的php进程服务一般都是守护进程,代码只在开启时进行加载,就算代码有更新也不会重新加载,直到进程结束都还是最开始加载的代码,导致每次更新代码都要重启php服务,这样的体验是非常不好的,我们可以借用swoole+Inotify来解决这个问题

Inotify介绍

inotify是Linux内核提供的一组系统调用,它可以监控文件系统操作,比如文件或者目录的创建、读取、写入、权限修改和删除等。

inotify使用也很简单,使用inotify_init创建一个句柄,然后通过inotify_add_watch/inotify_rm_watch增加/删除对文件和目录的监听。

PHP中提供了inotify扩展,支持了inotify系统调用。inotify本身也是一个文件描述符,可以加入到事件循环中,配合使用swoole扩展,就可以异步非阻塞地实时监听文件/目录变化

Inotif安装

1、可以使用pecl install inotify

2、(https://pecl.php.net/get/inotify-2.0.0.tgz)编译安装步骤跟之前一样,自行安装



在swoole中,我们可以向主进程发送各种不同的信号,主进程根据接收到的信号类型做出不同的处理。比如下面这几个

1、kill -SIGTERM|-15 master_pid  终止Swoole程序,一种优雅的终止信号,会待进程执行完当前程序之后中断,而不是直接干掉进程

2、kill -USR1|-10  master_pid  重启所有的Worker进程

3、kill -USR2|-12  master_pid  重启所有的Task Worker进程 

当USR1信号被发送给Master进程后,Master进程会将同样的信号通过Manager进程转发Worker进程,收到此信号的Worker进程会在处理完正在执行的逻辑之后,释放进程内存,关闭自己,然后由Manager进程重启一个新的Worker进程。新的Worker进程会占用新的内存空间。

具体场景:

如果是上线的项目,一台繁忙的后端服务器随时都在处理请求,如果管理员通过kill进程方式来终止/重启服务器程序,可能导致刚好代码执行到一半终止。

这种情况下会产生数据的不一致。如交易系统中,支付逻辑的下一段是发货,假设在支付逻辑之后进程被终止了。会导致用户支付了货币,但并没有发货,后果非常严重。


/*

*热重启

*/

class Worker{

    //监听socket

    protected $socket = NULL;

    //连接事件回调

    public $onConnect = NULL;

    public  $reusePort=1;

    //接收消息事件回调

    public $onMessage = NULL;

    public $workerNum=3; //子进程个数

    public  $allSocket; //存放所有socket

    public  $addr;

    protected $worker_pid; //子进程pid

    protected  $master_pid;//主进程id

    public function __construct($socket_address) {

        //监听地址+端口

        $this->addr=$socket_address;

        $this->master_pid=posix_getpid();

    }

    public function start() {

        //获取配置文件

        $this->watch();

        $this->fork($this->workerNum);

        $this->monitorWorkers(); //监视程序,捕获信号,监视worker进程

    }

    /**

* 文件监视,自动重启

*/

    protected  function watch(){

        $init=inotify_init(); //初始化

        $files=get_included_files();

        foreach ($files as $file){

            inotify_add_watch($init,$file,IN_MODIFY); //监视相关的文件

        }

        //监听

        swoole_event_add($init,function ($fd){

            $events=inotify_read($fd);

            if(!empty($events)){

                posix_kill($this->master_pid,SIGUSR1);

            }

        });

    }

    /**

* 捕获信号

* 监视worker进程.拉起进程

*/

    public  function monitorWorkers(){

        //注册信号事件回调,是不会自动执行的

// reload

        pcntl_signal(SIGUSR1, array($this, 'signalHandler'),false); //重启woker进程信号

//ctrl+c

        $status=0;

        while (1){

            // 当发现信号队列,一旦发现有信号就会触发进程绑定事件回调

            pcntl_signal_dispatch();

            $pid = pcntl_wait($status); //当信号到达之后就会被中断

//如果进程不是正常情况下的退出,重启子进程,我想要维持子进程个数

//            if($pid>1 && $pid != $this->master_pid  && !pcntl_wifexited($status)){

//                    $index=array_search($pid,$this->worker_pid);

//                    $this->fork(1);

//                    var_dump('拉起子进程');

//                    unset($this->worker_pid[$index]);

//            }

            pcntl_signal_dispatch();

            //进程重启的过程当中会有新的信号过来,如果没有调用pcntl_signal_dispatch,信号不会被处理

        }

}

    public function signalHandler($sigo){

        switch ($sigo){

            case SIGUSR1:

                $this->reload();

                echo "收到重启信号";

                break;

        }

}

    public function fork($worker_num){

        for ($i=0;$i<$worker_num;$i++){

            $test=include 'index.php';

            var_dump($test);

            $pid=pcntl_fork(); //创建成功会返回子进程id

            if($pid<0){

                exit('创建失败');

            }else if($pid>0){

                //父进程空间,返回子进程id

                $this->worker_pid[]=$pid;

            }else{ //返回为0子进程空间

                $this->accept();//子进程负责接收客户端请求

                exit;

            }

}

        //放在父进程空间,结束的子进程信息,阻塞状态

    }

    public  function  accept(){

        $opts = array(

            'socket' => array(

                'backlog' =>10240, //成功建立socket连接的等待个数

            ),

        );

        $context = stream_context_create($opts);

        //开启多端口监听,并且实现负载均衡

        stream_context_set_option($context,'socket','so_reuseport',1);

        stream_context_set_option($context,'socket','so_reuseaddr',1);

        $this->socket=stream_socket_server($this->addr,$errno,$errstr,STREAM_SERVER_BIND|STREAM_SERVER_LISTEN,$context);

        //第一个需要监听的事件(服务端socket的事件),一旦监听到可读事件之后会触发

        swoole_event_add($this->socket,function ($fd){

            $clientSocket=stream_socket_accept($fd);

            //触发事件的连接的回调

            if(!empty($clientSocket) && is_callable($this->onConnect)){

                call_user_func($this->onConnect,$clientSocket);

            }

            //监听客户端可读

            swoole_event_add($clientSocket,function ($fd){

                //从连接当中读取客户端的内容

                $buffer=fread($fd,1024);

                //如果数据为空,或者为false,不是资源类型

                if(empty($buffer)){

                    if(!is_resource($fd) || feof($fd) ){

                        //触发关闭事件

                        fclose($fd);

                    }

}

                //正常读取到数据,触发消息接收事件,响应内容

                if(!empty($buffer) && is_callable($this->onMessage)){

                    call_user_func($this->onMessage,$fd,$buffer);

                }

            });

        });

    }

    /**

* 重启worker进程

*/

    public  function reload(){

        foreach ($this->worker_pid as $index=>$pid){

            posix_kill($pid,SIGKILL); //结束进程

            var_dump("杀掉的子进程",$pid);

            unset($this->worker_pid[$index]);

            $this->fork(1); //重新拉起worker

        }

}

    //捕获信号之后重启worker进程

}

//ps -ef | grep php | grep -v grep | awk '{print $2}' | xargs kill -s 9

$worker = new Worker('tcp://0.0.0.0:9800');

//开启多进程的端口监听

$worker->reusePort = true;

//连接事件

$worker->onConnect = function ($fd) {

    //echo '连接事件触发',(int)$fd,PHP_EOL;

};

$worker->onTask = function ($fd) {

    //echo '连接事件触发',(int)$fd,PHP_EOL;

};

//消息接收

$worker->onMessage = function ($conn, $message) {

    //事件回调当中写业务逻辑

// $a=include 'index.php';

// var_dump($a);

//var_dump($conn,$message);

    $content="我是peter";

    $http_resonse = "HTTP/1.1 200 OK\r\n";

    $http_resonse .= "Content-Type: text/html;charset=UTF-8\r\n";

    $http_resonse .= "Connection: keep-alive\r\n"; //连接保持

    $http_resonse .= "Server: php socket server\r\n";

    $http_resonse .= "Content-length: ".strlen($content)."\r\n\r\n";

    $http_resonse .= $content;

    fwrite($conn, $http_resonse);

};

$worker->start(); //启动

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容