Английская документация | Китайская документация
🚀 LaravelS — это готовый адаптер между Laravel/Lumen и Swoole
Watch
этот репозиторий, чтобы получать последние обновления.Встроенный HTTP/WebSocket сервер
Оперативная память
Грациозное перезагружание
Поддерживает Laravel/Lumen, хорошую совместимость
Простой и готовый к использованию
Зависимость | Требование |
---|---|
PHP |
>=8.2 Рекомендуется 8.2
|
Swoole |
>=5.0 Рекомендуется Yöntem 5.1.1
|
Laravel/Lumen |
>=10 Рекомендуется 10
|
# 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 файл находится под системой контроля версий
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 вам потребуется повторное публикование; нажмите здесь для просмотра заметок о каждом выпуске.
php artisan laravels publish
# Конфигурация: config/laravels.php
# Двоичные файлы: bin/laravels bin/fswatch bin/inotify
Измените config/laravels.php
: listen_ip
, listen_port
, смотрите Настройки.
Настройка производительности
Настройте параметры ядра- Количество рабочих процессов: 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 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 сервера совпадает с адресом 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://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 самостоятельно.
}
}
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,
//...
],
// ...
Используйте SwooleTable
, чтобы связать FD & UserId, опционально, Пример использования SwooleTable. Также вы можете использовать другие глобальные службы хранения данных, такие как Redis/Memcached/MySQL, но будьте осторожны, так как FD может конфликтовать между несколькими Swoole Servers
.
Интеграция с 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.
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;
}
}
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; // Остановить распространение этого события до последующих слушателей
}
}
// Создайте экземпляр события и запустите его, "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.
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')); // Доставка другой задачи
}
}
// Создайте экземпляр 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 на основе Миллисекундного таймера Swoole, замена
Linux
Crontab
.
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'),
],
// ...
];
Примечание: серверная кластеризация запускает несколько таймеров, поэтому вам следует убедиться, что запущен только один таймер, чтобы избежать повторного выполнения задач.
LaravelS v3.4.0
начинает поддерживать горячую перезагрузку процесса Timer
. После получения сигнала SIGUSR1
LaravelS ждет max_wait_time
(по умолчанию 5) секунд до завершения процесса, затем процесс Manager
запускает процесс Timer
снова.
Если вам требуется использование только задач с расписанием на уровне минут, рекомендуется включить 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.
Установите 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
в вашем проекте/**
* $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
Все определенные таблицы создаются до старта Swoole.
// в файле "config/laravels.php"
[
// ...
'swoole_tables' => [
// Сценарий: связь UserId & FD в WebSocket
'ws' => [// Ключ - имя таблицы, к которому добавляется суффикс "Table", чтобы избежать конфликта имён. Здесь определена таблица с именем "wsTable".
'size' => 102400,// Максимальный размер
'column' => [// Определение столбцов
['name' => 'value', 'type' => \Swoole\Table::TYPE_INT, 'size' => 8],
],
],
// ...Определите остальные таблицы
],
// ...
];
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');
// }
}
}
}
// Измените `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,
//...
];
TCP: telnet 127.0.0.1 5291
UDP: [Linux] echo "Привет LaravelS" > /dev/udp/127.0.0.1/5292
Пример регистрации других протоколов.
'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,
],
],
'sockets' => [
[
'host' => '0.0.0.0',
'port' => 5293,
'type' => SWOOLE_SOCK_TCP,
'settings' => [
'open_http_protocol' => true,
],
'handler' => \App\Sockets\TestHttp::class,
],
],
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,
],
],
Предупреждение: Порядок выполнения кода в корутине может быть нарушен. Данные уровня запроса должны быть изолированы по идентификатору корутины. Однако во многих случаях в Laravel/Lumen используется однообразие и статические атрибуты, что приводит к взаимному влиянию данных между различными запросами, что является небезопасным
. Например, соединение с базой данных является однообразием, и то же соединение с базой данных использует один и тот же ресурс PDO. Это работает в синхронном блокирующем режиме, но не работает в асинхронном режиме корутин. Каждый запрос должен создавать различные соединения и поддерживать состояние I/O различных соединений, что требует пула соединений.
НЕ УСТАНОВЛЯТЬ корутину, только пользовательские процессы могут использовать корутины.
Поддерживает разработчиков в создании специальных рабочих процессов для мониторинга, отчетности или других специальных задач. См. addProcess.
Создайте класс процесса, реализуйте интерфейс 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
}
}
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
],
],
Note: The callback() method should not terminate. If it terminates, the controlling process will restart the process.
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());
}
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.
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
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(),
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.
Требуется расширение APCu >= 5.0.0, пожалуйста установите его командой pecl install apcu
.
Скопируйте файл конфигурации prometheus.php
в директорию config
вашего проекта. Измените конфигурацию по необходимости.
# Выполните команды в корневой директории проекта
cp vendor/hhxsv5/laravel-s/config/prometheus.php config/
Если ваш проект это Lumen
, вам также потребуется вручную загрузить конфигурацию $app->configure('prometheus');
в bootstrap/app.php
.
Настройте глобальное средство Hhxsv5\LaravelS\Component\Prometheus\RequestMiddleware::class
. Чтобы как можно более точно считать затраты времени на запрос, RequestMiddleware
должен быть первым глобальным средством, которое следует расположить перед другими средствами.
Зарегистрируйте ServiceProvider: Hhxsv5\LaravelS\Component\Prometheus\ServiceProvider::class
.
Настройте processes
в config/laravels.php
для сбора метрик процессов Swoole Worker/Task/Timer регулярно.
'processes' => Hhxsv5\LaravelS\Component\Prometheus\CollectorProcess::getDescription(),
Создайте маршрут для вывода метрик.
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]);
});
Завершите настройку Прометеуса и запустите его.
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
Запустите Grafana, затем импортируйте panel json.
Поддерживаемые события:
Событие | Интерфейс | Когда происходит |
---|---|---|
СерверНачало | 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],
],
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, запрос начался=>создание синглтона=>запрос закончился=>синглтон не уничтожается. Поэтому разработчику требуется поддерживать состояние каждого синглтона в каждом запросе.
Общие решения:
Создайте класс XxxCleaner
для очистки состояния объекта-одиночки. Этот класс реализует интерфейс Hhxsv5\LaravelS\Illuminate\Cleaners\CleanerInterface
и затем регистрируется в cleaners
файла laravels.php
.
Обновите состояние объектов-одиночек с помощью Middleware
.
Переопределите 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');
}
Устойчивое соединение одиночки
будет находиться в оперативной памяти, рекомендуется включить устойчивое соединение
для лучшей производительности.
будет
автоматически переподключаться сразу после отключения.// 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,
],
],
],
не будет
автоматически переподключаться сразу после отключения, а выбросит исключение о потере соединения, переподключится следующий раз. Вам следует убедиться, что 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');
}
Методы обнаружения утечек памяти
Измените 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
— последняя гарантия.
Настройка параметров ядра Linux### Пресс-тестирование Пресс-тестирование
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )