一分一毛也是爱

微信

微信

支付宝

支付宝

观海听潮

观海听潮博客

登录
还没有账号?去注册
观海听潮

观海听潮博客

注册
×

我的名片

网名:观海听潮

职业:PHP开发工程师

现居:山东省-青岛市

Email:[email protected]

网站统计

  • 观海听潮•博客
  • 70篇
  • 149条
  • 85686次
  • 1755次
  • 美国弗吉尼亚州

您现在的位置是:首页  > 技术杂谈  > Laravel  > swoole  > php php

观海听潮

laravel5.8+php7.1+swooleTW2.5+inotify实现文件热更新

摘要
利用swooleTW扩展加速laravel项目后,为避免更新代码后都需要重启swoole,实现热更新

前提:配置好环境,安装swooleTW2.5扩展和inotify,如果安装大于swooleTW2.5版本的扩展,就不要直接用下面的文件,单独拷方法即可。

1、配件置文

config目录下的配置文件 swoole_http.php

<?php

return [
    /*
    |--------------------------------------------------------------------------
    | HTTP server configurations.
    |--------------------------------------------------------------------------
    |
    | @see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
    |
     */
    'server' => [
        'host' => env('SWOOLE_HTTP_HOST', '127.0.0.1'),
        'port' => env('SWOOLE_HTTP_PORT', '1215'),
        'public_path' => base_path('public'),
        // Determine if to use swoole to respond request for static files
        'handle_static_files' => env('SWOOLE_HANDLE_STATIC', true),
        // You must add --enable-openssl while compiling Swoole
        // Put `SWOOLE_SOCK_TCP | SWOOLE_SSL` if you want to enable SSL
        'socket_type' => SWOOLE_SOCK_TCP,
        'options' => [
            'pid_file' => env('SWOOLE_HTTP_PID_FILE', base_path('storage/logs/swoole_http.pid')),
            'log_file' => env('SWOOLE_HTTP_LOG_FILE', base_path('storage/logs/swoole_http.log')),
            'daemonize' => env('SWOOLE_HTTP_DAEMONIZE', true),
            // Normally this value should be 1~4 times larger according to your cpu cores.
            'reactor_num' => env('SWOOLE_HTTP_REACTOR_NUM', swoole_cpu_num()),
            'worker_num' => env('SWOOLE_HTTP_WORKER_NUM', swoole_cpu_num()),
            'task_worker_num' => env('SWOOLE_HTTP_TASK_WORKER_NUM', swoole_cpu_num()),
            // The data to receive can't be larger than buffer_output_size.
            'package_max_length' => 20 * 1024 * 1024,
            // The data to send can't be larger than buffer_output_size.
            'buffer_output_size' => 10 * 1024 * 1024,
            // Max buffer size for socket connections
            'socket_buffer_size' => 128 * 1024 * 1024,
            // Worker will restart after processing this number of requests
            'max_request' => 3000,
            // Enable coroutine send
            'send_yield' => true,
            // You must add --enable-openssl while compiling Swoole
            'ssl_cert_file' => null,
            'ssl_key_file' => null,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Enable to turn on websocket server.
    |--------------------------------------------------------------------------
     */
    'websocket' => [
        'enabled' => env('SWOOLE_HTTP_WEBSOCKET', false),
    ],
    //文件监控热更新目录文件 ext后缀文件更新
    'watch' => [
        'file' => [
            app_path('business.php'),
            app_path('common.php'),
        ],
        'dir' => [
            app_path('Modules'),
            app_path('Models'),
            app_path('Services'),
            base_path('config'),
        ],
        'ext' => ['php']
    ],

    /*
    |--------------------------------------------------------------------------
    | Console output will be transferred to response content if enabled.
    |--------------------------------------------------------------------------
     */
    'ob_output' => env('SWOOLE_OB_OUTPUT', true),

    /*
    |--------------------------------------------------------------------------
    | Pre-resolved instances here will be resolved when sandbox created.
    |--------------------------------------------------------------------------
     */
    'pre_resolved' => [
        'view', 'files', 'session', 'session.store', 'routes',
        'db', 'db.factory', 'cache', 'cache.store', 'config', 'cookie',
        'encrypter', 'hash', 'router', 'translator', 'url', 'log',
    ],

    /*
    |--------------------------------------------------------------------------
    | Instances here will be cleared on every request.
    |--------------------------------------------------------------------------
     */
    'instances' => [
        //
    ],

    /*
    |--------------------------------------------------------------------------
    | Providers here will be registered on every request.
    |--------------------------------------------------------------------------
     */
    'providers' => [
        Illuminate\Pagination\PaginationServiceProvider::class,
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetters for sandbox app.
    |--------------------------------------------------------------------------
     */
    'resetters' => [
        SwooleTW\Http\Server\Resetters\ResetConfig::class,
        SwooleTW\Http\Server\Resetters\ResetSession::class,
        SwooleTW\Http\Server\Resetters\ResetCookie::class,
        SwooleTW\Http\Server\Resetters\ClearInstances::class,
        SwooleTW\Http\Server\Resetters\BindRequest::class,
        SwooleTW\Http\Server\Resetters\RebindKernelContainer::class,
        SwooleTW\Http\Server\Resetters\RebindRouterContainer::class,
        SwooleTW\Http\Server\Resetters\RebindViewContainer::class,
        SwooleTW\Http\Server\Resetters\ResetProviders::class,
    ],

    /*
    |--------------------------------------------------------------------------
    | Define your swoole tables here.
    |
    | @see https://www.swoole.co.uk/docs/modules/swoole-table
    |--------------------------------------------------------------------------
     */
    'tables' => [
        // 'table_name' => [
        //     'size' => 1024,
        //     'columns' => [
        //         ['name' => 'column_name', 'type' => Table::TYPE_STRING, 'size' => 1024],
        //     ]
        // ],
    ],
];

vendor/swooletw/laravel-swoole/config目录下的配置文件 swoole_http.php

<?php

use Swoole\Table;

return [
    /*
    |--------------------------------------------------------------------------
    | HTTP server configurations.
    |--------------------------------------------------------------------------
    |
    | @see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
    |
    */
    'server' => [
        'host' => env('SWOOLE_HTTP_HOST', '127.0.0.1'),
        'port' => env('SWOOLE_HTTP_PORT', '1215'),
        'public_path' => base_path('public'),
        // Determine if to use swoole to respond request for static files
        'handle_static_files' => env('SWOOLE_HANDLE_STATIC', true),
        // You must add --enable-openssl while compiling Swoole
        // Put `SWOOLE_SOCK_TCP | SWOOLE_SSL` if you want to enable SSL
        'socket_type' => SWOOLE_SOCK_TCP,
        'options' => [
            'pid_file' => env('SWOOLE_HTTP_PID_FILE', base_path('storage/logs/swoole_http.pid')),
            'log_file' => env('SWOOLE_HTTP_LOG_FILE', base_path('storage/logs/swoole_http.log')),
            'daemonize' => env('SWOOLE_HTTP_DAEMONIZE', false),
            // Normally this value should be 1~4 times larger according to your cpu cores.
            'reactor_num' => env('SWOOLE_HTTP_REACTOR_NUM', swoole_cpu_num()),
            'worker_num' => env('SWOOLE_HTTP_WORKER_NUM', swoole_cpu_num()),
            'task_worker_num' => env('SWOOLE_HTTP_TASK_WORKER_NUM', swoole_cpu_num()),
            // The data to receive can't be larger than buffer_output_size.
            'package_max_length' => 20 * 1024 * 1024,
            // The data to send can't be larger than buffer_output_size.
            'buffer_output_size' => 10 * 1024 * 1024,
            // Max buffer size for socket connections
            'socket_buffer_size' => 128 * 1024 * 1024,
            // Worker will restart after processing this number of requests
            'max_request' => 3000,
            // Enable coroutine send
            'send_yield' => true,
            // You must add --enable-openssl while compiling Swoole
            'ssl_cert_file' => null,
            'ssl_key_file' => null,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Enable to turn on websocket server.
    |--------------------------------------------------------------------------
    */
    'websocket' => [
        'enabled' => env('SWOOLE_HTTP_WEBSOCKET', false),
    ],

    'watch' => [
        'file' => [
        ],
        'dir' => [
        ],
        'ext' => []
    ],

    /*
    |--------------------------------------------------------------------------
    | Console output will be transferred to response content if enabled.
    |--------------------------------------------------------------------------
    */
    'ob_output' => env('SWOOLE_OB_OUTPUT', true),

    /*
    |--------------------------------------------------------------------------
    | Pre-resolved instances here will be resolved when sandbox created.
    |--------------------------------------------------------------------------
    */
    'pre_resolved' => [
        'view', 'files', 'session', 'session.store', 'routes',
        'db', 'db.factory', 'cache', 'cache.store', 'config', 'cookie',
        'encrypter', 'hash', 'router', 'translator', 'url', 'log',
    ],

    /*
    |--------------------------------------------------------------------------
    | Instances here will be cleared on every request.
    |--------------------------------------------------------------------------
    */
    'instances' => [
        //
    ],

    /*
    |--------------------------------------------------------------------------
    | Providers here will be registered on every request.
    |--------------------------------------------------------------------------
    */
    'providers' => [
        Illuminate\Pagination\PaginationServiceProvider::class,
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetters for sandbox app.
    |--------------------------------------------------------------------------
    */
    'resetters' => [
        SwooleTW\Http\Server\Resetters\ResetConfig::class,
        SwooleTW\Http\Server\Resetters\ResetSession::class,
        SwooleTW\Http\Server\Resetters\ResetCookie::class,
        SwooleTW\Http\Server\Resetters\ClearInstances::class,
        SwooleTW\Http\Server\Resetters\BindRequest::class,
        SwooleTW\Http\Server\Resetters\RebindKernelContainer::class,
        SwooleTW\Http\Server\Resetters\RebindRouterContainer::class,
        SwooleTW\Http\Server\Resetters\RebindViewContainer::class,
        SwooleTW\Http\Server\Resetters\ResetProviders::class,
    ],

    /*
    |--------------------------------------------------------------------------
    | Define your swoole tables here.
    |
    | @see https://www.swoole.co.uk/docs/modules/swoole-table
    |--------------------------------------------------------------------------
    */
    'tables' => [
        // 'table_name' => [
        //     'size' => 1024,
        //     'columns' => [
        //         ['name' => 'column_name', 'type' => Table::TYPE_STRING, 'size' => 1024],
        //     ]
        // ],
    ]
];

2、Manager.php

位置:vendor/swooletw/laravel-swoole/src/Server

<?php

namespace SwooleTW\Http\Server;

use Throwable;
use Swoole\Http\Server;
use SwooleTW\Http\Server\Sandbox;
use SwooleTW\Http\Task\SwooleTaskJob;
use Illuminate\Support\Facades\Facade;
use SwooleTW\Http\Websocket\Websocket;
use SwooleTW\Http\Transformers\Request;
use SwooleTW\Http\Transformers\Response;
use SwooleTW\Http\Concerns\WithApplication;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Debug\ExceptionHandler;
use SwooleTW\Http\Concerns\InteractsWithWebsocket;
use SwooleTW\Http\Concerns\InteractsWithSwooleTable;

class Manager
{
    use InteractsWithWebsocket,
        InteractsWithSwooleTable,
        WithApplication;

    /**
     * Container.
     *
     * @var \Illuminate\Contracts\Container\Container
     */
    protected $container;

    /**
     * @var string
     */
    protected $framework;

    /**
     * @var string
     */
    protected $basePath;

    protected $fileExt;

    /**
     * Server events.
     *
     * @var array
     */
    protected $events = [
        'start', 'shutDown', 'workerStart', 'workerStop', 'packet',
        'bufferFull', 'bufferEmpty', 'task', 'finish', 'pipeMessage',
        'workerError', 'managerStart', 'managerStop', 'request',
    ];

    /**
     * HTTP server manager constructor.
     *
     * @param \Swoole\Http\Server $server
     * @param \Illuminate\Contracts\Container\Container $container
     * @param string $framework
     * @param string $basePath
     */
    public function __construct(Container $container, $framework, $basePath = null)
    {
        $this->container = $container;
        $this->setFramework($framework);
        $this->setBasepath($basePath);
        $this->initialize();
    }

    /**
     * Run swoole server.
     */
    public function run()
    {
        $this->container['swoole.server']->start();
    }

    /**
     * Stop swoole server.
     */
    public function stop()
    {
        $this->container['swoole.server']->shutdown();
    }

    /**
     * Initialize.
     */
    protected function initialize()
    {
        $this->createTables();
        $this->prepareWebsocket();
        $this->setSwooleServerListeners();
    }

    /**
     * Set swoole server listeners.
     */
    protected function setSwooleServerListeners()
    {
        foreach ($this->events as $event) {
            $listener = 'on' . ucfirst($event);

            if (method_exists($this, $listener)) {
                $this->container['swoole.server']->on($event, [$this, $listener]);
            } else {
                $this->container['swoole.server']->on($event, function () use ($event) {
                    $event = sprintf('swoole.%s', $event);

                    $this->container['events']->dispatch($event, func_get_args());
                });
            }
        }
    }

    /**
     * "onStart" listener.
     */
    public function onStart()
    {
        $this->setProcessName('master process');
        $this->createPidFile();

        $this->container['events']->dispatch('swoole.start', func_get_args());
    }

    /**
     * The listener of "managerStart" event.
     *
     * @return void
     */
    public function onManagerStart()
    {
        $this->setProcessName('manager process');
        $this->container['events']->dispatch('swoole.managerStart', func_get_args());
    }

    protected function watch($server){
        $notify = inotify_init();
        $watch = $this->container['config']->get('swoole_http.watch');
        $dir = $watch['dir'];
        $list = $watch['file'];
        $this->fileExt = $watch['ext'];
        $this->getDir($dir,$list);
        foreach ($list as $item) {
            inotify_add_watch($notify, $item, IN_CREATE | IN_DELETE | IN_MODIFY);
        }
        swoole_event_add($notify, function () use ($notify, $server) {
            $events = inotify_read($notify);
            if (!empty($events)) {
                echo "Hot update success!\n";
                $server->reload();
            }
        });
    }

    protected function getDir($dir,&$list){
        foreach ($dir as $dr) {
            foreach (array_diff(scandir($dr), array('.', '..')) as $item) {
                if(is_dir($dr . '/' . $item)){
                    $dirArr = [$dr . '/' . $item];
                    $this->getDir($dirArr,$list);
                }else{
                    $ext = pathinfo($dr . '/' . $item, PATHINFO_EXTENSION);
                    if(in_array($ext,$this->fileExt)){
                        $list[] = $dr . '/' . $item;
                    }
                }
            }
        }
      }

    /**
     * "onWorkerStart" listener.
     */
    public function onWorkerStart($server)
    {
        $this->clearCache();
        $this->setProcessName('worker process');

        $this->container['events']->dispatch('swoole.workerStart', func_get_args());

        // don't init laravel app in task workers
        if ($server->taskworker) {
            return;
        }
        echo date('Y-m-d');
        $this->watch($server);
        // clear events instance in case of repeated listeners in worker process
        Facade::clearResolvedInstance('events');

        // prepare laravel app
        $this->getApplication();

        // bind after setting laravel app
        $this->bindToLaravelApp();

        // prepare websocket handler and routes
        if ($this->isWebsocket) {
            $this->prepareWebsocketHandler();
            $this->loadWebsocketRoutes();
        }
    }

    /**
     * "onRequest" listener.
     *
     * @param \Swoole\Http\Request $swooleRequest
     * @param \Swoole\Http\Response $swooleResponse
     */
    public function onRequest($swooleRequest, $swooleResponse)
    {
        $this->app['events']->dispatch('swoole.request');

        $this->resetOnRequest();
        $handleStatic = $this->container['config']->get('swoole_http.handle_static_files', true);
        $publicPath = $this->container['config']->get('swoole_http.server.public_path', base_path('public'));

        try {
            // handle static file request first
            if ($handleStatic && Request::handleStatic($swooleRequest, $swooleResponse, $publicPath)) {
                return;
            }
            // transform swoole request to illuminate request
            $illuminateRequest = Request::make($swooleRequest)->toIlluminate();

            // set current request to sandbox
            $this->app['swoole.sandbox']->setRequest($illuminateRequest);
            // enable sandbox
            $this->app['swoole.sandbox']->enable();

            // handle request via laravel/lumen's dispatcher
            $illuminateResponse = $this->app['swoole.sandbox']->run($illuminateRequest);
            $response = Response::make($illuminateResponse, $swooleResponse);
            $response->send();
        } catch (Throwable $e) {
            try {
                $exceptionResponse = $this->app[ExceptionHandler::class]->render($illuminateRequest, $e);
                $response = Response::make($exceptionResponse, $swooleResponse);
                $response->send();
            } catch (Throwable $e) {
                $this->logServerError($e);
            }
        } finally {
            // disable and recycle sandbox resource
            $this->app['swoole.sandbox']->disable();
        }
    }

    /**
     * Reset on every request.
     */
    protected function resetOnRequest()
    {
        // Reset websocket data
        if ($this->isWebsocket) {
            $this->app['swoole.websocket']->reset(true);
        }
    }

    /**
     * Set onTask listener.
     */
    public function onTask($server, $taskId, $srcWorkerId, $data)
    {
        $this->container['events']->dispatch('swoole.task', func_get_args());

        try {
            // push websocket message
            if (is_array($data)) {
                if ($this->isWebsocket
                    && array_key_exists('action', $data)
                    && $data['action'] === Websocket::PUSH_ACTION) {
                    $this->pushMessage($server, $data['data'] ?? []);
                }
            // push async task to queue
            } elseif (is_string($data)) {
                $decoded = json_decode($data, true);

                if (JSON_ERROR_NONE === json_last_error() && isset($decoded['job'])) {
                    (new SwooleTaskJob($this->container, $server, $data, $taskId, $srcWorkerId))->fire();
                }
            }
        } catch (Throwable $e) {
            $this->logServerError($e);
        }
    }

    /**
     * Set onFinish listener.
     */
    public function onFinish($server, $taskId, $data)
    {
        // task worker callback
        return;
    }

    /**
     * Set onShutdown listener.
     */
    public function onShutdown()
    {
        $this->removePidFile();
    }

    /**
     * Set bindings to Laravel app.
     */
    protected function bindToLaravelApp()
    {
        $this->bindSandbox();
        $this->bindSwooleTable();

        if ($this->isWebsocket) {
            $this->bindRoom();
            $this->bindWebsocket();
        }
    }

    /**
     * Bind sandbox to Laravel app container.
     */
    protected function bindSandbox()
    {
        $this->app->singleton(Sandbox::class, function ($app) {
            return new Sandbox($app, $this->framework);
        });
        $this->app->alias(Sandbox::class, 'swoole.sandbox');
    }

    /**
     * Gets pid file path.
     *
     * @return string
     */
    protected function getPidFile()
    {
        return $this->container['config']->get('swoole_http.server.options.pid_file');
    }

    /**
     * Create pid file.
     */
    protected function createPidFile()
    {
        $pidFile = $this->getPidFile();
        $pid = $this->container['swoole.server']->master_pid;

        file_put_contents($pidFile, $pid);
    }

    /**
     * Remove pid file.
     */
    protected function removePidFile()
    {
        $pidFile = $this->getPidFile();

        if (file_exists($pidFile)) {
            unlink($pidFile);
        }
    }

    /**
     * Clear APC or OPCache.
     */
    protected function clearCache()
    {
        if (function_exists('apc_clear_cache')) {
            apc_clear_cache();
        }

        if (function_exists('opcache_reset')) {
            opcache_reset();
        }
    }

    /**
     * Set process name.
     *
     * @codeCoverageIgnore
     * @param $process
     */
    protected function setProcessName($process)
    {
        // MacOS doesn't support modifying process name.
        if ($this->isMacOS() || $this->isInTesting()) {
            return;
        }
        $serverName = 'swoole_http_server';
        $appName = $this->container['config']->get('app.name', 'Laravel');

        $name = sprintf('%s: %s for %s', $serverName, $process, $appName);

        swoole_set_process_name($name);
    }

    /**
     * Indicates if the process is running in macOS.
     *
     * @return bool
     */
    protected function isMacOS()
    {
        return PHP_OS === 'Darwin';
    }

    /**
     * Indicates if it's in phpunit environment.
     *
     * @return bool
     */
    protected function isInTesting()
    {
        return defined('IN_PHPUNIT') && IN_PHPUNIT;
    }

    /**
     * Log server error.
     *
     * @param Throwable
     */
    public function logServerError(Throwable $e)
    {
        $this->container[ExceptionHandler::class]->report($e);
    }
}

注意点:laravel5.8+swooleTW2.5版本下不支持fire方法,将所有的fire方法改为dispatch,上面文件已修改完。

3、Manager.php的关键点

1)  //监控方法,在onStartWork中调用,有变化的文件会执行reload方法重载更新

protected function watch($server){

$notify = inotify_init();

$watch = $this->container['config']->get('swoole_http.watch');

$dir = $watch['dir'];

$list = $watch['file'];

$this->fileExt = $watch['ext'];

$this->getDir($dir,$list);

      foreach ($list as $item) {

inotify_add_watch($notify, $item, IN_CREATE | IN_DELETE | IN_MODIFY);

}

swoole_event_add($notify, function () use ($notify, $server) {

$events = inotify_read($notify);

if (!empty($events)) {

echo "Hot update success!\n";

$server->reload();

}

});

}

2)//获取配置文件热更新目录下的所有文件路径

protected function getDir($dir,&$list){

foreach ($dir as $dr) {

foreach (array_diff(scandir($dr), array('.', '..')) as $item) {

if(is_dir($dr . '/' . $item)){

$dirArr = [$dr . '/' . $item];

$this->getDir($dirArr,$list);

}else{

$ext = pathinfo($dr . '/' . $item, PATHINFO_EXTENSION);

if(in_array($ext,$this->fileExt)){

$list[] = $dr . '/' . $item;

}

}

}

}

}


上一篇:ssh更改端口号

下一篇:公众号基本配置

讨厌 (0)
微博logo QQ空间logo QQlogo 豆瓣logo 人人logo 百度贴吧logo 有道云笔记logo

文章评论

表情表情
×
图片图片

评论列表