1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/hhxsv5-laravel-s

Клонировать/Скачать
README-CN.md 86 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 02.03.2025 20:01 c8b2666
Логотип LaravelS

Китайская версия документации | Английская документация

🚀 LaravelS — это готовое к использованию адаптивное решение для работы с Laravel/Lumen и Swoole.

Последняя версия Версия PHP Версия Swoole Общее количество загрузок Статус сборки Статус аналитики кода Лицензия


Обновление

  • Подпишитесь на этот репозиторий, чтобы получать последние обновления.
  • Группа QQ для общения:
    • 698480528 Нажмите, чтобы присоединиться
    • 62075835 Нажмите, чтобы присоединиться

Содержание документации

Основные характеристики

Бенчмарки

Требования

Зависимость Описание
PHP >=8.2 рекомендуется 8.2
Swoole >=5.0 рекомендуется 5.1.1
Laravel/Lumen >=10 рекомендуется 10

Установка

  1. Установите через Composer (packagist). Возможно, вы не сможете найти версию 3.0, решение проблемы находится здесь #81.
# PHP >=8.2
composer require "hhxsv5/laravel-s:~3.8.0"

# PHP >=5.5.9,<=7.4.33
# composer require "hhxsv5/laravel-s:~3.7.0"

# Убедитесь, что ваш composer.lock файл находится под версионным контролем
  1. Зарегистрируйте Service Provider (выберите один из двух шагов ниже):

    • Laravel: Измените файл config/app.php, Laravel 5.5+ поддерживает автоматическое обнаружение пакетов, поэтому вам следует пропустить этот шаг.

      'providers' => [
          //...
          Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class,
      ],
    • Lumen: Измените файл bootstrap/app.php.

      $app->register(Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class);
  2. Выпустите конфигурацию и двоичные файлы.

    После каждого обновления LaravelS требуется повторное выполнение команды publish; нажмите Release для просмотра изменений между версиями.

    php artisan laravels publish
    # Конфигурационный файл: config/laravels.php
    # Двоичные файлы: bin/laravels bin/fswatch bin/inotify
  3. Настройте конфигурацию config/laravels.php: IP адреса, порты и прочее, см. конфигурационные параметры.

  4. Оптимизация производительности

    • Настройка системных параметров - Количество рабочих процессов: LaravelS использует режим синхронного I/O Swoole, увеличение значения worker_num повышает производительность при большом количестве запросов, но также увеличивает использование оперативной памяти и затраты на переключение процессов. Для обеспечения 1000 QPS при времени обработки одного запроса 100 мс, потребуется минимум 100 рабочих процессов, расчет выполняется следующей формулой: worker_num = 1000QPS / (1с / 1мс) = 100, поэтому рекомендуется проводить тестирование нагрузки для определения оптимального значения worker_num.

    • Количество рабочих процессов для задач

Запуск

Перед запуском внимательно ознакомьтесь с: внимание(это очень важно).

  • Команды управления:
Команда Описание
start Запуск LaravelS, вывод списка запущенных процессов "*ps -ef
stop Остановка LaravelS и вызов метода onStop для всех пользовательских процессов
restart Перезапуск LaravelS: сначала гладкая остановка Stop, затем запуск Start; сервис недоступен до завершения команды Start
reload Гладкий перезапуск всех процессов Task/Worker/Timer (включая бизнес-логику), вызов метода onReload для пользовательских процессов, не перезапускает главный процесс; после изменения файла конфигурации config/laravels.php требуется использовать команду restart для перезапуска
info Отображение информации о версиях компонентов
help Отображение справочной информации
  • Параметры запуска, применимы для команд start и restart.
Параметр Описание
-d --daemonize
-e --env
-i --ignore
-x --x-version
  • Файлы во время работы: они создаются автоматически при запуске команды start, обычно разработчики не должны обращаться к этим файлам, рекомендуется добавить их в .gitignore.
Файл Описание
storage/laravels.conf Файл конфигурации LaravelS во время работы
storage/laravels.pid PID файл главного процесса
storage/laravels-timer-process.pid PID файл процесса таймера
storage/laravels-custom-processes.pid PID файлы всех пользовательских процессов

Развертывание

Рекомендуется использовать Supervisord для управления главным процессом, условием является отсутствие параметра -d и установка swoole.daemonize как false.

[program:laravel-s-test]
directory=/var/www/laravel-s-test
command=/usr/local/bin/php bin/laravels start -i
numprocs=1
autostart=true
autorestart=true
startretries=3
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log

Интеграция с Nginx (рекомендовано)

Пример.```nginx gzip on; gzip_min_length 1024; gzip_comp_level 2; gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml; gzip_vary on; gzip_disable "msie6"; upstream swoole { # Подключение через IP:Port server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s; # Подключение через UnixSocket Stream, небольшой совет: поместите файл сокета в директорию /dev/shm для улучшенной производительности #server unix:/yourpath/laravel-s-test/storage/laravels.sock weight=5 max_fails=3 fail_timeout=30s; #server 192.168.1.1:5200 weight=3 max_fails=3 fail_timeout=30s; #server 192.168.1.2:5200 backup; keepalive 16; } server { listen 80; # Не забудьте привязать Host server_name laravels.com; root /yourpath/laravel-s-test/public; access_log /yourpath/log/nginx/$server_name.access.log main; autoindex off; index index.html index.htm; # Обработка статических ресурсов Nginx (рекомендовано включить gzip), LaravelS обрабатывает динамические ресурсы. location / { try_files $uri @laravels; } # Когда запрос направлен на PHP-файл, сразу вернуть 404, чтобы скрыть public/*.php #location ~* .php$ { # return 404; #} location @laravels { # proxy_connect_timeout 60s; # proxy_send_timeout 60s; # proxy_read_timeout 60s; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-PORT $remote_port; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header Scheme $scheme; proxy_set_header Server-Protocol $server_protocol; proxy_set_header Server-Name $server_name; proxy_set_header Server-Addr $server_addr; proxy_set_header Server-Port $server_port; # "swoole" указывает upstream proxy_pass http://swoole; } }



## Совместимость с Apache

```apache
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule remoteip_module modules/mod_remoteip.so
LoadModule deflate_module modules/mod_deflate.so

<IfModule deflate_module>
    SetOutputFilter DEFLATE
    LevelDeflate 2
    AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml
</IfModule>

<VirtualHost *:80>
    # Не забудьте привязать Host
    ServerName www.laravels.com
    ServerAdmin hhxsv5@sina.com

    DocumentRoot /yourpath/laravel-s-test/public;
    Indexes index.html index.htm
    <Directory "/">
        AllowOverride None
        Require all granted
    </Directory>

    RemoteIPHeader X-Forwarded-For

    ProxyRequests Off
    ProxyPreserveHost On
    <Proxy balancer://laravels>  
        BalancerMember http://192.168.1.1:5200 loadfactor=7
        #BalancerMember http://192.168.1.2:5200 loadfactor=3
        #BalancerMember http://192.168.1.3:5200 loadfactor=1 status=+H
        ProxySet method=byrequests
    </Proxy>
    #ProxyPass / balancer://laravels/
    #ProxyPassReverse / balancer://laravels/
</VirtualHost>
```    # Apache обрабатывает статические ресурсы, LaravelS обрабатывает динамические ресурсы.
    Перезапись Машина Включен
    Перезапись Условие %{ДОКУМЕНТНЫЙ_КОРЕНЬ}%{ЗАПРОСНЫЙ_ФАЙЛ} !-d
    Перезапись Условие %{ДОКУМЕНТНЫЙ_КОРЕНЬ}%{ЗАПРОСНЫЙ_ФАЙЛ} !-f
    Перезапись Правило ^/(.*)$ балансатор://laravels%{ЗАПРОСНЫЙ_URI} [P,L]

    Ошибочный Журнал ${APACHE_LOG_DIR}/www.laravels.com.error.log
    Кастомный Журнал ${APACHE_LOG_DIR}/www.laravels.com.access.log объединённый
</VirtualHost>

Активация WebSocket-сервера

WebSocket-сервер прослушивает тот же IP и порт, что и HTTP-сервер.

  1. Создайте класс обработчика WebSocket и реализуйте интерфейс WebSocketHandlerInterface. При запуске будет автоматически создан экземпляр, создание вручную не требуется.
namespace App\Services;
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;

/**
 * @see https://wiki.swoole.com/#/start/start_ws_server
 */
class WebSocketService implements WebSocketHandlerInterface
{
    // Объявление конструктора без аргументов
    public function __construct()
    {
    }

    public function onOpen(Server $server, Request $request)
    {
        // До того как событие onOpen было активировано, HTTP-запрос WebSocket был уже обработан Laravel маршрутом,
        // поэтому информация Laravel Request, Auth и прочее доступна, Session также доступен для чтения и записи, но только внутри события onOpen.
        // \Log::info('Новое соединение WebSocket', [$request->fd, request()->all(), session()->getId(), session('xxx'), session(['yyy' => time()])]);
        // Исключения, выброшенные здесь, будут пойманы верхним уровнем и записаны в журнал Swoole, разработчики должны использовать try/catch самостоятельно
        $server->push($request->fd, 'Добро пожаловать в LaravelS');
    }

    public function onMessage(Server $server, Frame $frame)
    {
        // \Log::info('Получено сообщение', [$frame->fd, $frame->data, $frame->opcode, $frame->finish]);
        // Исключения, выброшенные здесь, будут пойманы верхним уровнем и записаны в журнал Swoole, разработчики должны использовать try/catch самостоятельно
        $server->push($frame->fd, date('Y-m-d H:i:s'));
    }

    public function onClose(Server $server, $fd, $reactorId)
    {
        // Исключения, выброшенные здесь, будут пойманы верхним уровнем и записаны в журнал Swoole, разработчики должны использовать try/catch самостоятельно
    }
}
  1. Измените конфигурацию config/laravels.php.
// ...
'websocket' => [
    'enable' => true, // Обратите внимание: установите enable в true
    'handler' => \App\Services\WebSocketService::class,
],
'swoole' => [
    //...
    // dispatch_mode может быть установлено только в  Yöntem 2, 4 veya 5, https://wiki.swoole.com/#/server/setting?id=dispatch_mode
    'dispatch_mode' => 2,
    //...
],
// ...
  1. Используйте SwooleTable, чтобы связать FD с UserId, это не обязательно, пример использования SwooleTable. Также можно использовать другие глобальные службы хранения данных, такие как Redis/Memcached/MySQL, но следует учитывать возможность конфликта FD при нескольких Swoole Server экземплярах.

  2. Совместимость с Nginx (рекомендуется)

См. Прокси WebSocket

карта $http_upgrade $connection_upgrade {
    по умолчанию upgrade;
    ''      close;
}
upstream swoole {
    # Подключение через IP:Port
    server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;
    # Подключение через UnixSocket Stream, небольшой совет: поместите файл сокета в директорию /dev/shm для улучшенной производительности
    #server unix:/yourpath/laravel-s-test/storage/laravels.sock weight=5 max_fails=3 fail_timeout=30s;
    #server 192.168.1.1:5200 weight=3 max_fails=3 fail_timeout=30s;
    #server 192.168.1.2:5200 backup;
    keepalive 16;
}
server {
    слушает 80;
    # Не забудьте привязать Host
    сервер_имя laravels.com;
    корень /yourpath/laravel-s-test/public;
    access_log /yourpath/log/nginx/$сервер_имя.access.log  основной;
    автоиндекс отключен;
    индекс index.html index.htm;
    # Обработка статических ресурсов Nginx (рекомендовано включить gzip), LaravelS обрабатывает динамические ресурсы.
    локация / {
        try_files $uri @laravels;
    }
    # Когда запрос направлен на PHP-файл, сразу вернуть 404, чтобы скрыть public/*.php
    #location ~* \.php$ {
    #    return 404;
    #}
    # Общее существование HTTP и WebSocket, Nginx использует location для разделения
    # !!! WebSocket-соединение имеет путь /ws
    # JavaScript: var ws = new WebSocket("ws://laravels.com/ws");
    локация =/ws {
        # proxy_connect_timeout 60s;
        # proxy_send_timeout 60s;
        # proxy_read_timeout: если за 60 секунд сервер-прокси не отправил данные Nginx, то Nginx закроет текущий соединение; также влияние на закрытие соединения оказывают настройки heartbeat Swoole
        # proxy_read_timeout 60s;
        proxy_http_version 1.1;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Real-PORT $remote_port;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header Server-Protocol $server_protocol;
        proxy_set_header Server-Name $server_name;
        proxy_set_header Server-Addr $server_addr;
        proxy_set_header Server-Port $server_port;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_pass http://swoole;
    }
    локация @laravels {
        # proxy_connect_timeout 60s;
        # proxy_send_timeout 60s;
        # proxy_read_timeout 60s;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Real-PORT $remote_port;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header Server-Protocol $server_protocol;
        proxy_set_header Server-Name $server_name;
        proxy_set_header Server-Addr $server_addr;
        proxy_set_header Server-Port $server_port;
        proxy_pass http://swoole;
    }
}
  1. Настройка heartbeat
  • Настройки heartbeat Swoole
// config/laravels.php
'swoole' => [
    //...
    // Отображает каждые 60 секунд проверку соединений, если соединение не отправляет данные серверу в течение 600 секунд, то это соединение будет принудительно закрыто
    'heartbeat_idle_time'      => 600,
    'heartbeat_check_interval' => 60,
    //...
],
  • Конфигурация Nginx для чтения времени ожидания ответа от прокси-сервера
# Если за 60 секунд прокси-сервер не отправил данные Nginx, то текущее соединение будет закрыто
proxy_read_timeout 60s;
  1. Отправка данных в контроллере
namespace App\Http\Controllers;
class TestController extends Controller
{
    public function push()
    {
        $fd = 1; // Найти fd по userId из карты [userId=>fd].
        /** @var \Swoole\WebSocket\Server $swoole */
        $swoole = app('swoole');
        $success = $swoole->push($fd, 'Отправка данных на fd#1 в контроллере');
        var_dump($success);
    }
}

Обработка событий

Системные события

Обычно вы можете использовать эти события для сброса или удаления некоторых глобальных или статических переменных, а также для изменения текущего запроса и ответа.- laravels.received_request После того как Swoole\Http\Request преобразован в Illuminate\Http\Request, перед тем как Laravel ядро начнёт обработку запроса.

```php
// Измените `app/Providers/EventServiceProvider.php`, добавьте следующий слушатель код в метод boot
// Если переменная $events не существует, вы также можете использовать Facade вызова \Event::listen().
$events->listen('laravels.received_request', function (\Illuminate\Http\Request $req, $app) {
    $req->query->set('get_key', 'hhxsv5'); // Изменение querystring
    $req->request->set('post_key', 'hhxsv5'); // Изменение post body
});
```
  • laravels.generated_response После того как Laravel ядро завершило обработку запроса, но до того как Illuminate\Http\Response преобразуется в Swoole\Http\Response (следующий шаг — отправка ответа клиенту).

    // Измените `app/Providers/EventServiceProvider.php`, добавьте следующий слушатель код в метод boot
    // Если переменная $events не существует, вы также можете использовать Facade вызова \Event::listen().
    $events->listen('laravels.generated_response', function (\Illuminate\Http\Request $req, \Symfony\Component\HttpFoundation\Response $rsp, $app) {
        $rsp->headers->set('header-key', 'hhxsv5'); // Изменение header
    });

Пользовательские асинхронные события

Эта возможность зависит от AsyncTask в Swoole. Необходимо установить config/laravels.php для swoole.task_worker_num. Производительность обработки асинхронных событий зависит от количества процессов Task, поэтому следует правильно настроить task_worker_num.

1.Создание класса события.

use Hhxsv5\LaravelS\Swoole\Task\Event;
class TestEvent extends Event
{
    protected $listeners = [
        // Список слушателей
        TestListener1::class,
        // TestListener2::class,
    ];
    private $data;
    public function __construct($data)
    {
        $this->data = $data;
    }
    public function getData()
    {
        return $this->data;
    }
}

2.Создание класса слушателя.

use Hhxsv5\LaravelS\Swoole\Task\Event;
use Hhxsv5\LaravelS\Swoole\Task\Listener;
use Hhxsv5\LaravelS\Swoole\Task\Task;
class TestListener1 extends Listener
{
    public function handle(Event $event)
    {
        \Log::info(__CLASS__ . ':handle start', [$event->getData()]);
        sleep(2); // Моделирование медленной обработки событий
        // Внутри слушателя можно также запустить задачу, но не поддерживаются завершающие callback для Task.
        // Убедитесь, что конфигурация task_ipc_mode в config/laravels.php установлена на 1 или 2, см. https://wiki.swoole.com/#/server/setting?id=task_ipc_mode
        $ret = Task::deliver(new TestTask('task data'));
        var_dump($ret);
        // Исключения, выбрасываемые здесь, будут пойманы верхним уровнем и записаны в лог Swoole, разработчики должны использовать try/catch
        // return false; // Остановить распространение события дальше по списку слушателей
    }
}

3.Вызов события.

// Создайте экземпляр TestEvent и вызовите его через fire, этот вызов является асинхронным, после вызова он сразу возвращает управление, дальнейшая обработка слушателей происходит в процессах Task
use Hhxsv5\LaravelS\Swoole\Task\Event;
$event = new TestEvent('event data');
// $event->delay(10); // Вызвать через 10 секунд
// $event->setTries(3); // При возникновении ошибки повторять 3 раза
$success = Event::fire($event);
var_dump($success); // Проверка успешности вызова

Асинхронная очередь задач

Эта возможность зависит от AsyncTask в Swoole. Необходимо установить config/laravels.php для swoole.task_worker_num. Производительность обработки асинхронных задач зависит от количества процессов Task, поэтому следует правильно настроить task_worker_num.

1.Создание класса задачи.

use Hhxsv5\LaravelS\Swoole\Task\Task;
class TestTask extends Task
{
    private $data;
    private $result;
    public function __construct($data)
    {
        $this->data = $data;
    }
    // Логика обработки задачи, выполняется в процессах Task, не может запускать новые задачи
    public function handle()
    {
        \Log::info(__CLASS__ . ':handle start', [$this->data]);
        sleep(2); // Моделирование медленной обработки задачи
        // Исключения, выбрасываемые здесь, будут пойманы верхним уровнем и записаны в лог Swoole, разработчики должны использовать try/catch
        $this->result = 'результат ' . $this->data;
    }
    // Дополнительный метод завершения, выполняется после выполнения задачи, работает в процессах Worker, может запускать новые задачи
    public function finish()
    {
        \Log::info(__CLASS__ . ':finish start', [$this->result]);
        Task::deliver(new TestTask2('task2')); // Запуск другой задачи
    }
}

2.Запуск задачи.

// Создайте экземпляр TestTask и запустите его через deliver, этот вызов является асинхронным, после запуска он сразу возвращает управление, дальнейшая обработка задачи происходит в процессах Task
use Hhxsv5\LaravelS\Swoole\Task\Task;
$task = new TestTask('task data');
// $task->delay(3); // Запустить задачу через 3 секунды
// $task->setTries(3); // При возникновении ошибки повторять 3 раза
$ret = Task::deliver($task);
var_dump($ret); // Проверка успешности запуска

Миллисекундные периодические задачи

На основе миллисекундных таймеров Swoole, упакованные периодические задачи, заменяют Linux Crontab.

1.Создание класса периодической задачи.

namespace App\Jobs\Timer;
use App\Tasks\TestTask;
use Swoole\Coroutine;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Hhxsv5\LaravelS\Swoole\Timer\CronJob;
class TestCronJob extends CronJob
{
    protected $i = 0;
    // !!! Конфигурация `interval` и `isImmediate` для периодической задачи имеет два способа (выбрать один): либо переопределить соответствующие методы, либо указать параметры при регистрации задачи.
    // --- Переопределение соответствующих методов для возврата конфигурации: начало
    public function interval()
    {
        return 1000; // Каждую секунду
    }
    public function isImmediate()
    {
        return false; // Немедленное выполнение первой задачи, false - ждать интервал времени перед первым выполнением
    }
    // --- Переопределение соответствующих методов для возврата конфигурации: окончание
    public function run()
    {
        \Log::info(__METHOD__, ['начало', $this->i, microtime(true)]);
        // Выполнение действий
        // sleep(1); // Для Swoole < 2.1
        Coroutine::sleep(1); // Для Swoole>=2.1 метод run() автоматически создает корутину.
        $this->i++;
        \Log::info(__METHOD__, ['конец', $this->i, microtime(true)]);
    }
}        if ($this->i >= 10) { // Выполнить 10 раз и затем прекратить выполнение
            \Log::info(__METHOD__, ['остановка', $this->i, microtime(true)]);
            $this->stop(); // Остановить эту периодическую задачу, но она снова будет выполнена после перезагрузки/перезапуска
            // В CronJob можно также запустить задачи, но завершение callback для Task не поддерживается.
            // Убедитесь, что конфигурация task_ipc_mode в config/laravels.php установлена на 1 или 2, см. https://wiki.swoole.com/#/server/setting?id=task_ipc_mode
            $ret = Task::deliver(new TestTask('task data'));
            var_dump($ret);
        }
        // Исключения, выбрасываемые здесь, будут пойманы верхним уровнем и записаны в лог Swoole, разработчики должны использовать try/catch
    }
}

2.Регистрация класса периодической задачи.

// В "config/laravels.php" зарегистрировать класс периодической задачи
[
    // ...
    'timer'          => [
        'enable' => true, // Включить Timer
        'jobs'   => [ // Список зарегистрированных классов периодических задач
            // Включить LaravelScheduleJob для выполнения `php artisan schedule:run`, каждую минуту, заменяет Linux Crontab
            // \Hhxsv5\LaravelS\Illuminate\LaravelScheduleJob::class,
            // Два способа указания параметров:
            // [\App\Jobs\Timer\TestCronJob::class, [1000, true]], // Указание параметров при регистрации
            \App\Jobs\Timer\TestCronJob::class, // Переопределение соответствующих методов для возврата параметров
        ],
        'max_wait_time' => 5, // Максимальное время ожидания при перезагрузке
        // Открыть глобальный таймер: когда используется многоместное размещение, убедитесь, что только один экземпляр выполняет периодические задачи, эта функция зависит от Redis, подробнее см. https://laravel.com/docs/7.x/redis
        'global_lock'     => false,
        'global_lock_key' => config('app.name', 'Laravel'),
    ],
    // ...
];

3.При построении кластера серверов, важно убедиться, что только один экземпляр запущен для выполнения периодических задач, чтобы избежать повторного выполнения одной и той же задачи.

4.С версии LaravelS v3.4.0 поддерживается горячая перезагрузка [Reload] процесса таймера. Когда LaravelS получает сигнал SIGUSR1, он ждет max_wait_time (по умолчанию 5 секунд) перед завершением процесса, затем Manager процесс перезапускает процесс таймера.

5.Если вам требуется только использование минутных периодических задач, рекомендуется включить Hhxsv5\LaravelS\Illuminate\LaravelScheduleJob для замены Linux Crontab, таким образом вы сможете продолжать использовать планировщик задач Laravel с привычными методами конфигурации Kernel.

// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
    // Метод runInBackground() создает новый подпроцесс для выполнения задачи, это асинхронно, не влияет на выполнение других задач
    $schedule->command(TestCommand::class)->runInBackground()->everyMinute();
}

Автоматическая перезагрузка после изменения кода

  • Базируется на inotify, поддерживает только Linux.

    1.Установите расширение inotify.

    2.Активируйте настройку.### Внимание: inotify работает только с изменениями файлов в операционной системе Linux, поэтому рекомендуется использовать последнюю версию Docker и решение Vagrant (вот пример).

  • Основано на fswatch, поддерживает OS X, Linux и Windows.

    1. Установите fswatch.

    2. Выполните команду в корневой директории проекта.

    # Отслеживание текущей директории
    ./bin/fswatch
    # Отслеживание директории app
    ./bin/fswatch ./app
  • Основано на inotifywait, поддерживает только Linux.

    1. Установите inotify-tools.

    2. Выполните команду в корневой директории проекта.

    # Отслеживание текущей директории
    ./bin/inotify
    # Отслеживание директории app
    ./bin/inotify ./app
  • В случае, если вышеописанные методы не работают, можно использовать конечное решение: настройте max_request=1, worker_num=1. Это приведёт к тому, что процесс Worker будет перезапущен после каждого запроса, что значительно снижает производительность. Поэтому это решение следует применять только в среде разработки.

Пример использования SwooleServer в вашем проекте

/**
 * Если включен сервер WebSocket, то $swoole является экземпляром `Swoole\WebSocket\Server`,
 * в противном случае — экземпляром `Swoole\Http\Server`.
 * @var \Swoole\WebSocket\Server|\Swoole\Http\Server $swoole
 */
$swoole = app('swoole');
var_dump($swoole->stats());
$swoole->push($fd, 'Отправка сообщения через WebSocket');

Использование SwooleTable

  1. Определение таблицы, поддерживаются несколько таблиц.

Перед запуском Swoole все определенные таблицы создаются.

// Конфигурация в "config/laravels.php"
[
    // ...
    'swoole_tables'  => [
        // Сценарий: Соединение UserId с FD в WebSocket
        'ws' => [// Ключ — имя таблицы, используется автоматически при обращении, чтобы избежать коллизий. Здесь определяется таблица с именем wsTable
            'size'   => 102400,// Максимальное количество записей в таблице
            'column' => [// Описание столбцов таблицы
                ['name' => 'value', 'type' => \Swoole\Table::TYPE_INT, 'size' => 8],
            ],
        ],
        // ... Добавление других таблиц
    ],
    // ...
];
  1. Обращение к таблице: Все таблицы доступны через SwooleServer, используйте app('swoole')->wsTable.
namespace App\Services;
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;

class WebSocketService implements WebSocketHandlerInterface
{
    /**@var \Swoole\Table $wsTable */
    private $wsTable;

    public function __construct()
    {
        $this->wsTable = app('swoole')->wsTable;
    }

    // Сценарий: Соединение UserId с FD в WebSocket
    public function onOpen(Server $server, Request $request)
    {
        // var_dump(app('swoole') === $server); // Одна и та же таблица
        /**
         * Получение текущего пользователя
         * Этот механизм требует, чтобы путь к WebSocket использовал аутентификацию или аналогичные middleware.
         * Например:
         * Браузер: var ws = new WebSocket("ws://127.0.0.1:5200/ws");
         * Логика в Laravel: Route::get('/ws', function () {
         *     // Любое содержимое со статусом 200
         *     return 'websocket';
         * })->middleware(['auth']);
         */
        // $user = Auth::user();
        // $userId = $user ? $user->id : 0; // 0 — незарегистрированный пользователь
        $userId = mt_rand(1000, 10000);```markdown
## Поддержка нескольких портов и протоколов

> Для получения более подробной информации обратитесь к [документации Swoole](https://wiki.swoole.com/#/server/methods?id=addlistener) и [документации по работе с несколькими портами](https://wiki.swoole.com/#/server/port).

Чтобы наш основной сервер поддерживал больше протоколов, кроме HTTP и WebSocket, мы можем воспользоваться возможностью работы Swoole с несколькими портами и протоколами, которая называется Socket в LaravelS. Теперь вы можете легко создавать приложения на основе TCP/UDP на Laravel.

1. Создайте обработчики для Socket, расширяющие `Hhxsv5\LaravelS\Swoole\Socket\{TcpSocket|UdpSocket|Http|WebSocket}`.

    ```php
    namespace App\Sockets;
    use Hhxsv5\LaravelS\Swoole\Socket\TcpSocket;
    use Swoole\Server;

    class TestTcpSocket extends TcpSocket
    {
        public function onConnect(Server $server, $fd, $reactorId)
        {
            \Log::info('Новое TCP соединение', [$fd]);
            $server->send($fd, 'Добро пожаловать в LaravelS.');
        }

        public function onReceive(Server $server, $fd, $reactorId, $data)
        {
            \Log::info('Получены данные', [$fd, $data]);
            $server->send($fd, 'LaravelS: ' . $data);
            if ($data === "quit\r\n") {
                $server->send($fd, 'LaravelS: до свидания' . PHP_EOL);
                $server->close($fd);
            }
        }

        public function onClose(Server $server, $fd, $reactorId)
        {
            \Log::info('Закрыто TCP соединение', [$fd]);
            $server->send($fd, 'Прощай');
        }
    }
    ```

    Эти соединения используют те же процессы Worker, что и основной сервер HTTP/WebSocket, поэтому вы можете использовать такие возможности как асинхронная отправка задач, SwooleTable и компоненты Laravel, такие как DB и Eloquent. Также, если вам требуется объект Port для данного протокольного порта, вы можете получить его следующим образом:

    ```php
    public function onReceive(Server $server, $fd, $reactorId, $data)
    {
        $port = $this->swoolePort; // Получение объекта `Swoole\Server\Port`
    }
    ```

    ```php
    namespace App\Http\Controllers;
    
    class TestController extends Controller
    {
        public function test()
        {
            /**@var \Swoole\Http\Server|\Swoole\WebSocket\Server $swoole */
            $swoole = app('swoole');
            
            // $swoole->ports: проходит по всем объектам Port, https://wiki.swoole.com/#/server/properties?id=ports
            $port = $swoole->ports[1]; // Получение объекта `Swoole\Server\Port`, где $port[0] — порт основного сервера
            
            foreach ($port->connections as $fd) { // Проходит по всем соединениям
                // $swoole->send($fd, 'Отправка сообщения TCP');
                // if($swoole->isEstablished($fd)) {
                //     $swoole->push($fd, 'Отправка сообщения WebSocket');
                // }
            }
        }
    }
    ```

2. Регистрация сокета.

    ```php
    // Измените файл config/laravels.php
    // ...
    'sockets' => [
        [
            'host'     => '127.0.0.1',
            'port'     => 5291,
            'type'     => SWOOLE_SOCK_TCP,// Поддерживаемый тип сокета: https://wiki.swoole.com/#/consts?id=socket-%e7%b1%bb%e5%9e%8c
            'settings' => [// Настройки Swoole: https://wiki.swoole.com/#/server/port?id=%e5%8f%af%e9%80%89%e5%8f%82%e6%95%b0
                'open_eof_check' => true,
                'package_eof'    => "\r\n",
            ],
            'handler'  => \App\Sockets\TestTcpSocket::class,
            'enable'   => true, // Активировать, значение по умолчанию true
        ],
    ],
    ```

    Для настроек heartbeat можно указывать только на основном сервере, они будут наследованы сокетами.

    Для протокола TCP, когда `dispatch_mode` установлен в `1/3`, события `onConnect` и `onClose` могут быть скрыты, поскольку эти режимы не гарантируют порядок событий `onConnect`, `onClose` и `onReceive`. Если вам нужны эти события, установите `dispatch_mode` в `2/4/5`, [см. здесь](https://wiki.swoole.com/#/server/setting?id=dispatch_mode).

    ```php
    'swoole' => [
        //...
        'dispatch_mode' => 2,
        //...
    ];
    ```

3. Тестирование.

- TCP: `telnet 127.0.0.1 5291`

- UDP: Linux `echo "Hello LaravelS" > /dev/udp/127.0.0.1/5292`

4. Пример регистрации других протоколов.

    - UDP
    ```php
    'sockets' => [
        [
            'host'     => '0.0.0.0',
            'port'     => 5292,
            'type'     => SWOOLE_SOCK_UDP,
            'settings' => [
                'open_eof_check' => true,
                'package_eof'    => "\r\n",
            ],
            'handler'  => \App\Sockets\TestUdpSocket::class,
        ],
    ],
    ```

    - HTTP
    ```php
    'sockets' => [
        [
            'host'     => '0.0.0.0',
            'port'     => 5293,
            'type'     => SWOOLE_SOCK_TCP,
            'settings' => [
                'open_http_protocol' => true,
            ],
            'handler'  => \App\Sockets\TestHttp::class,
        ],
    ],
    ```

    - WebSocket: Основной сервер должен быть настроен для работы с WebSocket, то есть `websocket.enable` должен быть установлен в `true`.
    ```php
    'sockets' => [
        [
            'host'     => '0.0.0.0',
            'port'     => 5294,
            'type'     => SWOOLE_SOCK_TCP,
            'settings' => [
                'open_http_protocol'      => true,
                'open_websocket_protocol' => true,
            ],
            'handler'  => \App\Sockets\TestWebSocket::class,
        ],
    ],
    ```
    
## Корутины> [Оригинальная документация Swoole](https://wiki.swoole.com/#/start/coroutine)

- **Предупреждение:** В контексте корутин порядок выполнения кода может быть случайным. Данные уровня запроса должны быть изолированы с помощью ID корутины. Однако в Laravel/Lumen существует множество одиночных объектов и статических свойств, что приводит к взаимному влиянию данных между различными запросами — это **небезопасно**. Например, соединение с базой данных является одиночным объектом, и все запросы используют одно и то же соединение PDO. Это работает при синхронной модели блокировки, но при асинхронной модели корутин каждый запрос должен использовать отдельное соединение и поддерживать различные состояния I/O. Для этого требуется использование пула соединений.

- **Не используйте** корутины, за исключением **персонализированных процессов**.

## Персонализированные процессы
```> Поддерживает создание специальных рабочих процессов для мониторинга, отправки отчетов или других специфических задач. См. [addProcess](https://wiki.swoole.com/#/server/methods?id=addprocess).


1. Создайте класс Process, реализующий интерфейс CustomProcessInterface.

    ```php
    namespace App\Processes;
    use App\Tasks\TestTask;
    use Hhxsv5\LaravelS\Swoole\Process\CustomProcessInterface;
    use Hhxsv5\LaravelS\Swoole\Task\Task;
    use Swoole\Coroutine;
    use Swoole\Http\Server;
    use Swoole\Process;
    class TestProcess implements CustomProcessInterface
    {
        /**
         * @var bool Отметка выхода, используется для обновления Reload
         */
        private static $quit = false;

        public static function callback(Server $swoole, Process $process)
        {
            // Код, выполняющийся в процессе, не должен завершаться, поскольку Manager процесс автоматически воссоздаст этот процесс после его завершения.
            while (!self::$quit) {
                \Log::info('Test process: running');
                Coroutine::sleep(1); // Swoole>=2.1 Автоматически создаёт корутину для метода callback(), активирует режим корутин Runtime. Обратите внимание на совместимость используемых компонентов с корутинами.
                $ret = Task::deliver(new TestTask('task data'));
                var_dump($ret);
                // Выброшенные исключения будут пойманы верхним уровнем, записаны в лог Swoole, затем процесс будет завершён. Через 3 секунды Manager процесс снова создаст процесс. Поэтому разработчику следует самостоятельно поймать исключение try/catch, чтобы избежать частого создания процесса.
                // throw new \Exception('an exception');
            }
        }

        public static function onReload(Server $swoole, Process $process)
        {
            \Log::info('Test process: reloading');
            self::$quit = true;
            // $process->exit(0); // Принудительно завершает процесс
        }

        public static function onStop(Server $swoole, Process $process)
        {
            \Log::info('Test process: stopping');
            self::$quit = true;
            // $process->exit(0); // Принудительно завершает процесс
        }
    }
    ```

2. Регистрация TestProcess.

    ```php
    // Измените файл config/laravels.php
    // ...
    'processes' => [
        'test' => [ // Ключ — имя процесса
            'class'    => \App\Processes\TestProcess::class,
            'redirect' => false, // Перенаправление входных/выходных данных
            'pipe'     => 0,     // Тип трубки: 0 — не создавать, 1 — создать SOCK_STREAM тип, 2 — создать SOCK_DGRAM тип
            'enable'   => true,  // Активировать, по умолчанию true
            //'num'    => 3,  // Создать несколько экземпляров процесса, по умолчанию 1
            //'queue'    => [ // Активировать очередь сообщений как канал связи между процессами, конфигурация пустого массива указывает на использование значений по умолчанию
            //    'msg_key'  => 0,    // Ключ очереди сообщений, по умолчанию ftok(__FILE__, 1)
            //    'mode'     => 2,    // Режим связи, по умолчанию 2, указывает на режим конкурентного доступа
            //    'capacity' => 8192, // Размер одного сообщения, ограничен системными параметрами ОС, по умолчанию 8192, максимальное значение не должно превышать 65536
            //],
            //'restart_interval' => 5, // Интервал времени в секундах перед повторным запуском процесса после его аварийного завершения, по умолчанию 5 секунд
        ],
    ],
    ```

3. **Важно:** Метод callback() не должен завершаться, если он завершится, Manager процесс автоматически воссоздаст этот процесс.

4. Пример: записи данных в персонализированный процесс.

    ```php
    // config/laravels.php
    'processes' => [
        'test' => [
            'class'    => \App\Processes\TestProcess::class,
            'redirect' => false,
            'pipe'     => 1,
        ],
    ],
    ```

    ```php
    // app/Processes/TestProcess.php
    public static function callback(Server $swoole, Process $process)
    {
        while ($data = $process->read()) {
            \Log::info('TestProcess: read data', [$data]);
            $process->write('TestProcess: ' . $data);
        }
    }
    ```

    ```php
    // app/Http/Controllers/TestController.php
    public function testProcessWrite()
    {
        /**@var \Swoole\Process[] $process */
        $customProcesses = \Hhxsv5\LaravelS\LaravelS::getCustomProcesses();
        $process = $customProcesses['test'];
        $process->write('TestController: write data' . time());
        var_dump($process->read());
    }
    ```

## Часто используемые компоненты

### Apollo
> При запуске `LaravelS` конфигурация `Apollo` загружается в `.env` файл, а также запускается персонализированный процесс `apollo`, который слушает изменения конфигурации и автоматически перезагружает при их изменении.

1. Активация компонента Apollo: добавьте параметры запуска `--enable-apollo` и параметры конфигурации Apollo.

    ```bash
    php bin/laravels start --enable-apollo --apollo-server=http://127.0.0.1:8080 --apollo-app-id=LARAVEL-S-TEST
    ```

2. Конфигурация горячего обновления (необязательна).

    ```php
    // Измените файл config/laravels.php
    'processes' => Hhxsv5\LaravelS\Components\Apollo\Process::getDefinition(),
    ```

    ```php
    // Когда есть другие конфигурации персонализированных процессов
    'processes' => [
        'test' => [
            'class'    => \App\Processes\TestProcess::class,
            'redirect' => false,
            'pipe'     => 1,
        ],
        // ...
    ] + Hhxsv5\LaravelS\Components\Apollo\Process::getDefinition(),
    ```

3. Список доступных параметров.

| Параметр | Описание | Значение по умолчанию | Пример |
| -------- | -------- | -------- | -------- |
| apollo-server | URL сервера Apollo | - | --apollo-server=http://127.0.0.1:8080 |
| apollo-app-id | ID приложения Apollo | - | --apollo-app-id=LARAVEL-S-TEST |
| apollo-namespaces | Назначенные пространства имён для приложения, можно указывать несколько | application | --apollo-namespaces=application --apollo-namespaces=env |
| apollo-cluster | Назначенный кластер для приложения | default | --apollo-cluster=default |
| apollo-client-ip | IP текущего экземпляра, также используется для серых выпусков | IP внутренней сети | --apollo-client-ip=10.2.1.83 |
| apollo-pull-timeout | Время ожидания получения конфигурации (в секундах) | 5 | --apollo-pull-timeout=5 |
| apollo-backup-old-env | Бэкап старого файла конфигурации .env при обновлении | false | --apollo-backup-old-env |

### Prometheus
> Поддерживает мониторинг и тревожные сигналы Prometheus, визуализацию метрик через Grafana. Установите окружение Prometheus и Grafana с помощью Docker Compose. См. [Docker Compose](https://github.com/hhxsv5/docker).

1. Требуется расширение APCu >= 5.0.0, установите его командой `pecl install apcu`.

2. Копируйте файл конфигурации prometheus.php в директорию config вашего проекта. В зависимости от потребностей модифицируйте конфигурацию.
    ```bash
    # Выполните команду в корневой директории проекта
    cp vendor/hhxsv5/laravel-s/config/prometheus.php config/
    ```
    Если вы используете Lumen, вам также потребуется вручную загрузить конфигурацию в bootstrap/app.php `$app->configure('prometheus');`.3. Настройте глобальный middleware: `Hhxsv5\LaravelS\Components\Prometheus\RequestMiddleware::class`. Для максимально точного учета времени выполнения запросов, RequestMiddleware должен быть первым глобальным middleware, расположенным перед другими middleware.

4. Зарегистрируйте ServiceProvider: `Hhxsv5\LaravelS\Components\Prometheus\ServiceProvider::class`.

5. Настройте процесс CollectorProcess в config/laravels.php для периодического сбора метрик Swoole Worker/Task/Timer процессов.
    ```php
    'processes' => Hhxsv5\LaravelS\Components\Prometheus\CollectorProcess::getDefinition(),
    ```

6. Создайте маршрут для вывода данных мониторинга.
    ```php
    use Hhxsv5\LaravelS\Components\Prometheus\Exporter;

    Route::get('/actuator/prometheus', function () {
        $result = app(Exporter::class)->render();
        return response($result, 200, ['Content-Type' => Exporter::REDNER_MIME_TYPE]);
    });
    ```

7. Настройте Prometheus и запустите его.
    ```yml
    global:
      scrape_interval: bk 5s
      scrape_timeout: 5s
      evaluation_interval: 30s
    scrape_configs:
    - job_name: laravel-s-test
      honor_timestamps: true
      metrics_path: /actuator/prometheus
      scheme: http
      follow_redirects: true
      static_configs:
      - targets:
        - 127.0.0.1:5200 # IP и порт мониторируемого сервиса
    # Динамическое обнаружение с использованием одной из поддерживаемых механизмов discovery
    # https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config
    # - job_name: laravels-eureka
    #   honor_timestamps: true
    #   scrape_interval: 5s
    #   metrics_path: /actuator/prometheus
    #   scheme: http
    #   follow_redirects: true
      # eureka_sd_configs:
      # - server: http://127.0.0.1:8080/eureka
      #   follow_redirects: true
      #   refresh_interval: 5s
    ```

8. Запустите Grafana, затем импортируйте [panel json](https://github.com/hhxsv5/laravel-s/tree/PHP-8.x/grafana-dashboard.json).<img src="https://raw.githubusercontent.com/hhxsv5/laravel-s/PHP-8.x/grafana-dashboard.png" height="800px" alt="Панель Grafana">

## Другие возможности

### Настройка событий Swoole

Поддерживаемый список событий:

| Событие | Необходимый интерфейс | Время возникновения |
| -------- | ---------------------- | -------------------- |
| ServerStart | Hhxsv5\LaravelS\Swoole\Events\ServerStartInterface | Возникает при запуске процесса Master, `в этом событии не следует выполнять сложную бизнес-логику, можно только выполнить простую инициализацию` |
| ServerStop | Hhxsv5\LaravelS\Swoole\Events\ServerStopInterface | Возникает при нормальном завершении сервера, `в этом событии нельзя использовать асинхронные или корутинные API` |
| WorkerStart | Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface | Возникает после успешного запуска процесса Worker/Task |
| WorkerStop | Hhxsv5\LaravelS\Swoole\Events\WorkerStopInterface | Возникает после нормального завершения процесса Worker/Task |
| WorkerError | Hhxsv5\LaravelS\Swoole\Events\WorkerErrorInterface | Возникает при возникновении ошибки или смертельного исключения в процессе Worker/Task |

1. Создайте обработчики событий, реализуйте соответствующие интерфейсы.

```php
namespace App\Events;
use Hhxsv5\LaravelS\Swoole\Events\ServerStartInterface;
use Swoole\Atomic;
use Swoole\Http\Server;

class ServerStartEvent implements ServerStartInterface
{
    public function __construct()
    {
    }

    public function handle(Server $server)
    {
        // Инициализация глобального счетчика (доступна между процессами)
        $server->atomicCount = new Atomic(2233);
        
        // Вызов в контроллере: app('swoole')->atomicCount->get();
    }
}
namespace App\Events;
use Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface;
use Swoole\Http\Server;

class WorkerStartEvent implements WorkerStartInterface
{
    public function __construct()
    {
    }

    public function handle(Server $server, $workerId)
    {
        // Инициализация объекта пула соединений базы данных
        // DatabaseConnectionPool::init();
    }
}
  1. Конфигурация.
// Измените файл config/laravels.php
'event_handlers' => [
    'ServerStart' => [\App\Events\ServerStartEvent::class], // Управляются в порядке массива
    'WorkerStart' => [\App\Events\WorkerStartEvent::class],
],

Серверные функции

АлиБаба Облако Функциональное вычисление

Официальная документация АлиБаба Облако.

  1. Измените bootstrap/app.php, установите путь к хранилищу. Поскольку проект находится в режиме только чтения, /tmp директория доступна для записи.
$app->useStoragePath(env('APP_STORAGE_PATH', '/tmp/storage'));
  1. Создайте Shell скрипт laravels_bootstrap, затем присвойте ему права на выполнение.
#!/usr/bin/env bash
set +e

# Создание каталогов для хранения
mkdir -p /tmp/storage/app/public
mkdir -p /tmp/storage/framework/cache
mkdir -p /tmp/storage/framework/sessions
mkdir -p /tmp/storage/framework/testing
mkdir -p /tmp/storage/framework/views
mkdir -p /tmp/storage/logs

# Установите переменную окружения APP_STORAGE_PATH, убедитесь что она совпадает с APP_STORAGE_PATH в .env
export APP_STORAGE_PATH=/tmp/storage

# Запустите LaravelS
php bin/laravels start
  1. Настройте template.xml.
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  laravel-s-demo:
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: 'Пример LaravelS для облачной службы'
    fc-laravel-s:
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: laravels.handler
        Runtime: custom
        MemorySize: 512
        Timeout: 30
        CodeUri: ./
        InstanceConcurrency: 10
        EnvironmentVariables:
          BOOTSTRAP_FILE: laravels_bootstrap

Внимание

Проблемы синглтонов

  • В традиционном FPM, срок жизни объекта синглтона ограничен каждым запросом, то есть запрос начинается => создается синглтон => запрос заканчивается => ресурсы синглтонов освобождаются.

  • В Swoole Server, все объекты синглтонов остаются в памяти постоянно, поэтому срок их жизни отличается от FPM. Запрос начинается => создается синглтон => запрос заканчивается => синглтон остается в памяти, требует управления со стороны разработчика.

  • Общие решения:

    1. Напишите класс XxxCleaner очистителя, который реализует интерфейс Hhxsv5\LaravelS\Illuminate\Cleaners\CleanerInterface, затем зарегистрируйте его в laravels.php в разделе cleaners.

    2. Используйте middleware для сброса состояния синглтонов.

    3. Если синглтон был зарегистрирован через ServiceProvider, добавьте этот ServiceProvider в laravels.php в разделе register_providers, чтобы каждый запрос реинициализировал ServiceProvider и заново создал синглтон.

Очистители

Настройка очистителей.

Часто встречающиеся проблемы

Часто встречающиеся проблемы: полный список известных проблем и решений.

Методы отладки

  • Запись логов; если требуется выводить сообщения в консоли, используйте stderr, Log::channel('stderr')->debug('сообщение отладки');

  • Laravel Dump Server (встроен в Laravel 5.7 по умолчанию).### Чтение запросов Должно осуществляться через объект \Illuminate\Http\Request. Переменная окружения ($_ENV) доступна для чтения, часть переменной сервера ($_SERVER) также доступна для чтения. НЕ РЕКОМЕНДУЕТСЯ ИСПОЛЬЗОВАТЬ $_GET, $_POST, $_FILES, $_COOKIE, $_REQUEST, $_SESSION, $GLOBALS.

public function form(\Illuminate\Http\Request $request)
{
    $name = $request->input('name');
    $all = $request->all();
    $sessionId = $request->cookie('sessionId');
    $photo = $request->file('photo');
    // Вызов getContent() для получения оригинального POST body, вместо использования file_get_contents('php://input')
    $rawContent = $request->getContent();
    //...
}

Ответ на запрос

РЕКОМЕНДУЕТСЯ ИСПОЛЬЗОВАТЬ ответ \Illuminate\Http\Response для реакции на запрос, совместимость с echo, vardump(), print_r(). НЕ РЕКОМЕНДУЕТСЯ ИСПОЛЬЗОВАТЬ функции dd(), exit(), die(), header(), setcookie(), http_response_code().

public function json()
{
    return response()->json(['время' => time()])->header('header1', 'значение1')->withCookie('c1', 'v1');
}

Постоянные соединения

СИНГЛТОННЫЕ СОЕДИНЕНИЯ будут находиться в постоянной памяти, РЕКОМЕНДУЕТСЯ АКТИВИРОВАТЬ ПОСТОЯННОЕ СОЕДИНЕНИЕ, чтобы получить лучшие производственные характеристики.

  1. Соединение с базой данных, соединение автоматически восстанавливается после разрыва.
// config/database.php
'connections' => [
    'my_conn' => [
        'driver'    => 'mysql',
        'host'      => env('DB_MY_CONN_HOST', 'localhost'),
        'port'      => env('DB_MY_CONN_PORT', 3306),
        'database'  => env('DB_MY_CONN_DATABASE', 'forge'),
        'username'  => env('DB_MY_CONN_USERNAME', 'forge'),
        'password'  => env('DB_MY_CONN_PASSWORD', ''),
        'charset'   => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
        'prefix'    => '',
        'strict'    => false,
        'options'   => [
            // Активировать постоянное соединение
            \PDO::ATTR_PERSISTENT => true,
        ],
    ],
],
  1. Соединение с Redis, соединение не будет автоматически восстановлено сразу после разрыва, будет выброшено исключение о разрыве соединения, следующий запрос автоматически восстановит соединение. Убедитесь, что правильно выбираете базу данных перед каждой операцией с Redis.
// config/database.php
'redis' => [
    'client' => env('REDIS_CLIENT', 'phpredis'), // РЕКОМЕНДУЕТСЯ ИСПОЛЬЗОВАТЬ phpredis для лучших производственных характеристик
    'default' => [
        'host'       => env('REDIS_HOST', 'localhost'),
        'password'   => env('REDIS_PASSWORD', null),
        'port'       => env('REDIS_PORT', 6379),
        'database'   => 0,
        'persistent' => true, // Активировать постоянное соединение
    ],
],

О проблемах утечки памяти

  • Избегайте использования глобальных переменных, если это необходимо, очистите или переопределите их вручную.

  • Бесконечное добавление элементов в глобальные переменные, статические переменные, синглтоны может привести к переполнению памяти.

    class Test
    {
        public static $array = [];
        public static $string = '';
    }
    
    // Контроллер
    public function test(Request $req)
    {
        // Переполнение памяти
        Test::$array[] = $req->input('param1');
        Test::$string .= $req->input('param2');
    }
  • Методы обнаружения утечек памяти

    1. Измените config/laravels.php: worker_num=1, max_request=1000000, после тестирования верните значения обратно;

    2. Добавьте маршрут /debug-memory-leak, не устанавливайте никаких middleware маршрута, используйте для наблюдения за изменениями памяти процесса Worker;

    Route::get('/debug-memory-leak', function () {
        global $previous;
        $current = memory_get_usage();
        $stats = [
            'prev_mem' => $previous,
            'curr_mem' => $current,
            'diff_mem' => $current - $previous,
        ];
        $previous = $current;
        return $stats;
    });
    1. Запустите LaravelS, отправьте запрос /debug-memory-leak, продолжайте до тех пор, пока diff_mem не станет меньше или равен нулю; если diff_mem всегда больше нуля, значит возможно наличие утечки памяти в глобальных middleware или Laravel framework;

    2. После завершения шага 3, чередуйте запросы к бизнес-маршрутам и /debug-memory-leak (рекомендуется использовать ab/wrk для большого количества запросов к бизнес-маршрутам); начальное увеличение потребления памяти является нормальным явлением. После множества запросов к бизнес-маршрутам, если diff_mem всегда больше нуля, и curr_mem продолжает расти, то скорее всего существует утечка памяти; если curr_mem остается в определенных границах, без постоянного увеличения, то скорее всего нет утечки памяти.

    3. Если невозможно найти решение, max_request является последней мерой защиты.

Настройка параметров ядра Linux

Настройка параметров ядра Linux

Пресс-тестирование

Пресс-тестирование

Пользователи и примеры

  • ОСОБЫЙ СПОНСОР Желе-Комьюнити: Желе-Комьюнити — это профессиональное членство-сообщество, которое предоставляет высококачественные технические материалы, сосредоточенное на развитии программистов, стартапах, предоставляющее надежные открытые продукты.

    Желе-Комьюнити- KuCoin: один из крупнейших криптовалютных бирж.

  • MedLinker: система управления учетными записями для веб-сайта, мобильной версии сайта, приложения и маленьких программ (mini-programs).

    MedLinker
  • Интерактивная платформа онлайн-поддержки ITOK: система отслеживания задач IT и онлайн-общения.

    Интерактивная платформа онлайн-поддержки ITOK
  • Мэньгуагуа

    Мэньгуагуа
  • WookTeam: WookTeam - это легковесный инструмент для онлайн-сотрудничества команд, предоставляющий различные инструменты для работы с документами, онлайн-майнд-мапами, онлайн-схемами процессов, управлением проектами, распределением задач и управлением базой знаний.

  • WeChat публичный аккаунт "Гуанчжоу Та": мероприятия, магазин

    Гуанчжоу Та
  • Платформа для игр Penguin Game Box, новое поколение звезд шоу-бизнеса и реклама услуг маленьких программ Платформа для игр Penguin Game Box

  • Маленькая программа Xiujijiang: сервис ремонта телефонов с возможностью вызова мастера на дом и онлайн-ремонта.

    Сервис ремонта телефонов Xiujijiang
  • Приложение Yi Jian

Другие варианты

Поддержка

Ваша поддержка является нашей самой большой силой вдохновения.

Поддержка

Благодарность

Поддерживаемый Сумма (юань)
*Си Юнг 18.88
*Германия 18.88
Хун Цзи Ван Гуа 100
Маленький тыквенок 10.01
*Дин Чжи 16.66
Аноним 20
Аноним 20
*Янг Блюз 18.88
*Чжуан Жун Панда 10.24
*Шао Янг Хайцюань Луффи 12
*Юэ Аксонг 10
Лога 10
Очень толстый человек 15
Пейге Софтваре 18.88
Бигонс 18.88
*Цзинь Флеймоо 100
Чужестранец 20
Только Вы 100
Месячная Судьба 18.88
Шмилли 20
*Жун 20
*Чжэ 20
Алекс 20
X 20
*Янг 20
*Янг 20
*Чжан 50
Антонио 18.88
*Гуань Лонг 100
0o Фьючерс o0 Му Му *Ко 288
*Юнг 66.66
Джем Социальность 1076
*Чжан 18.88
*Ао 8.88
Двойной деревянный 20

Лицензия

MIT

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/hhxsv5-laravel-s.git
git@api.gitlife.ru:oschina-mirror/hhxsv5-laravel-s.git
oschina-mirror
hhxsv5-laravel-s
hhxsv5-laravel-s
PHP-8.x