LaravelDcatAdminGateWayWorkerWorkermanSwooleOctane实现一键部署

AI摘要
本文是一篇技术知识分享,详细介绍了如何在Laravel项目中集成并部署GatewayWorker,以构建WebSocket长连接服务。核心内容包括:通过自定义Artisan命令统一管理GatewayWorker的Register、BusinessWorker、Gateway等核心进程,并整合Laravel的Horizon、Octane及任务调度等后台服务。文章提供了完整的配置示例与代码实现,并最终指导使用Supervisor进行进程守护,实现生产环境的稳定部署。

再开始前我默认你已经安装了Swoole扩展,其他扩展可以composer安装

composer require dcat/laravel-admin
composer require laravel/horizon
composer require laravel/octane
composer require workerman/gateway-worker

首先咱们要借助Laravel强大的Console模块,创建文件夹如下:
app/Console/Commands/Worker
文件夹里创建2个文件Events.phpWorkers.php
Workers.php

<?php

namespace App\Console\Commands\Worker;

use App\Utils\ConsoleUtil;
use GatewayWorker\BusinessWorker;
use GatewayWorker\Gateway;
use GatewayWorker\Register;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
use Workerman\Crontab\Crontab;
use Workerman\Events\Fiber;
use Workerman\Events\Swoole;
use Workerman\Timer;
use Workerman\Worker;

class Workers extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'workers {worker_command}  {--daemon} {--d}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'WorkerMan Server for Laravel. Start E.g "php artisan workers restart --daemon"';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }


    public function handle(): void
    {
        if (str_starts_with(PHP_OS, 'WIN')) {
            $this->output->caution('GatewayWorker Not Support On Windows.');
        }
        global $argv;// php artisan workers restart -d
        $command = $this->argument('worker_command');
        if (!in_array($command, ['start', 'stop', 'reload', 'restart', 'status', 'connections'])) {
            $this->output->caution("$command Is Invalid command");
        }
        $argv[1] = $command;
        if (!empty($this->option('daemon')) || !empty($this->option('d'))) {
            $argv[2] = '-d';
            if (count($argv) == 5) array_pop($argv);
        } else {
            // 配置 Laravel Workers 只有非守护进程(supervisor守护服务时使用)下才启动laravel相关进程
            $this->laravelWorker();
        }
        array_pop($argv);

        $this->start();
    }

    public function laravelWorker(): void
    {
        $laravel_workers = [
            1 => 'LaravelSchedule',
            2 => 'LaravelHttpServe',// 本地启动serve 正式启动 octane:start
            0 => 'LaravelHorizon',
        ];
        $laravel_worker = new Worker();
        $laravel_worker->count = count($laravel_workers);
        $laravel_worker->name = "LaravelWorker";
        //$laravel_worker->eventLoop = Swoole::class; // 使用Swoole协程
        $laravel_worker->onWorkerStart = function () use (&$laravel_worker, $laravel_workers) {
            $laravel_worker->name = $laravel_workers[$laravel_worker->id];
            ConsoleUtil::instance()->output("onWorkerStart:" . $laravel_worker->id . " " . $laravel_worker->name);
            match ($laravel_worker->id) {
                1 => $this->call('schedule:work'),
                2 => $this->call(App::environment('local') ? 'serve' : 'octane:start'),// serve octane:start
                default => $this->call('horizon'),
            };
        };
        $laravel_worker->onWorkerStop = function ($worker) {
            ConsoleUtil::instance()->output("onWorkerStop:" . $worker->id . " " . $worker->name);
            match ($worker->id) {
                1 => null,
                2 => $this->call('octane:stop'),
                default => $this->call('horizon:terminate'),
            };
        };
        $laravel_worker->onWorkerReload = function ($worker) {
            ConsoleUtil::instance()->output("onWorkerReload:" . $worker->id . " " . $worker->name);
            match ($worker->id) {
                1 => null,
                2 => $this->call('octane:stop'),
                default => $this->call('horizon:terminate'),
            };
        };
        $laravel_worker->onError = function ($worker, $code, $msg) {
            ConsoleUtil::instance()->output("onError:" . $worker->id . " " . $worker->name . " " . $code . " " . $msg);
            match ($worker->id) {
                1 => null,
                2 => $this->call('octane:stop'),
                default => $this->call('horizon:terminate'),
            };
        };
    }

    /**
     * 启动
     * @return void
     */
    public function start(): void
    {
        $option = Config::get('gateway');
        $register_address = !empty($option['register_address']) ? $option['register_address'] : '127.0.0.1:1236';

        if (!empty($option['register_deploy'])) {
            // 分布式部署的时候其它服务器可以关闭register服务
            // 注意需要设置不同的lanIp
            $this->register($register_address);
        }

        // 启动businessWorker
        if (!empty($option['businessWorker_deploy'])) {
            $this->businessWorker($register_address, $option['businessWorker'] ?? []);
        }

        // 启动gateway
        if (!empty($option['gateway_deploy'])) {
            $this->gateway($register_address, $option);
        }

        //启动RobotWorker
        $this->robotWorker();

        Worker::$pidFile = storage_path('app') . '/gateway.pid';
        Worker::$logFile = storage_path('logs') . '/worker_man.log';
        Worker::runAll();
    }

    /**
     * 启动register
     * @access public
     * @param string $registerAddress
     * @return void
     */
    public function register(string $registerAddress): void
    {
        // 初始化register
        $worker = new Register('text://' . $registerAddress);
        //$worker->eventLoop = Fiber::class; // 使用自带的Fiber协程
        $worker->eventLoop = Swoole::class; // 使用Swoole协程
    }

    /**
     * 启动businessWorker
     * @access public
     * @param string $registerAddress registerAddress
     * @param array $option 参数
     * @return void
     */
    public function businessWorker(string $registerAddress, array $option = []): void
    {
        // 初始化 businessWorker 进程
        $worker = new BusinessWorker();
        $this->setOption($worker, $option);
        $worker->registerAddress = $registerAddress;
    }

    /**
     * 启动gateway
     * @param string $register_address registerAddress
     * @param array $option 参数
     * @return void
     */
    public function gateway(string $register_address, array $option = []): void
    {
        // 初始化 gateway 进程
        if (!empty($option['socket'])) {
            $socket = $option['socket'];
            unset($option['socket']);
        } else {
            $protocol = !empty($option['protocol']) ? $option['protocol'] : 'websocket';
            $socket = $protocol . '://' . $option['host'] . ':' . $option['port'];
            unset($option['host'], $option['port'], $option['protocol']);
        }

        $gateway = new Gateway($socket, $option['context'] ?? []);

        // 以下设置参数都可以在配置文件中重新定义覆盖
        $gateway->name = 'Gateway';
        $gateway->count = 4;
        $gateway->lanIp = '127.0.0.1';
        $gateway->startPort = 2000;
        $gateway->pingInterval = 30;
        $gateway->pingNotResponseLimit = 0;
        $gateway->pingData = '{"type":"ping"}';
        $gateway->registerAddress = $register_address;

        // 全局静态属性设置
        foreach ($option as $name => $val) {
            if (in_array($name, ['stdoutFile', 'daemonize', 'pidFile', 'logFile'])) {
                Worker::${$name} = $val;
                unset($option[$name]);
            }
        }

        $this->setOption($gateway, $option);
    }

    public function robotWorker(): void
    {
        $worker = new Worker();
        $worker->name = 'RobotWorker';
        $worker->onWorkerStart = function () use ($worker) {
            ConsoleUtil::instance()->output("onWorkerStart:{$worker->id} {$worker->name}");
            Timer::add(60, function () use ($worker) {
                //ConsoleUtil::instance()->output("{$worker->name} 60s 执行一次。");
            });
        };
    }

    /**
     * 设置参数
     * @access protected
     * @param Worker $worker Worker对象
     * @param array $option 参数
     * @return void
     */
    protected function setOption(Worker $worker, array $option = []): void
    {
        // 设置参数
        if (!empty($option)) {
            foreach ($option as $key => $val) {
                $worker->$key = $val;
            }
        }
    }
}

Events.php

<?php

namespace App\Console\Commands\Worker;

use App\Models\User;
use App\Utils\ConsoleUtil;
use Exception;
use GatewayWorker\BusinessWorker;
use GatewayWorker\Lib\Gateway;

/**
 * 推送主逻辑
 * 主要是处理 onMessage onClose
 */
class Events
{
    /**
     *
     * 当businessWorker进程启动时触发。每个进程生命周期内都只会触发一次。
     * @param BusinessWorker $businessWorker
     */
    public static function onWorkerStart(BusinessWorker $businessWorker)
    {

    }

    /**
     * 当有客户端连接时,将client_id返回,让mvc框架判断当前uid并执行绑定
     * @param string $client_id
     */
    public static function onConnect(string $client_id): void
    {
        //$_SERVER:{"REMOTE_ADDR":"127.0.0.1","REMOTE_PORT":38508,"GATEWAY_ADDR":"127.0.0.1","GATEWAY_PORT":8282,"GATEWAY_CLIENT_ID":"7f00000108fc00000002"}
    }

    /**
     * @param string $client_id client_id固定为20个字符的字符串,用来全局标记一个socket连接,每个客户端连接都会被分配一个全局唯一的client_id。
     * @param array $data websocket握手时的http头数据,包含get、server等变量 $data['get']['token'] $data['server']['SERVER_NAME'] $data['cookie']['uid']
     */
    public static function onWebSocketConnect(string $client_id, array $data): void
    {
        $client_ip = get_client_ip($data['server']);
        $_SERVER['REMOTE_ADDR'] = $client_ip;//修正客户ip
        //ConsoleUtil::instance()->output("ip:" . json_encode(get_client_ips($data['server'])) . " " . json_encode($data) . " " . json_encode($_SERVER));
        Gateway::sendToClient($client_id, json_encode([
            'm' => 'Lobby',
            'f' => 'init',
            'd' => [
                'type' => 'init',
                'client_id' => $client_id,
                'client_ip' => $client_ip,
            ]
        ]));
    }

    /**
     * 服务端有消息时
     * @param string $client_id
     * @param mixed $message
     * @throws Exception
     */
    public static function onMessage(string $client_id, mixed $message): void
    {
        //ConsoleUtil::instance()->output($message);
        // 客户端传递的是json数据
        $message = json_decode($message, true);
        if (!$message) {
            return;
        }
        // 检测用户状态
        $user_id = $_SESSION['user_id'] ?? 0;
        if ($message['f'] != 'bind') {
            if (!$user_id) return;
            if (User::query()->find($user_id)->status < 1) return;
        }
        // 根据类型执行不同的业务
        switch ($message['f']) {
            // 客户端回应服务端的心跳
            case 'pong':
                break;
            case 'ping':
                self::sendStatus($client_id);
                break;
            case 'bind':
                self::auth($client_id, $message);
                break;
            case 'simple':
                Gateway::sendToUid($message['d']['to_user_id'], json_encode($message['d'], JSON_UNESCAPED_UNICODE));
                break;
            case 'room':
                Gateway::sendToGroup($message['d']['room_id'], json_encode($message['d'], JSON_UNESCAPED_UNICODE));
                //Gateway::sendToAll(json_encode($message, JSON_UNESCAPED_UNICODE));
                break;
            case 'read_message':
                break;
        }
    }

    /**
     * 回复 Ping
     * @param string $client_id
     * @return bool
     */
    protected static function sendStatus(string $client_id): bool
    {
        $uid = $_SESSION['user_id'] ?? 0;
        $multi_port = false;
        if ($uid) {
            $arr = Gateway::getClientIdByUid($uid);
            if (count($arr) > 1) {
                $multi_port = true;
            }
        }
        return Gateway::sendToClient($client_id, json_encode([
            'm' => 'Lobby',
            'f' => 'pong',
            'd' => ['type' => 'pong', 'multi_port' => $multi_port,]
        ]));
    }

    /**
     * 验证用户的真实性并绑定
     * @param string $client_id
     * @param array $message
     * @return bool
     */
    protected static function auth(string $client_id, array $message): bool
    {
        try {
            $d = $message['d'] ?? [];
            $user_id = (int)($d['user_id'] ?? 0);
            $room_id = (int)(empty($d['room_id']) ? 1 : $d['room_id']);
            if ($user_id <= 0) {
                return Gateway::closeClient($client_id);
            }
            $user = User::query()->find($user_id);
            if (is_null($user)) return Gateway::closeClient($client_id);
            $user->ws_online = 1;
            $user->save();
            $_SESSION['user_id'] = $user_id;
            Gateway::bindUid($client_id, $user_id);
            if ($room_id > 0) {
                Gateway::joinGroup($client_id, $room_id);
            }
            //测试消息
            // 跑马灯 就是content内容 推全量用户 不存数据库
//            $message = "Player8152064 successfully recharge ₹137.00(跑马灯你只需拿这个content显示即可,和接口不一样)";
//            (new MessageService())->setF('notify_lamp')->sendToGroup(1, $message);
//            // 30分钟奖励弹窗 发给个人的 存库
//            (new MessageService())->from(1)->setF('active_reward')
//                ->setMessage([
//                    'msg_type' => "active_reward",
//                    'ext' => [
//                        ['amount' => 1000, 'id' => 123, 'key' => 'lucky_30_minutes', 'status' => 0, 'user_id' => 30],
//                        ['amount' => 1000, 'id' => 123, 'key' => 'lucky_monday_bonus', 'status' => 0, 'user_id' => 30]
//                    ],
//                ])
//                ->sendToUid($user_id, "这是活动弹窗消息类型,拿ext数据,是一个列表关键字段和/api/activity_user_logs保持一致");
//
//            //余额变动提醒
//            (new MessageService())->from(1)->setF('user_balance')
//                ->setMessage([
//                    'msg_type' => 'bill_1',
//                    'ext' => ['balance' => 1300]
//                ])->sendToUid($user_id, "这是余额变动提醒消息的结构类型,拿ext里的balance");
            return self::sendStatus($client_id);
        } catch (Exception $exception) {
            return Gateway::closeClient($client_id);
        }
    }

    /**
     * 断开连接
     * @param $client_id
     * @return bool
     */
    protected static function closeClient($client_id): bool
    {
        $_SESSION['user_id'] = null;
        return Gateway::closeClient($client_id);
    }

    /**
     * 当断开连接时
     * @param string $client_id
     * @throws Exception
     */
    public static function onClose(string $client_id): void
    {
        $user_id = $_SESSION['user_id'] ?? false;
        if ($user_id) {
            Gateway::sendToAll(json_encode([
                'm' => 'Lobby',
                'f' => 'isOnline',
                'd' => [
                    'type' => 'isOnline',
                    'user_id' => $user_id,
                    'client_id' => $client_id,
                    'time' => time(),
                    'is_online' => 0,
                ]
            ]));
            $user = User::query()->find($user_id);
            $user->ws_online = 0;
            $user->save();
        }
    }

    /**
     * 当businessWorker进程退出时触发。每个进程生命周期内都只会触发一次。
     * 可以在这里为每一个businessWorker进程做一些清理工作,例如保存一些重要数据等。
     * @param BusinessWorker $businessWorker
     */
    public static function onWorkerStop(BusinessWorker $businessWorker)
    {
    }

}

config/gateway.php

<?php
return [
    'php_bin' => env('PHP_BIN', 'php'),
    // 一些自定义配置
    'register_deploy' => env('REGISTER_DEPLOY', true), // 是否需要部署register
    'businessWorker_deploy' => true, // 是否需要部署businessWorker
    'gateway_deploy' => true, // 是否需要部署gateway

    // Register配置
    'register_address' => env('REGISTER_ADDRESS', '127.0.0.1:1236'),

    // BusinessWorker配置
    'businessWorker' => [
        'name' => 'BusinessWorker',
        'count' => env('BUSINESS_WORKER_COUNT', 1),
        'eventHandler' => \App\Console\Commands\Worker\Events::class,
    ],

    // Gateway配置
    'protocol' => 'Websocket', // 协议 支持 tcp udp unix http websocket text
    'host' => '0.0.0.0', // 监听地址
    'port' => env('GATEWAY_PORT', 8282), // 监听端口
    'socket' => '', // 完整监听地址
    'name' => env('GATEWAY_WORKER_NAME', 'gatewayWorker'),
    'count' => env('GATEWAY_WORKER_COUNT', 1),
    'lanIp' => env('GATEWAY_WORKER_LAN_IP', '127.0.0.1'),
    'startPort' => env('GATEWAY_WORKER_START_PORT', 2300),
    'daemonize' => false,
    'pingInterval' => 30,//心跳间隔
    'pingNotResponseLimit' => 1,
    'pingData' => '{"m": "Lobby", "f": "ping", "d": null}',//{"type":"ping"}

];

config/horizon.php

<?php

use Illuminate\Support\Str;

return [

    /*
    |--------------------------------------------------------------------------
    | Horizon Domain
    |--------------------------------------------------------------------------
    |
    | This is the subdomain where Horizon will be accessible from. If this
    | setting is null, Horizon will reside under the same domain as the
    | application. Otherwise, this value will serve as the subdomain.
    |
    */

    'domain' => env('HORIZON_DOMAIN'),

    /*
    |--------------------------------------------------------------------------
    | Horizon Path
    |--------------------------------------------------------------------------
    |
    | This is the URI path where Horizon will be accessible from. Feel free
    | to change this path to anything you like. Note that the URI will not
    | affect the paths of its internal API that aren't exposed to users.
    |
    */

    'path' => env('HORIZON_PATH', 'horizon'),

    /*
    |--------------------------------------------------------------------------
    | Horizon Redis Connection
    |--------------------------------------------------------------------------
    |
    | This is the name of the Redis connection where Horizon will store the
    | meta information required for it to function. It includes the list
    | of supervisors, failed jobs, job metrics, and other information.
    |
    */

    'use' => 'queue',

    /*
    |--------------------------------------------------------------------------
    | Horizon Redis Prefix
    |--------------------------------------------------------------------------
    |
    | This prefix will be used when storing all Horizon data in Redis. You
    | may modify the prefix when you are running multiple installations
    | of Horizon on the same server so that they don't have problems.
    |
    */

    'prefix' => env(
        'HORIZON_PREFIX',
        Str::slug(env('APP_NAME', 'laravel'), '_') . '_horizon:'
    ),

    /*
    |--------------------------------------------------------------------------
    | Horizon Route Middleware
    |--------------------------------------------------------------------------
    |
    | These middleware will get attached onto each Horizon route, giving you
    | the chance to add your own middleware to this list or change any of
    | the existing middleware. Or, you can simply stick with this list.
    |
    */

    'middleware' => ['web'],

    /*
    |--------------------------------------------------------------------------
    | 队列等待时间阈值
    |--------------------------------------------------------------------------
    |
    | 此选项允许您配置何时触发LongWaitDetected事件。在触发此事件之前,每个连接/队列组合都可能有自己唯一的阈值(秒)。
    |
    */

    'waits' => [
        'redis:default' => 60,
    ],

    /*
    |--------------------------------------------------------------------------
    | Job Trimming Times
    |--------------------------------------------------------------------------
    |
    | 在这里,您可以配置希望Horizon保存最近和失败的作业的时间长度(以分钟为单位)。通常,最近
    | 的作业会保留一个小时,而所有失败的作业会存储一整周。
    |
    */

    'trim' => [
        'recent' => 10080,
        'pending' => 10080,
        'completed' => 10080,
        'recent_failed' => 10080,
        'failed' => 10080,
        'monitored' => 10080,
    ],

    /*
    |--------------------------------------------------------------------------
    | Silenced Jobs
    |--------------------------------------------------------------------------
    |
    | Silencing a job will instruct Horizon to not place the job in the list
    | of completed jobs within the Horizon dashboard. This setting may be
    | used to fully remove any noisy jobs from the completed jobs list.
    |
    */

    'silenced' => [
        // App\Jobs\ExampleJob::class,
    ],

    'silenced_tags' => [
        // 'notifications',
    ],

    /*
    |--------------------------------------------------------------------------
    | Metrics
    |--------------------------------------------------------------------------
    |
    | Here you can configure how many snapshots should be kept to display in
    | the metrics graph. This will get used in combination with Horizon's
    | `horizon:snapshot` schedule to define how long to retain metrics.
    |
    */

    'metrics' => [
        'trim_snapshots' => [
            'job' => 24,
            'queue' => 24,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Fast Termination
    |--------------------------------------------------------------------------
    |
    | When this option is enabled, Horizon's "terminate" command will not
    | wait on all of the workers to terminate unless the --wait option
    | is provided. Fast termination can shorten deployment delay by
    | allowing a new instance of Horizon to start while the last
    | instance will continue to terminate each of its workers.
    |
    */

    'fast_termination' => false,

    /*
    |--------------------------------------------------------------------------
    | Memory Limit (MB)
    |--------------------------------------------------------------------------
    |
    | This value describes the maximum amount of memory the Horizon master
    | supervisor may consume before it is terminated and restarted. For
    | configuring these limits on your workers, see the next section.
    |
    */

    'memory_limit' => 64,

    /*
    |--------------------------------------------------------------------------
    | Queue Worker Configuration
    |--------------------------------------------------------------------------
    |
    | 在这里,您可以定义应用程序在所有环境中使用的队列辅助器设置。
    | 这些管理器和设置将处理所有排队的作业,并将在部署期间由Horizon提供。
    |
    */

    'defaults' => [
        /**
         * connection:工作连接的名称
         * balance=:管理器应该应用的平衡策略
         * delay=0:延迟失败作业的时间
         * force:强制工人在维护模式下运行
         * max-processes=1:启动的最大工人数
         * min-processes=1:每个队列分配的最小工人数
         * memory=128:内存限制,以兆字节为单位
         * nice=0:进程优先级
         * paused:以暂停状态启动管理器
         * queue=:工作队列的名称
         * sleep=3:没有工作时休眠的秒数
         * timeout=60:子进程可以运行的秒数
         * tries=0:在记录任务失败之前尝试执行该任务的次数
         */
        'supervisor' => [
            'connection' => 'redis',
            'queue' => ['default', 'event', 'send_telegram_message'],
            'balance' => 'auto',//simple, auto, 和 false
            'autoScalingStrategy' => 'time',//time size
            'minProcesses' => 1,
            'maxProcesses' => 1,
            'maxTime' => 0,
            'maxJobs' => 0,
            'memory' => 1024,
            'tries' => 3,
            'timeout' => 28800,
            'nice' => 0,
            'balanceMaxShift' => 1,
            'balanceCooldown' => 3,
        ],
        'multiples' => [//可以并发的队列
            'connection' => 'redis',
            'queue' => ['event',],
            'balance' => 'auto',//simple, auto, 和 false
            'maxProcesses' => 1,
            'memory' => 1024,
            'tries' => 3,
            'timeout' => 28800,
        ],
    ],

    'environments' => [
        'production' => [
            'supervisor' => [
                'maxProcesses' => 1,
            ],
            'multiples' => [//可以并发的队列
                'maxProcesses' => 16,
            ],
        ],

        'local' => [
            'supervisor' => [
                'maxProcesses' => 1,
            ],
            'multiples' => [//可以并发的队列
                'maxProcesses' => 1,
            ],
        ],

        'prod' => [
            'supervisor' => [
                'maxProcesses' => 1,
            ],
            'multiples' => [//可以并发的队列
                'maxProcesses' => 16,
            ],
        ],

        'test' => [
            'supervisor' => [
                'nice' => 0,
            ],
            'multiples' => [//可以并发的队列
                'maxProcesses' => 16,
            ],
        ],

        '*' => [
            'supervisor' => [
                'maxProcesses' => 1,
            ],
            'multiples' => [//可以并发的队列
                'maxProcesses' => 1,
            ],
        ],
    ],
];

config/octane.php

<?php

use Laravel\Octane\Contracts\OperationTerminated;
use Laravel\Octane\Events\RequestHandled;
use Laravel\Octane\Events\RequestReceived;
use Laravel\Octane\Events\RequestTerminated;
use Laravel\Octane\Events\TaskReceived;
use Laravel\Octane\Events\TaskTerminated;
use Laravel\Octane\Events\TickReceived;
use Laravel\Octane\Events\TickTerminated;
use Laravel\Octane\Events\WorkerErrorOccurred;
use Laravel\Octane\Events\WorkerStarting;
use Laravel\Octane\Events\WorkerStopping;
use Laravel\Octane\Listeners\CloseMonologHandlers;
use Laravel\Octane\Listeners\CollectGarbage;
use Laravel\Octane\Listeners\DisconnectFromDatabases;
use Laravel\Octane\Listeners\EnsureUploadedFilesAreValid;
use Laravel\Octane\Listeners\EnsureUploadedFilesCanBeMoved;
use Laravel\Octane\Listeners\FlushOnce;
use Laravel\Octane\Listeners\FlushTemporaryContainerInstances;
use Laravel\Octane\Listeners\FlushUploadedFiles;
use Laravel\Octane\Listeners\ReportException;
use Laravel\Octane\Listeners\StopWorkerIfNecessary;
use Laravel\Octane\Octane;

return [

    /*
    |--------------------------------------------------------------------------
    | Octane Server
    |--------------------------------------------------------------------------
    |
    | This value determines the default "server" that will be used by Octane
    | when starting, restarting, or stopping your server via the CLI. You
    | are free to change this to the supported server of your choosing.
    |
    | Supported: "roadrunner", "swoole", "frankenphp"
    |
    */

    'server' => env('OCTANE_SERVER', 'swoole'),

    'swoole' => [
        'options' => [
            'log_file' => storage_path('logs/swoole_http.log'),
            'package_max_length' => 10 * 1024 * 1024,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Force HTTPS
    |--------------------------------------------------------------------------
    |
    | When this configuration value is set to "true", Octane will inform the
    | framework that all absolute links must be generated using the HTTPS
    | protocol. Otherwise your links may be generated using plain HTTP.
    |
    */

    'https' => env('OCTANE_HTTPS', false),

    /*
    |--------------------------------------------------------------------------
    | Octane Listeners
    |--------------------------------------------------------------------------
    |
    | All of the event listeners for Octane's events are defined below. These
    | listeners are responsible for resetting your application's state for
    | the next request. You may even add your own listeners to the list.
    |
    */

    'listeners' => [
        WorkerStarting::class => [
            EnsureUploadedFilesAreValid::class,
            EnsureUploadedFilesCanBeMoved::class,
        ],

        RequestReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
            ...Octane::prepareApplicationForNextRequest(),
            // 开启对 Dcat Admin 的支持
            Dcat\Admin\Octane\Listeners\FlushAdminState::class,
        ],

        RequestHandled::class => [
            //
        ],

        RequestTerminated::class => [
            // FlushUploadedFiles::class,
        ],

        TaskReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
            //
        ],

        TaskTerminated::class => [
            //
        ],

        TickReceived::class => [
            ...Octane::prepareApplicationForNextOperation(),
            //
        ],

        TickTerminated::class => [
            //
        ],

        OperationTerminated::class => [
            FlushOnce::class,
            FlushTemporaryContainerInstances::class,
            // DisconnectFromDatabases::class,
            // CollectGarbage::class,
        ],

        WorkerErrorOccurred::class => [
            ReportException::class,
            StopWorkerIfNecessary::class,
        ],

        WorkerStopping::class => [
            CloseMonologHandlers::class,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Warm / Flush Bindings
    |--------------------------------------------------------------------------
    |
    | The bindings listed below will either be pre-warmed when a worker boots
    | or they will be flushed before every new request. Flushing a binding
    | will force the container to resolve that binding again when asked.
    |
    */

    'warm' => [
        ...Octane::defaultServicesToWarm(),
    ],

    'flush' => [
        //
    ],

    /*
    |--------------------------------------------------------------------------
    | Octane Swoole Tables
    |--------------------------------------------------------------------------
    |
    | While using Swoole, you may define additional tables as required by the
    | application. These tables can be used to store data that needs to be
    | quickly accessed by other workers on the particular Swoole server.
    |
    */

    'tables' => [
        'example:1000' => [
            'name' => 'string:1000',
            'votes' => 'int',
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Octane Swoole Cache Table
    |--------------------------------------------------------------------------
    |
    | While using Swoole, you may leverage the Octane cache, which is powered
    | by a Swoole table. You may set the maximum number of rows as well as
    | the number of bytes per row using the configuration options below.
    |
    */

    'cache' => [
        'rows' => 1000,
        'bytes' => 10000,
    ],

    /*
    |--------------------------------------------------------------------------
    | File Watching
    |--------------------------------------------------------------------------
    |
    | The following list of files and directories will be watched when using
    | the --watch option offered by Octane. If any of the directories and
    | files are changed, Octane will automatically reload your workers.
    |
    */

    'watch' => [
        'app',
        'bootstrap',
        'config/**/*.php',
        'database/**/*.php',
        'public/**/*.php',
        'resources/**/*.php',
        'routes',
        'composer.lock',
        '.env',
    ],

    /*
    |--------------------------------------------------------------------------
    | Garbage Collection Threshold
    |--------------------------------------------------------------------------
    |
    | When executing long-lived PHP scripts such as Octane, memory can build
    | up before being cleared by PHP. You can force Octane to run garbage
    | collection if your application consumes this amount of megabytes.
    |
    */

    'garbage' => 50,

    /*
    |--------------------------------------------------------------------------
    | Maximum Execution Time
    |--------------------------------------------------------------------------
    |
    | The following setting configures the maximum execution time for requests
    | being handled by Octane. You may set this value to 0 to indicate that
    | there isn't a specific time limit on Octane request execution time.
    |
    */

    'max_execution_time' => 30,

];

启动效果如下

Laravel-DcatAdmin-GateWayWorker-Workerman-Swoole-Octane组合实现一键部署,只需一条命令即可启动全部

最后,线上部署的时候用Supervisor守护一下即可。

[program:e-workers]
command=/www/server/php/82/bin/php artisan workers restart
directory=/www/wwwroot/laravel12/
autorestart=true
startsecs=3
startretries=3
stdout_logfile=/www/server/panel/plugin/supervisor/log/e-workers.out.log
stderr_logfile=/www/server/panel/plugin/supervisor/log/e-workers.err.log
stdout_logfile_maxbytes=2MB
stderr_logfile_maxbytes=2MB
user=www
priority=999
numprocs=1
process_name=%(program_name)s_%(process_num)02d
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
0
粉丝
0
喜欢
0
收藏
0
排名:3828
访问:0
私信
所有博文
社区赞助商