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

OSCHINA-MIRROR/hhxsv5-laravel-s

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

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

🚀 LaravelS — это готовый адаптер между Laravel/Lumen и Swoole

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


Продолжительные обновления

  • Пожалуйста, Watch этот репозиторий, чтобы получать последние обновления.

Содержание

Особенности

Бенчмарки

Зависимости

Зависимость Требование
PHP >=8.2 Рекомендуется 8.2
Swoole >=5.0 Рекомендуется Yöntem 5.1.1
Laravel/Lumen >=10 Рекомендуется 10

Установка

  1. Установите пакет через Composer(packagist).
# 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. Зарегистрируйте провайдер сервиса (выберите один из двух вариантов).
  • Laravel: в файле config/app.php, Laravel 5.5+ поддерживает автоматическое открытие пакетов, поэтому вы можете пропустить этот шаг.

    'providers' => [
        //...
        Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class,
    ],
  • Lumen: в файле bootstrap/app.php

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

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

php artisan laravels publish
# Конфигурация: config/laravels.php
# Двоичные файлы: bin/laravels bin/fswatch bin/inotify
  1. Измените config/laravels.php: listen_ip, listen_port, смотрите Настройки.

  2. Настройка производительности

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

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

Запуск

Пожалуйста, внимательно прочитайте уведомления перед запуском, Важные уведомления(ВАЖНО).

  • Команды: php bin/laravels {start|stop|restart|reload|info|help}.
Команда Описание
start Запуск LaravelS, вывод списка процессов командой "*ps -ef
stop Остановка LaravelS, и активация метода onStop настроенных процессов
restart Перезапуск LaravelS: грациозная остановка перед запуском; сервис недоступен до завершения запуска
reload Перезагрузка всех процессов Task/Worker/Timer, содержащих бизнес-коды, и активация метода onReload настроенных процессов, НЕ может перезагружать основные процессы. После изменения config/laravels.php вам следует использовать restart для перезапуска
info Отображение информации о версии компонента
help Отображение справочной информации
  • Параметры запуска для команд start и restart.
Параметр Описание
-d --daemonize
-e --env
-i --ignore
-x --x-version
  • Файлы выполнения: start автоматически выполнит php artisan laravels config и создаст эти файлы, разработчики обычно не должны обращать внимание на них, рекомендуется добавить их в .gitignore.
Файл Описание
storage/laravels.conf Файл конфигурации выполнения LaravelS
storage/laravels.pid Файл PID основного процесса
storage/laravels-timer-process.pid Файл PID процесса Timer
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 потока файла, совет: положите файл сокета в директорию /dev/shm для лучшей производительности #server unix:/вашпуть/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; # Не забудьте привязать хост server_name laravels.com; root /вашпуть/laravel-s-test/public; access_log /вашпуть/log/nginx/$server_name.access.log main; autoindex off; index index.html index.htm; # Nginx обрабатывает статические ресурсы (рекомендовано включить gzip), LaravelS обрабатывает динамические ресурсы. location / { try_files $uri @laravels; } # Ответить 404 сразу при запросе PHP файла, чтобы избежать раскрытия public/*.php #location ~* .php$ { # return 404; #} location @laravels { # proxy_connect_timeout 60s; # proxy_send_timeout 60s; # proxy_read_timeout 120s; 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>
    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
    SetOutputFilter DEFLATE
    Level DEFLATE 2
</IfModule>

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

    DocumentRoot /вашпуть/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 lbmethod=byrequests
    </Proxy>
    #ProxyPass / balancer://laravels/
    #ProxyPassReverse / balancer://laravels/
```    # Apache обрабатывает статические ресурсы, LaravelS обрабатывает динамические ресурсы.
    RewriteEngine Включен
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
    RewriteRule ^/(.*)$ balancer://laravels%{REQUEST_URI} [P,L]

    ErrorLog ${APACHE_LOG_DIR}/www.laravels.com.error.log
    CustomLog ${APACHE_LOG_DIR}/www.laravels.com.access.log объединенный
</VirtualHost>

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

Адрес прослушивания WebSocket сервера совпадает с адресом 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://www.swoole.co.uk/docs/modules/swoole-websocket-server
 */
class WebSocketService implements WebSocketHandlerInterface
{
    // Объявляем конструктор без параметров
    public function __construct()
    {
    }

    public function onOpen(Server $server, Request $request)
    {
        // До того как событие onOpen будет активировано, HTTP запрос на установление WebSocket прошел через Laravel маршрут,
        // поэтому Laravel's Request, Auth информация доступна, а также можно читать и записывать Session, но только в событии onOpen.
        // \Log::info('Новое соединение WebSocket', [$request->fd, request()->все(), 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 в (2, 4, 5), см. https://www.swoole.co.uk/docs/modules/swoole-server/configuration
    'dispatch_mode' => 2,
    //...
],
// ...
  1. Используйте SwooleTable, чтобы связать FD & UserId, опционально, Пример использования SwooleTable. Также вы можете использовать другие глобальные службы хранения данных, такие как Redis/Memcached/MySQL, но будьте осторожны, так как FD может конфликтовать между несколькими Swoole Servers.

  2. Интеграция с Nginx (Рекомендуется)

См. Прокси WebSocket```nginx map $http_upgrade $connection_upgrade { default upgrade; '' close; } upstream swoole { # Подключение IP:Port server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s; # Подключение UnixSocket потока файла, совет: поместите сокет-файл в директорию /dev/shm для лучшей производительности #server unix:/вашпуть/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; # Не забудьте привязать хост server_name laravels.com; root /вашпуть/laravel-s-test/public; access_log /вашпуть/log/nginx/$server_name.access.log main; autoindex off; index index.html index.htm; # Nginx обрабатывает статические ресурсы (рекомендовано включить gzip), LaravelS обрабатывает динамические ресурсы. location / { try_files $uri @laravels; } # Ответ 404 непосредственно при запросе PHP-файла, чтобы избежать раскрытия публичных *.php файлов #location ~* .php$ { # return 404; #} # Http и WebSocket одновременно, Nginx идентифицирует их по "location" # !!! Расположение WebSocket - "/ws" # JavaScript: var ws = new WebSocket("ws://laravels.com/ws"); location =/ws { # proxy_connect_timeout 60s; # proxy_send_timeout 60s; # proxy_read_timeout: Nginx закроет соединение, если прокси сервер не отправит данные в течение 60 секунд; В то же время это поведение также зависит от настроек heartbeats 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; } 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; proxy_pass http://swoole; } }



5. Настройка heartbeats

- Настройка heartbeats Swoole

    ```php
    // config/laravels.php
    'swoole' => [
        //...
        // Все соединения проверяются каждые 60 секунд. Если соединение не отправляет данные серверу в течение 600 секунд, соединение будет принудительно закрыто.
        'heartbeat_idle_time'      => 600,
        'heartbeat_check_interval' => 60,
        //...
    ],
    ```

- Прокси read timeout Nginx

    ```nginx
    # Nginx закроет соединение, если прокси сервер не отправит данные в течение 60 секунд
    proxy_read_timeout 60s;
    ```

6. Отправка данных в контроллере

```php
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);
    }
}

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

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

Обычно вы можете сбросить/разрушить некоторые глобальные/статические переменные, или изменить текущий объект Request/Response.

  • laravels.received_request После того как LaravelS проанализировал 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'); // Измените запрос $req->request->set('post_key', 'hhxsv5'); // Измените данные POST });

    
    
    
  • laravels.generated_response После того как ядро Laravel завершило обработку запроса, перед тем как LaravelS проанализирует 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'); // Измените заголовок ответа
    });

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

Эта функциональность зависит от AsyncTask Swoole, вам нужно установить swoole.task_worker_num в config/laravels.php первым делом. Производительность обработки асинхронных событий влияется количеством процессов задач Swoole, вам следует правильно настроить 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;
    }
}
  1. Создайте класс слушателя.
use Hhxsv5\LaravelS\Swoole\Task\Event;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Hhxsv5\LaravelS\Swoole\Task\Listener;
class TestListener1 extends Listener
{
    public function handle(Event $event)
    {
        \Log::info(__CLASS__ . ':handle start', [$event->getData()]);
        sleep(2); // Имитация медленного кода
        // Доставка задачи в CronJob, но не поддерживаются завершения задач
        // Примечание: Измените task_ipc_mode на 1 или 2 в конфигурации config/laravels.php, см. https://www.swoole.co.uk/docs/modules/swoole-server/configuration
        $ret = Task::deliver(new TestTask('task data'));
        var_dump($ret);
        // Исключения, выбрасываемые здесь, будут перехватываться верхним уровнем и записываться в лог Swoole. Разработчики должны использовать try/catch самостоятельно.
        // return false; // Остановить распространение этого события до последующих слушателей
    }
}
  1. Запустите событие.
// Создайте экземпляр события и запустите его, "fire" является асинхронным.
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); // Вернуть true, если успешно, иначе false

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

Эта функциональность зависит от AsyncTask Swoole, вам нужно установить swoole.task_worker_num в config/laravels.php первым делом. Производительность обработки задач влияется количеством процессов задач Swoole, вам следует правильно настроить 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;
    }
    // Логика обработки задачи, выполняется в процессе задачи, НЕЛЬЗЯ доставлять задачи
    public function handle()
    {
        \Log::info(__CLASS__ . ':handle start', [$this->data]);
        sleep(2); // Имитация медленного кода
        // Исключения, выбрасываемые здесь, будут перехватываться верхним уровнем и записываться в лог Swoole. Разработчики должны использовать try/catch самостоятельно.
        $this->result = 'результат ' . $this->data;
    }
    // Необходимо, событие завершения, логика после выполнения задачи, выполняется в рабочем процессе, МОЖНО доставлять задачи
    public function finish()
    {
        \Log::info(__CLASS__ . ':finish start', [$this->result]);
        Task::deliver(new TestTask2('task2 data')); // Доставка другой задачи
    }
}
  1. Доставьте задачу.
// Создайте экземпляр TestTask и доставьте его, "deliver" является асинхронным.
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); // Вернуть true, если успешно, иначе false

Миллисекундный cron job

Обертка cron job на основе Миллисекундного таймера Swoole, замена Linux Crontab.

  1. Создайте класс задачи cron.
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;
    // !!! Интервал выполнения задачи cron можно настроить двумя способами (выберите один из двух): либо переопределив соответствующий метод, либо передав параметры при регистрации задачи cron.
    // --- Переопределение соответствующего метода для возврата конфигурации: начало
    public function interval()
    {
        return 1000; // Выполнять каждые 1000 миллисекунд
    }
    public function isImmediate()
    {
        return false; // Незамедлительно запустить `run` после установки
    }
    // --- Переопределение соответствующего метода для возврата конфигурации: конец
    public function run()
    {
        \Log::info(__METHOD__, ['начало', $this->i, microtime(true)]);
        // выполнить действия
        // sleep(1); // Для Swoole < 2.1
        Coroutine::sleep(1); // Для Swoole>=2.1 корутин будет автоматически создан для выполнения.
        $this->i++;
        \Log::info(__METHOD__, ['конец', $this->i, microtime(true)]);

        if ($this->i >= 10) { // Выполнить 10 раз только
            \Log::info(__METHOD__, ['остановка', $this->i, microtime(true)]);
            $this->stop(); // Остановить эту задачу cron, но она снова запустится после перезапуска/перезагрузки.
            // Отправить задачу в CronJob, но не поддерживаются завершающие события задач.
            // Примечание: Измените task_ipc_mode на 1 или 2 в config/laravels.php, см. https://www.swoole.co.uk/docs/modules/swoole-server/configuration
            $ret = Task::deliver(new TestTask('задача данных'));
            var_dump($ret);
        }
        // Исключения, выброшенные здесь, будут пойманы верхним слоем и записаны в лог Swoole. Разработчики должны использовать try/catch вручную.
    }
}
```2. Зарегистрировать задачу cron.
```php
// Зарегистрировать задачи cron в файле "config/laravels.php"
[
    // ...
    'timer'          => [
        'enable' => true, // Включить Timer
        'jobs'   => [ // Список задач cron
            // Включить 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'),
    ],
    // ...
];
  1. Примечание: серверная кластеризация запускает несколько таймеров, поэтому вам следует убедиться, что запущен только один таймер, чтобы избежать повторного выполнения задач.

  2. LaravelS v3.4.0 начинает поддерживать горячую перезагрузку процесса Timer. После получения сигнала SIGUSR1 LaravelS ждет max_wait_time(по умолчанию 5) секунд до завершения процесса, затем процесс Manager запускает процесс Timer снова.

  3. Если вам требуется использование только задач с расписанием на уровне минут, рекомендуется включить 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. Включите переключатель в Настройках.

    3. Обратите внимание: изменяйте файлы только в 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 в вашем проекте

/**
 * $swoole является экземпляром `Swoole\WebSocket\Server`, если включен WebSocket сервер, иначе `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' => [// Ключ - имя таблицы, к которому добавляется суффикс "Table", чтобы избежать конфликта имён. Здесь определена таблица с именем "wsTable".
            'size'   => 102400,// Максимальный размер
            'column' => [// Определение столбцов
                ['name' => 'value', 'type' => \Swoole\Table::TYPE_INT, 'size' => 8],
            ],
        ],
        // ...Определите остальные таблицы
    ],
    // ...
];
  1. Доступ к Table: все экземпляры таблиц будут связаны с SwooleServer, доступны через app('swoole')->xxxTable.
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, такие как Authenticate.
         * Например:
         * Браузерная сторона: var ws = new WebSocket("ws://127.0.0.1:5200/ws");
         * Затем маршрут /ws в Laravel должен иметь middleware, такой как Authenticate.
         * Route::get('/ws', function () {
         *     // Ответить любым содержимым с кодом состояния 200
         *     return 'websocket';
         * })->middleware(['auth']);
         */
        // $user = Auth::user();
        // $userId = $user ? $user->id : 0; // 0 означает гостевой пользователь, который не вошел в систему
        $userId = mt_rand(1000, 10000);
        // if (!$userId) {
        //     // Отключить соединения незарегистрированных пользователей
        //     $server->disconnect($request->fd);
        //     return;
        // }
        $this->wsTable->set('uid:' . $userId, ['value' => $request->fd]);// Связать карту uid с fd
        $this->wsTable->set('fd:' . $request->fd, ['value' => $userId]);// Связать карту fd с uid
        $server->push($request->fd, "Добро пожаловать в LaravelS #{$request->fd}");
    }
    public function onMessage(Server $server, Frame $frame)
    {
        // Распространять сообщение
        foreach ($this->wsTable as $key => $row) {
            if (strpos($key, 'uid:') === 0 && $server->isEstablished($row['value'])) {
                $content = sprintf('Распространение: новое сообщение "%s" от #%d', $frame->data, $frame->fd);
                $server->push($row['value'], $content);
            }
        }
    }
    public function onClose(Server $server, $fd, $reactorId)
    {
        $uid = $this->wsTable->get('fd:' . $fd);
        if ($uid !== false) {
            $this->wsTable->del('uid:' . $uid['value']);// Отвязать карту uid
        }
        $this->wsTable->del('fd:' . $fd);// Отвязать карту fd
        $server->push($fd, "Прощай #{$fd}");
    }
}
```## Поддержка множества портов и протоколов



> Для более подробной информации обратитесь к [Swoole Server AddListener](https://www.swoole.co.uk/docs/modules/swoole-server-methods#swoole_server-addlistener)

Чтобы сделать наш основной сервер поддержкой большего количества протоколов, а не только HTTP и WebSocket, мы внедрили возможность `поддержки нескольких портов и протоколов` Swoole в LaravelS и назвали её `Socket`. Теперь вы можете легко создавать приложения на основе 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, 'До свидания');
    }
}

Эти соединения сокетов используют одни и те же рабочие процессы с вашими соединениями HTTP/WebSocket. Поэтому проблем возникнуть не должно, если вы хотите выполнять задачи, использовать SwooleTable, а также компоненты Laravel такие как DB и Eloquent и так далее. В то же время вы можете получить доступ к объекту Swoole\Server\Port непосредственно через свойство объекта swoolePort.

public function onReceive(Server $server, $fd, $reactorId, $data)
{
    $port = $this->swoolePort; // Получаем объект `Swoole\Server\Port`
}
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://www.swoole.co.uk/docs/modules/swoole-server/multiple-ports
        $port = $swoole->ports[0]; // Получаем объект `Swoole\Server\Port`, $port[ Yöntem:0] — это порт основного сервера
        foreach ($port->connections as $fd) { // Проходимся по всем соединениям
            // $swoole->send($fd, 'Отправка сообщения TCP');
            // if($swoole->isEstablished($fd)) {
            //     $swoole->push($fd, 'Отправка сообщения WebSocket');
            // }
        }
    }
}
  1. Регистрация сокетов.
// Измените `config/laravels.php`
//...
'sockets' => [
    [
        'host'     => '127.0.0.1',
        'port'     => 5291,
        'type'     => SWOOLE_SOCK_TCP,// Тип сокета: SWOOLE_SOCK_TCP/SWOOLE_SOCK_TCP6/SWOOLE_SOCK_UDP/SWOOLE_SOCK_UDP6/SWOOLE_UNIX_DGRAM/SWOOLE_UNIX_STREAM
        'settings' => [// Настройки Swoole:https://www.swoole.co.uk/docs/modules/swoole-server-methods#swoole_server-addlistener
            'open_eof_check' => true,
            'package_eof'    => "\r\n",
        ],
        'handler'  => \App\Sockets\TestTcpSocket::class,
        'enable'   => true, // Включено ли, значение по умолчанию true
    ],
],

Что касается конфигурации heartbeats, она может быть установлена только на основном сервере и не может быть настроена на сокете, но сокет наследует конфигурацию heartbeats от основного сервера.

Для TCP сокета события onConnect и onClose будут заблокированы при dispatch_mode Swoole равном 1/3, поэтому чтобы разблокировать эти два события, установите dispatch_mode на 2/4/5.

'swoole' => [
    //...
    'dispatch_mode' => 2,
    //...
];
  1. Тестирование.
  • TCP: telnet 127.0.0.1 5291

  • UDP: [Linux] echo "Привет LaravelS" > /dev/udp/127.0.0.1/5292

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

    • UDP
    '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
    '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.
    '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 Coroutine

  • Предупреждение: Порядок выполнения кода в корутине может быть нарушен. Данные уровня запроса должны быть изолированы по идентификатору корутины. Однако во многих случаях в Laravel/Lumen используется однообразие и статические атрибуты, что приводит к взаимному влиянию данных между различными запросами, что является небезопасным. Например, соединение с базой данных является однообразием, и то же соединение с базой данных использует один и тот же ресурс PDO. Это работает в синхронном блокирующем режиме, но не работает в асинхронном режиме корутин. Каждый запрос должен создавать различные соединения и поддерживать состояние I/O различных соединений, что требует пула соединений.

  • НЕ УСТАНОВЛЯТЬ корутину, только пользовательские процессы могут использовать корутины.

Пользовательский процесс

Поддерживает разработчиков в создании специальных рабочих процессов для мониторинга, отчетности или других специальных задач. См. addProcess.

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

    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 Quit tag for Reload updates
         */
        private static $quit = false;
    ```        public static function callback(Server $swoole, Process $process)
        {
            // Callback method should not terminate. Once it does, the controlling process will automatically create a new process.
            while (!self::$quit) {
                \Log::info('Test process: running');
                // sleep(1); // Swoole < 2.1
                Coroutine::sleep(1); // Swoole>=2.1: Coroutines and runtime environment will be automatically activated for callback(). Note compatibility of used components with coroutines. If they are incompatible, some coroutines may still be activated, e.g., \Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_TCP | SWOOLE_HOOK_SLEEP | SWOOLE_HOOK_FILE);
                // Send a task to the user process, but task completion actions are not supported.
                // Note: Change task_ipc_mode to 1 or 2 in config/laravels.php, see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
                $ret = Task::deliver(new TestTask('task data'));
                var_dump($ret);
                // The top level will catch exceptions thrown in the callback method and log them into the Swoole log. After that, this process will terminate. The controlling process will again create a process after 3 seconds, so developers should use try/catch to catch exceptions themselves to avoid frequent creation of processes.
                // throw new \Exception('exception');
            }
        }
    
    
    
        // Requirement: LaravelS >= v3.4.0 & callback() must be an asynchronous program without blocking.
        public static function onReload(Server $swoole, Process $process)
        {
            // Stop the process...
            // Then terminate the process
            \Log::info('Test process: reloading');
            self::$quit = true;
            // $process->exit(0); // Forcefully call termination of the process
        }
    
        // Requirement: LaravelS >= v3.7.4 & callback() must be an asynchronous program without blocking.
        public static function onStop(Server $swoole, Process $process)
        {
            // Stop the process...
            // Then terminate the process
            \Log::info('Test process: stopping');
            self::$quit = true;
            // $process->exit(0); // Forcefully call termination of the process
        }
    }
  2. Register the TestProcess.

    // Modify `config/laravels.php`
    // ...
    'processes' => [
        'test' => [ // Key name - this is the process name
            'class'    => \App\Processes\TestProcess::class,
            'redirect' => false, // Redirect stdin/stdout, true or false
            'pipe'     => 0,     // Pipe type, 0: no pipe 1: SOCK_STREAM 2: SOCK_DGRAM
            'enable'   => true,  // Enabled, default value true
            //'num'    => 3   // To create multiple processes of this class, default value 1
            //'queue'    => [ // Enable message queue as means of inter-process communication, empty array configuration means using default parameters
            //    'msg_key'  => 0,    // Message queue key. Default: ftok(__FILE__, 1).
            //    'mode'     => 2,    // Connection mode, default 2 which means concurrent access mode
            //    'capacity' => 8192, // Length of one message, limited by kernel OS parameters. Default 8192, maximum value 65536
            //],
            //'restart_interval' => 5, // After exiting the process with error, how many seconds to wait before restarting the process, default value 5 seconds
        ],
    ],
  3. Note: The callback() method should not terminate. If it terminates, the controlling process will restart the process.

  4. Example: Writing data to the user process.

    // config/laravels.php
    'processes' => [
        'test' => [
            'class'    => \App\Processes\TestProcess::class,
            'redirect' => false,
            'pipe'     => 1,
        ],
    ],
// app/Processes/TestProcess.php
public static function callback(Server $swoole, Process $process)
{
    while ($data = $process->read()) {
        \Log::info('TestProcess: reading data', [$data]);
        $process->write('TestProcess: ' . $data);
    }
}
// app/Http/Controllers/TestController.php
public function testProcessWrite()
{
    /** @var \Swoole\Process[] $process */
    $special_processes = \Hhxsv5\LaravelS\LaravelS::getSpecialProcesses();
    $process = $special_processes['test'];
    $process->write('TestController: writing data ' . time());
    dd($process->read());
}

Common Components

Apollo

LaravelS will use the Apollo configuration and write it to the .env file at startup. At the same time, LaravelS will start a special Apollo process for monitoring the configuration and automatic reload when the configuration changes.

  1. Enable Apollo: add parameters --enable-apollo and Apollo to the launch parameters.

    php bin/laravels start --enable-apollo --apollo-server=http://127.0.0.1:8080 --apollo-application-id=LARAVEL-S-TEST
  2. Hot updates support (optional).

    // Modify `config/laravels.php`
    'processes' => Hhxsv5\LaravelS\Components\Apollo\Process::getConfig(),
    // When there are other special process configurations
    'processes' => [
        'test' => [
            'class'    => \App\Processes\TestProcess::class,
            'redirect' => false,
            'pipe'     => 1,
        ],
        // ...
    ] + Hhxsv5\LaravelS\Components\Apollo\Process::getConfig(),
  3. List of available parameters.| Параметр | Описание | По умолчанию | Пример | | -------- | -------- | -------- | -------- | | apollo-сервер | URL сервера Аполло | - | --apollo-сервер=http://127.0.0.1:8080 | | apollo-приложение-id | ID приложения Аполло | - | --apollo-приложение-id=LARAVEL-S-ТЕСТ | | apollo-namespace | Неймспейс, к которому принадлежит приложение, поддерживает указание нескольких значений | приложение | --apollo-namespace=приложение --apollo-namespace=окружение | | apollo-кластер | Кластер, к которому принадлежит приложение | по умолчанию | --apollo-кластер=по умолчанию | | apollo-client-ip | IP текущего экземпляра, также может использоваться для серого выпуска | локальная внутренняя сеть IP | --apollo-client-ip=10.2.1.83 | | apollo-pull-timeout | Время ожидания получения конфигурации (в секундах) | 5 | --apollo-pull-timeout=5 | | apollo-сохранение-старого-окружения | Сохранять ли старый файл конфигурации .env при его обновлении | false | --apollo-сохранение-старого-окружения=true |

Прометеус

Поддержка мониторинга и тревог Прометеуса, графический просмотр метрик в Графане. Для создания окружения Прометеуса и Графаны обратитесь к Docker Compose.

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

  2. Скопируйте файл конфигурации prometheus.php в директорию config вашего проекта. Измените конфигурацию по необходимости.

    # Выполните команды в корневой директории проекта
    cp vendor/hhxsv5/laravel-s/config/prometheus.php config/

    Если ваш проект это Lumen, вам также потребуется вручную загрузить конфигурацию $app->configure('prometheus'); в bootstrap/app.php.

  3. Настройте глобальное средство Hhxsv5\LaravelS\Component\Prometheus\RequestMiddleware::class. Чтобы как можно более точно считать затраты времени на запрос, RequestMiddleware должен быть первым глобальным средством, которое следует расположить перед другими средствами.

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

  5. Настройте processes в config/laravels.php для сбора метрик процессов Swoole Worker/Task/Timer регулярно.

    'processes' => Hhxsv5\LaravelS\Component\Prometheus\CollectorProcess::getDescription(),
  6. Создайте маршрут для вывода метрик.

    use Hhxsv5\LaravelS\Component\Prometheus\Exporter;
    
    Route::get('/actuator/prometheus', function () {
        $result = app(Exporter::class)->render();
        return response($result, 200, ['Content-Type' => Exporter::RENDER_MIME_TYPE]);
    });
  7. Завершите настройку Прометеуса и запустите его.

    global:
      scrape_interval: 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 and port of the monitored service
    # Dynamically discovered using one of the methods of 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.

Grafana Dashboard

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

Конфигурация событий Swoole

Поддерживаемые события:

Событие Интерфейс Когда происходит
СерверНачало Hhxsv5\LaravelS\Swoole\Events\ServerStartInterface Происходит при запуске основного процесса, это событие не должно содержать сложной бизнес-логики и может выполнять только простую работу инициализации.
СерверЗавершение Hhxsv5\LaravelS\Swoole\Events\ServerStopInterface Происходит при нормальном завершении сервера, НЕЛЬЗЯ использовать асинхронные или корутинные API в этом событии.
РаботникНачало Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface Происходит после запуска процесса Рабочий/Задача, и завершения инициализации Laravel.
РаботникЗавершение Hhxsv5\LaravelS\Swoole\Events\WorkerStopInterface Происходит после нормального завершения процесса Рабочий/Задача
РаботникОшибка Hhxsv5\LaravelS\Swoole\Events\WorkerErrorInterface Происходит при возникновении исключения или критической ошибки в процессе Рабочий/Задача

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

namespace App\События;
use Hhxsv5\LaravelS\Swoole\Events\ServerStartInterface;
use Swoole\Atomic;
use Swoole\Http\Server;
class СерверНачалоСобытие implements ServerStartInterface
{
    public function __construct()
    {
    }
    public function handle(Server $server)
    {
        // Инициализация глобального счетчика (доступен во всех процессах)
        $server->atomicCounter = new Atomic(2233);

        // Вызывается в контроллере: app('swoole')->atomicCounter->get();
    }
}
namespace App\События;
use Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface;
use Swoole\Http\Server;
class РаботникНачалоСобытие implements WorkerStartInterface
{
    public function __construct()
    {
    }
    public function handle(Server $server, $workerId)
    {
        // Инициализация пула соединений базы данных
        // DatabaseConnectionPool::init();
    }
}

2.Настройка.

// Измените `config/laravels.php`
'event_handlers' => [
    'СерверНачало' => [\App\События\СерверНачалоСобытие::class], // Триггер событий в порядке массива
    'РаботникНачало' => [\App\События\РаботникНачалоСобытие::class],
],

Серверлесс

АлиБаба Облачная Функция Compute

Функция Compute.

1.Измените bootstrap/app.php и установите каталог хранения. Поскольку проектная директория является только для чтения, каталог /tmp может только читаться и записываться.

$app->useStoragePath(app('env')->get('APP_STORAGE_PATH', '/tmp/storage')));

2.Создайте скрипт командной строки 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

3.Настройте template.xml.

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  laravel-s-демо:
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: 'Демо LaravelS для Serverless'
    fc-laravel-s:
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Хэндлер: laravels.хэндлер
        Рантайм: кастом
        MemorySize: OnClickListener 512
        Timeout: 30
        CodeUri: ./
        InstanceConcurrency: 10
        EnvironmentVariables:
          BOOTSTRAP_FILE: laravels_bootstrap

Важные примечания

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

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

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

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

    1. Создайте класс XxxCleaner для очистки состояния объекта-одиночки. Этот класс реализует интерфейс Hhxsv5\LaravelS\Illuminate\Cleaners\CleanerInterface и затем регистрируется в cleaners файла laravels.php.

    2. Обновите состояние объектов-одиночек с помощью Middleware.

    3. Переопределите ServiceProvider, добавьте XxxServiceProvider в register_providers файла laravels.php. Таким образом, можно будет переинициализировать объекты-одиночки при каждом запросе См..

Очистители

Конфигурация очистителей.

Знаменитые проблемы

Знаменитые проблемы: пакет известных проблем и решений.

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

  • Логирование; если вы хотите выводить данные в консоль, вы можете использовать stderr, Log::channel('stderr')->debug('сообщение отладки').

  • Laravel Dump Server(Laravel 5.7 уже встроен по умолчанию).

Чтение запроса

Чтение запроса через объект Illuminate\Http\Request, переменные окружения доступны для чтения, некоторые переменные сервера доступны для чтения, НЕЛЬЗYO ИСПОЛЬЗОВАТЬ переменные $_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, вместо использования file_get_contents('php://input')
    $rawContent = $request->getContent();
    //...
}

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

Ответ на запрос через объект Illuminate\Http\Response, совместим с echo/vardump()/print_r(), НЕЛЬЗYO ИСПОЛЬЗОВАТЬ функции 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, оно не будет автоматически переподключаться сразу после отключения, а выбросит исключение о потере соединения, переподключится следующий раз. Вам следует убедиться, что SELECT DB правильно перед каждым использованием 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, // Включение устойчивого соединения
    ],
],

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

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

  • Бесконечное добавление элементов в статическую/глобальную переменную приведёт к OOM (Out of Memory).

    class Test
    {
        public static $array = [];
        public static $string = '';
    }
    
    // Контроллер
    public function test(Request $req)
    {
        // Out of Memory
        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### Пресс-тестирование Пресс-тестирование

Альтернативы

Поддержка проекта

Лицензия

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