Китайская версия документации | Английская документация
🚀 LaravelS — это готовое к использованию адаптивное решение для работы с Laravel/Lumen и Swoole.
Встроенный HTTP/WebSocket сервер
Непрерывная работа в памяти
Гладкое перезагружение
Поддерживает одновременно Laravel и Lumen, совместимость с основными версиями
Простой, готовый к использованию
Зависимость | Описание |
---|---|
PHP |
>=8.2 рекомендуется 8.2
|
Swoole |
>=5.0 рекомендуется 5.1.1
|
Laravel/Lumen |
>=10 рекомендуется 10
|
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 файл находится под версионным контролем
Зарегистрируйте 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);
Выпустите конфигурацию и двоичные файлы.
После каждого обновления LaravelS требуется повторное выполнение команды
publish
; нажмите Release для просмотра изменений между версиями.
php artisan laravels publish
# Конфигурационный файл: config/laravels.php
# Двоичные файлы: bin/laravels bin/fswatch bin/inotify
Настройте конфигурацию config/laravels.php
: IP адреса, порты и прочее, см. конфигурационные параметры.
Оптимизация производительности
Настройка системных параметров - Количество рабочих процессов: 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 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-сервер прослушивает тот же IP и порт, что и HTTP-сервер.
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 самостоятельно
}
}
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,
//...
],
// ...
Используйте SwooleTable
, чтобы связать FD с UserId, это не обязательно, пример использования SwooleTable. Также можно использовать другие глобальные службы хранения данных, такие как Redis/Memcached/MySQL, но следует учитывать возможность конфликта FD при нескольких Swoole Server
экземплярах.
Совместимость с 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;
}
}
// config/laravels.php
'swoole' => [
//...
// Отображает каждые 60 секунд проверку соединений, если соединение не отправляет данные серверу в течение 600 секунд, то это соединение будет принудительно закрыто
'heartbeat_idle_time' => 600,
'heartbeat_check_interval' => 60,
//...
],
# Если за 60 секунд прокси-сервер не отправил данные Nginx, то текущее соединение будет закрыто
proxy_read_timeout 60s;
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.
Установите fswatch.
Выполните команду в корневой директории проекта.
# Отслеживание текущей директории
./bin/fswatch
# Отслеживание директории app
./bin/fswatch ./app
Основано на inotifywait
, поддерживает только Linux.
Установите inotify-tools.
Выполните команду в корневой директории проекта.
# Отслеживание текущей директории
./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
Перед запуском Swoole все определенные таблицы создаются.
// Конфигурация в "config/laravels.php"
[
// ...
'swoole_tables' => [
// Сценарий: Соединение UserId с FD в WebSocket
'ws' => [// Ключ — имя таблицы, используется автоматически при обращении, чтобы избежать коллизий. Здесь определяется таблица с именем wsTable
'size' => 102400,// Максимальное количество записей в таблице
'column' => [// Описание столбцов таблицы
['name' => 'value', 'type' => \Swoole\Table::TYPE_INT, 'size' => 8],
],
],
// ... Добавление других таблиц
],
// ...
];
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();
}
}
// Измените файл config/laravels.php
'event_handlers' => [
'ServerStart' => [\App\Events\ServerStartEvent::class], // Управляются в порядке массива
'WorkerStart' => [\App\Events\WorkerStartEvent::class],
],
bootstrap/app.php
, установите путь к хранилищу. Поскольку проект находится в режиме только чтения, /tmp
директория доступна для записи.$app->useStoragePath(env('APP_STORAGE_PATH', '/tmp/storage'));
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
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. Запрос начинается => создается синглтон => запрос заканчивается => синглтон остается в памяти, требует управления со стороны разработчика.
Общие решения:
Напишите класс XxxCleaner
очистителя, который реализует интерфейс Hhxsv5\LaravelS\Illuminate\Cleaners\CleanerInterface
, затем зарегистрируйте его в laravels.php
в разделе cleaners
.
Используйте middleware для сброса состояния синглтонов.
Если синглтон был зарегистрирован через 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');
}
СИНГЛТОННЫЕ СОЕДИНЕНИЯ будут находиться в постоянной памяти, РЕКОМЕНДУЕТСЯ АКТИВИРОВАТЬ ПОСТОЯННОЕ СОЕДИНЕНИЕ, чтобы получить лучшие производственные характеристики.
// 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,
],
],
],
// 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');
}
Методы обнаружения утечек памяти
Измените config/laravels.php
: worker_num=1, max_request=1000000
, после тестирования верните значения обратно;
Добавьте маршрут /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;
});
Запустите LaravelS
, отправьте запрос /debug-memory-leak
, продолжайте до тех пор, пока diff_mem
не станет меньше или равен нулю; если diff_mem
всегда больше нуля, значит возможно наличие утечки памяти в глобальных middleware
или Laravel framework
;
После завершения шага 3, чередуйте запросы к бизнес-маршрутам и /debug-memory-leak
(рекомендуется использовать ab
/wrk
для большого количества запросов к бизнес-маршрутам); начальное увеличение потребления памяти является нормальным явлением. После множества запросов к бизнес-маршрутам, если diff_mem
всегда больше нуля, и curr_mem
продолжает расти, то скорее всего существует утечка памяти; если curr_mem
остается в определенных границах, без постоянного увеличения, то скорее всего нет утечки памяти.
Если невозможно найти решение, max_request
является последней мерой защиты.
ОСОБЫЙ СПОНСОР Желе-Комьюнити: Желе-Комьюнити — это профессиональное членство-сообщество, которое предоставляет высококачественные технические материалы, сосредоточенное на развитии программистов, стартапах, предоставляющее надежные открытые продукты.
- KuCoin: один из крупнейших криптовалютных бирж.
MedLinker: система управления учетными записями для веб-сайта, мобильной версии сайта, приложения и маленьких программ (mini-programs).
Интерактивная платформа онлайн-поддержки ITOK: система отслеживания задач IT и онлайн-общения.
WookTeam: WookTeam - это легковесный инструмент для онлайн-сотрудничества команд, предоставляющий различные инструменты для работы с документами, онлайн-майнд-мапами, онлайн-схемами процессов, управлением проектами, распределением задач и управлением базой знаний.
WeChat публичный аккаунт "Гуанчжоу Та": мероприятия, магазин
Платформа для игр Penguin Game Box, новое поколение звезд шоу-бизнеса и реклама услуг маленьких программ
Маленькая программа 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 |
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )