laravel-s / laravel-sw
Requires
- php: >=5.5.9
- ext-curl: *
- ext-json: *
- ext-pcntl: *
- swoole/ide-helper: @dev
- symfony/console: >=2.7.0
Requires (Dev)
- phpunit/phpunit: >=4.8.36
Suggests
- ext-inotify: Inotify, used to real-time reload.
- ext-swoole: Coroutine-based concurrency library for PHP, require >= 1.7.19.
- dev-master
- v3.7.21
- v3.7.20
- v3.7.19
- v3.7.18
- v3.7.17
- v3.7.16
- v3.7.15
- v3.7.14
- v3.7.13
- v3.7.12
- v3.7.11
- v3.7.10
- v3.7.9
- v3.7.8
- v3.7.7
- v3.7.6
- v3.7.5
- v3.7.4
- v3.7.3
- v3.7.2
- v3.7.1
- v3.7.0
- v3.6.4
- v3.6.3
- v3.6.2
- v3.6.1
- v3.6.0
- v3.5.17
- v3.5.16
- v3.5.15
- v3.5.14
- v3.5.13
- v3.5.12
- v3.5.11
- v3.5.10
- v3.5.9
- v3.5.8
- v3.5.7
- v3.5.6
- v3.5.5
- v3.5.4
- v3.5.3
- v3.5.2
- v3.5.1
- v3.5.0
- v3.4.4
- v3.4.3
- v3.4.2
- v3.4.1
- v3.4.0
- v3.3.10
- v3.3.9
- v3.3.8
- v3.3.7
- v3.3.6
- v3.3.5
- v3.3.4
- v3.3.3
- v3.3.2
- v3.3.1
- v3.3.0
- v3.2.1
- v3.2.0
- v3.1.3
- v3.1.2
- v3.1.1
- v3.1.0
- v3.0.7
- v3.0.6
- v3.0.5
- v3.0.4
- v3.0.3
- v3.0.2
- v3.0.1
- v3.0.0
- v2.0.7
- v2.0.6
- v2.0.5
- v2.0.4
- v2.0.3
- v2.0.2
- v2.0.1
- v2.0.0
- v2.0.0-Alpha
- v1.11.3
- v1.11.2
- v1.11.1
- v1.11.0
- v1.10.19
- v1.10.18
- v1.10.17
- v1.10.16
- v1.10.15
- v1.10.14
- v1.10.13
- v1.10.12
- v1.10.11
- v1.10.10
- v1.10.9
- v1.10.8
- v1.10.7
- v1.10.6
- v1.10.5
- v1.10.3
- v1.10.2
- v1.10.1
- v1.10.0
- v1.9.17
- v1.9.16
- v1.9.15
- v1.9.14
- v1.9.13
- v1.9.12
- v1.9.11
- v1.9.10
- v1.9.9
- v1.9.8
- v1.9.7
- v1.9.6
- v1.9.5
- v1.9.4
- v1.9.2
- v1.9.1
- v1.9.0
- v1.8.14
- v1.8.13
- v1.8.12
- v1.8.11
- v1.8.10
- v1.8.9
- v1.8.8
- v1.8.7
- v1.8.6
- v1.8.5
- v1.8.4
- v1.8.3
- v1.8.2
- v1.8.1
- v1.8.0
- v1.7.14
- v1.7.13
- v1.7.12
- v1.7.11
- v1.7.10
- v1.7.9
- v1.7.8
- v1.7.7
- v1.7.6
- v1.7.5
- v1.7.4
- v1.7.3
- v1.7.1
- v1.7.0
- v1.6.13
- v1.6.12
- v1.6.11
- v1.6.10
- v1.6.9
- v1.6.8
- v1.6.7
- v1.6.6
- v1.6.5
- v1.6.4
- v1.6.3
- v1.6.2
- v1.6.1
- v1.6.0
- v1.5.3
- v1.5.2
- v1.5.1
- v1.5.0
- v1.4.3
- v1.4.2
- v1.4.1
- v1.4.0
- v1.3.6
- v1.3.5
- v1.3.4
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.2
- v1.1.11
- v1.1.10
- v1.1.9
- v1.1.8
- v1.1.7
- v1.1.6
- v1.1.5
- v1.1.4
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.7
- v1.0.6
- v1.0.5
- v1.0.4
- v1.0.3
- v1.0.1
- v1.0.0
- dev-feature/dd
- dev-feature/connection_pool
- dev-feature/full_support_coroutine
- dev-feature/coroutine_clients
This package is not auto-updated.
Last update: 2024-10-02 02:02:17 UTC
README
_ _ _____
| | | |/ ____|
| | __ _ _ __ __ ___ _____| | (___
| | / _` | '__/ _` \ \ / / _ \ |\___ \
| |___| (_| | | | (_| |\ V / __/ |____) |
|______\__,_|_| \__,_| \_/ \___|_|_____/
🚀 LaravelS 是
一个开箱即用的适配器,用于 Swoole 和 Laravel/Lumen 之间。
请 关注 此存储库以获取最新更新。
目录
- 功能
- 基准测试
- 要求
- 安装
- 运行
- 部署
- 与 Nginx 协作(推荐)
- 与 Apache 协作
- 启用 WebSocket 服务器
- 监听事件
- 异步任务队列
- 毫秒级 cron 作业
- 修改代码后自动重新加载
- 在项目中获取 SwooleServer 实例
- 使用 SwooleTable
- 多端口混合协议
- 协程
- 自定义进程
- 常用组件
- 其他功能
- 重要通知
- 用户和案例
- 替代方案
- 赞助商
- 许可证
功能
内置 Http/WebSocket 服务器
内存驻留
优雅地重新加载
支持 Laravel/Lumen,具有良好的兼容性
简单且开箱即用
基准测试
要求
| 依赖项 | 要求 |
|---|---|
| PHP | >= 5.5.9 推荐 PHP7+ |
| Swoole | >= 1.7.19 自 2.0.12 版本起不再支持 PHP5 推荐 4.5.0+ |
| Laravel/Lumen | >= 5.1 推荐 8.0+ |
安装
1. 通过 Composer(packagist) 安装包。
composer require "hhxsv5/laravel-s:~3.7.0" -vvv
# Make sure that your composer.lock file is under the VCS
2. 注册服务提供者(选择以下两个之一)。
Laravel: 在config/app.php文件中,Laravel 5.5+ 支持自动发现包,因此您应该跳过此步骤'providers' => [ //... Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class, ],Lumen: 在bootstrap/app.php文件中$app->register(Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class);
3. 发布配置和二进制文件。
LaravelS 升级后,您需要重新发布;点击 此处 查看每个版本的变更说明。
php artisan laravels publish
# Configuration: config/laravels.php
# Binary: bin/laravels bin/fswatch bin/inotify
5. 修改 config/laravels.php:listen_ip,listen_port,参考 设置。
5. 性能调整
工作进程数量:LaravelS 使用 Swoole 的
同步 IO模式,worker_num设置越大,并发性能越好,但会导致更多内存使用和进程切换开销。如果每个请求需要 100ms,为了提供 1000QPS 并发,至少需要配置 100 个 Worker 进程。计算方法为:worker_num = 1000QPS/(1s/1ms) = 100,因此需要增量压力测试以计算最佳的worker_num。
运行
在运行之前请仔细阅读通知,重要通知(重要)。
- 命令:
php bin/laravels {start|stop|restart|reload|info|help}。
| 命令 | 描述 |
|---|---|
| start | 启动 LaravelS,通过 "ps -ef|grep laravels" 列出进程 |
| stop | 停止 LaravelS,并触发自定义进程的 onStop 方法 |
| restart | 重启 LaravelS:在启动之前优雅地停止;服务将在启动完成之前不可用 |
| reload | 重新加载所有包含您的业务代码的任务/工作进程/定时器进程,并触发自定义进程的 onReload 方法,不能重新加载主/管理进程。修改 config/laravels.php 后,您只需调用 restart 来重启。 |
| 信息 | 显示组件版本信息 |
| 帮助 | 显示帮助信息 |
start和restart命令的启动选项。
| 选项 | 描述 |
|---|---|
| -d|--daemonize | 以守护进程运行,此选项将覆盖 laravels.php 中的 swoole.daemonize 设置。 |
| -e|--env | 命令应运行的环境,例如 --env=testing 将首先使用 .env.testing 配置文件,此功能需要 Laravel 5.2+。 |
| -i|--ignore | 忽略检查主进程的 PID 文件。 |
| -x|--x-version | 当前项目的版本(分支),存储在 $_ENV/$_SERVER 中,通过 $_ENV['X_VERSION']、$_SERVER['X_VERSION'] 或 $request->server->get('X_VERSION') 访问。 |
Runtime文件:start将自动执行php artisan laravels config并生成这些文件,开发者通常不需要关注它们,建议将它们添加到.gitignore。
| 文件 | 描述 |
|---|---|
| storage/laravels.conf | LaravelS 的 runtime 配置文件 |
| 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 {
# Connect IP:Port
server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;
# Connect UnixSocket Stream file, tips: put the socket file in the /dev/shm directory to get better performance
#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;
# Don't forget to bind the 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 handles the static resources(recommend enabling gzip), LaravelS handles the dynamic resource.
location / {
try_files $uri @laravels;
}
# Response 404 directly when request the PHP file, to avoid exposing 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" is the upstream
proxy_pass http://swoole;
}
}
与 Apache 协作
LoadModule proxy_module /yourpath/modules/mod_proxy.so
LoadModule proxy_balancer_module /yourpath/modules/mod_proxy_balancer.so
LoadModule lbmethod_byrequests_module /yourpath/modules/mod_lbmethod_byrequests.so
LoadModule proxy_http_module /yourpath/modules/mod_proxy_http.so
LoadModule slotmem_shm_module /yourpath/modules/mod_slotmem_shm.so
LoadModule rewrite_module /yourpath/modules/mod_rewrite.so
LoadModule remoteip_module /yourpath/modules/mod_remoteip.so
LoadModule deflate_module /yourpath/modules/mod_deflate.so
<IfModule deflate_module>
SetOutputFilter DEFLATE
DeflateCompressionLevel 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>
# Don't forget to bind the host
ServerName www.laravels.com
ServerAdmin hhxsv5@sina.com
DocumentRoot /yourpath/laravel-s-test/public;
DirectoryIndex 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 handles the static resources, LaravelS handles the dynamic resource.
RewriteEngine On
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 combined
</VirtualHost>
启用 WebSocket 服务器
WebSocket 服务器监听地址与 Http 服务器相同。
1. 创建 WebSocket 处理器类,并实现 WebSocketHandlerInterface 接口。实例将在启动时自动实例化,您不需要手动创建它。
namespace App\Services;
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
/**
* @see https://www.swoole.co.uk/docs/modules/swoole-websocket-server
*/
class WebSocketService implements WebSocketHandlerInterface
{
// Declare constructor without parameters
public function __construct()
{
}
// public function onHandShake(Request $request, Response $response)
// {
// Custom handshake: https://www.swoole.co.uk/docs/modules/swoole-websocket-server-on-handshake
// The onOpen event will be triggered automatically after a successful handshake
// }
public function onOpen(Server $server, Request $request)
{
// Before the onOpen event is triggered, the HTTP request to establish the WebSocket has passed the Laravel route,
// so Laravel's Request, Auth information are readable, Session is readable and writable, but only in the onOpen event.
// \Log::info('New WebSocket connection', [$request->fd, request()->all(), session()->getId(), session('xxx'), session(['yyy' => time()])]);
// The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
$server->push($request->fd, 'Welcome to LaravelS');
}
public function onMessage(Server $server, Frame $frame)
{
// \Log::info('Received message', [$frame->fd, $frame->data, $frame->opcode, $frame->finish]);
// The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
$server->push($frame->fd, date('Y-m-d H:i:s'));
}
public function onClose(Server $server, $fd, $reactorId)
{
// The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
}
}
2. 修改 config/laravels.php。
// ...
'websocket' => [
'enable' => true, // Note: set enable to true
'handler' => \App\Services\WebSocketService::class,
],
'swoole' => [
//...
// Must set dispatch_mode in (2, 4, 5), see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
'dispatch_mode' => 2,
//...
],
// ...
3. 使用 SwooleTable 绑定 FD &UserId,可选,Swoole Table 示例。您还可以使用其他全局存储服务,如 Redis/Memcached/MySQL,但请注意,多个 Swoole 服务器 之间的 FD 可能会发生冲突。
4. 与 Nginx 协作(推荐)
参考 WebSocket 代理
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream swoole {
# Connect IP:Port
server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;
# Connect UnixSocket Stream file, tips: put the socket file in the /dev/shm directory to get better performance
#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;
# Don't forget to bind the 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 handles the static resources(recommend enabling gzip), LaravelS handles the dynamic resource.
location / {
try_files $uri @laravels;
}
# Response 404 directly when request the PHP file, to avoid exposing public/*.php
#location ~* \.php$ {
# return 404;
#}
# Http and WebSocket are concomitant, Nginx identifies them by "location"
# !!! The location of WebSocket is "/ws"
# Javascript: var ws = new WebSocket("ws://laravels.com/ws");
location =/ws {
# proxy_connect_timeout 60s;
# proxy_send_timeout 60s;
# proxy_read_timeout: Nginx will close the connection if the proxied server does not send data to Nginx in 60 seconds; At the same time, this close behavior is also affected by heartbeat setting of 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. 心跳设置
Swoole 的心跳设置
// config/laravels.php 'swoole' => [ //... // All connections are traversed every 60 seconds. If a connection does not send any data to the server within 600 seconds, the connection will be forced to close. 'heartbeat_idle_time' => 600, 'heartbeat_check_interval' => 60, //... ],Nginx 的代理读取超时
# Nginx will close the connection if the proxied server does not send data to Nginx in 60 seconds proxy_read_timeout 60s;
6. 控制器中的推送数据
namespace App\Http\Controllers;
class TestController extends Controller
{
public function push()
{
$fd = 1; // Find fd by userId from a map [userId=>fd].
/**@var \Swoole\WebSocket\Server $swoole */
$swoole = app('swoole');
$success = $swoole->push($fd, 'Push data to fd#1 in Controller');
var_dump($success);
}
}
监听事件
系统事件
通常,您可以重置/销毁一些
global/static变量,或更改当前的Request/Response对象。
laravels.received_request在 LaravelS 将Swoole\Http\Request解析为Illuminate\Http\Request之后,在 Laravel 的 Kernel 处理此请求之前。// Edit file `app/Providers/EventServiceProvider.php`, add the following code into method `boot` // If no variable $events, you can also call Facade \Event::listen(). $events->listen('laravels.received_request', function (\Illuminate\Http\Request $req, $app) { $req->query->set('get_key', 'hhxsv5');// Change query of request $req->request->set('post_key', 'hhxsv5'); // Change post of request });laravels.generated_response在 Laravel 的 Kernel 处理请求之后,在 LaravelS 将Illuminate\Http\Response解析为Swoole\Http\Response之前。// Edit file `app/Providers/EventServiceProvider.php`, add the following code into method `boot` // If no variable $events, you can also call 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');// Change header of response });
自定义异步事件
此功能依赖于
Swoole的AsyncTask,您需要首先在config/laravels.php中设置swoole.task_worker_num。异步事件处理的性能受 Swoole 任务进程数量的影响,您需要适当设置 task_worker_num。
1. 创建事件类。
use Hhxsv5\LaravelS\Swoole\Task\Event;
class TestEvent extends Event
{
protected $listeners = [
// Listener list
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\Task;
use Hhxsv5\LaravelS\Swoole\Task\Listener;
class TestListener1 extends Listener
{
/**
* @var TestEvent
*/
protected $event;
public function handle()
{
\Log::info(__CLASS__ . ':handle start', [$this->event->getData()]);
sleep(2);// Simulate the slow codes
// Deliver task in CronJob, but NOT support callback finish() of task.
// Note: Modify 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 exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
}
}
3. 触发事件。
// Create instance of event and fire it, "fire" is asynchronous.
use Hhxsv5\LaravelS\Swoole\Task\Event;
$event = new TestEvent('event data');
// $event->delay(10); // Delay 10 seconds to fire event
// $event->setTries(3); // When an error occurs, try 3 times in total
$success = Event::fire($event);
var_dump($success);// Return true if sucess, otherwise false
异步任务队列
此功能依赖于
Swoole的AsyncTask,您需要首先在config/laravels.php中设置swoole.task_worker_num。任务处理的性能受 Swoole 任务进程数量的影响,您需要适当设置 task_worker_num。
1. 创建任务类。
use Hhxsv5\LaravelS\Swoole\Task\Task;
class TestTask extends Task
{
private $data;
private $result;
public function __construct($data)
{
$this->data = $data;
}
// The logic of task handling, run in task process, CAN NOT deliver task
public function handle()
{
\Log::info(__CLASS__ . ':handle start', [$this->data]);
sleep(2);// Simulate the slow codes
// The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
$this->result = 'the result of ' . $this->data;
}
// Optional, finish event, the logic of after task handling, run in worker process, CAN deliver task
public function finish()
{
\Log::info(__CLASS__ . ':finish start', [$this->result]);
Task::deliver(new TestTask2('task2 data')); // Deliver the other task
}
}
2. 交付任务。
// Create instance of TestTask and deliver it, "deliver" is asynchronous.
use Hhxsv5\LaravelS\Swoole\Task\Task;
$task = new TestTask('task data');
// $task->delay(3);// delay 3 seconds to deliver task
// $task->setTries(3); // When an error occurs, try 3 times in total
$ret = Task::deliver($task);
var_dump($ret);// Return true if sucess, otherwise false
毫秒级 cron 作业
基于 Swoole 的毫秒级定时器 的 cron 作业包装,替换
Linux的Crontab。
1. 创建 cron 作业类。
namespace App\Jobs\Timer;
use App\Tasks\TestTask;
use Swoole\Coroutine;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Hhxsv5\LaravelS\Swoole\Timer\CronJob;
class TestCronJob extends CronJob
{
protected $i = 0;
// !!! The `interval` and `isImmediate` of cron job can be configured in two ways(pick one of two): one is to overload the corresponding method, and the other is to pass parameters when registering cron job.
// --- Override the corresponding method to return the configuration: begin
public function interval()
{
return 1000;// Run every 1000ms
}
public function isImmediate()
{
return false;// Whether to trigger `run` immediately after setting up
}
// --- Override the corresponding method to return the configuration: end
public function run()
{
\Log::info(__METHOD__, ['start', $this->i, microtime(true)]);
// do something
// sleep(1); // Swoole < 2.1
Coroutine::sleep(1); // Swoole>=2.1 Coroutine will be automatically created for run().
$this->i++;
\Log::info(__METHOD__, ['end', $this->i, microtime(true)]);
if ($this->i >= 10) { // Run 10 times only
\Log::info(__METHOD__, ['stop', $this->i, microtime(true)]);
$this->stop(); // Stop this cron job, but it will run again after restart/reload.
// Deliver task in CronJob, but NOT support callback finish() of task.
// Note: Modify 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 exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
}
}
2. 注册定时任务。
// Register cron jobs in file "config/laravels.php"
[
// ...
'timer' => [
'enable' => true, // Enable Timer
'jobs' => [ // The list of cron job
// Enable LaravelScheduleJob to run `php artisan schedule:run` every 1 minute, replace Linux Crontab
// \Hhxsv5\LaravelS\Illuminate\LaravelScheduleJob::class,
// Two ways to configure parameters:
// [\App\Jobs\Timer\TestCronJob::class, [1000, true]], // Pass in parameters when registering
\App\Jobs\Timer\TestCronJob::class, // Override the corresponding method to return the configuration
],
'max_wait_time' => 5, // Max waiting time of reloading
// Enable the global lock to ensure that only one instance starts the timer when deploying multiple instances. This feature depends on Redis, please see https://laravel.net.cn/docs/7.x/redis
'global_lock' => false,
'global_lock_key' => config('app.name', 'Laravel'),
],
// ...
];
3. 注意:在构建服务器集群时会启动多个定时器,因此请确保只启动一个定时器以避免重复运行任务。
4. LaravelS v3.4.0 开始支持热重启 [重新加载] Timer 进程。当 LaravelS 收到 SIGUSR1 信号后,它会等待 max_wait_time(默认 5 秒)结束进程,然后 Manager 进程会再次拉起 Timer 进程。
5. 如果您只需要使用 分钟级 的计划任务,建议启用 Hhxsv5\LaravelS\Illuminate\LaravelScheduleJob 而不是 Linux Crontab,这样您就可以遵循 Laravel 任务调度的编码习惯并配置 Kernel。
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
// runInBackground() will start a new child process to execute the task. This is asynchronous and will not affect the execution timing of other tasks.
$schedule->command(TestCommand::class)->runInBackground()->everyMinute();
}
修改代码后自动重新加载
通过
inotify支持 Linux 系统。1. 安装 inotify 扩展。
2. 在 设置 中开启开关。
3. 注意:仅在
Linux系统中修改文件以接收文件更改事件。建议使用最新的 Docker。 Vagrant 解决方案。通过
fswatch支持 OS X/Linux/Windows 系统。1. 安装 fswatch。
2. 在您的项目根目录中运行命令。
# Watch current directory ./bin/fswatch # Watch app directory ./bin/fswatch ./app通过
inotifywait支持 Linux 系统。1. 安装 inotify-tools。
2. 在您的项目根目录中运行命令。
# Watch current directory ./bin/inotify # Watch app directory ./bin/inotify ./app当上述方法无效时,最后的解决方案是设置
max_request=1,worker_num=1,这样Worker进程在处理完一个请求后将会重启。这种方法性能非常差,因此仅在开发环境中使用。
在您的项目中获取 SwooleServer 实例。
/**
* $swoole is the instance of `Swoole\WebSocket\Server` if enable WebSocket server, otherwise `Swoole\Http\Server`
* @var \Swoole\WebSocket\Server|\Swoole\Http\Server $swoole
*/
$swoole = app('swoole');
var_dump($swoole->stats());
$swoole->push($fd, 'Push WebSocket message');
使用 SwooleTable。
1. 定义 Table,支持多个。
所有定义的 Table 都会在 Swoole 启动前创建。
// in file "config/laravels.php"
[
// ...
'swoole_tables' => [
// Scene:bind UserId & FD in WebSocket
'ws' => [// The Key is table name, will add suffix "Table" to avoid naming conflicts. Here defined a table named "wsTable"
'size' => 102400,// The max size
'column' => [// Define the columns
['name' => 'value', 'type' => \Swoole\Table::TYPE_INT, 'size' => 8],
],
],
//...Define the other tables
],
// ...
];
2. 访问 Table:所有 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;
}
// Scene:bind UserId & FD in WebSocket
public function onOpen(Server $server, Request $request)
{
// var_dump(app('swoole') === $server);// The same instance
/**
* Get the currently logged in user
* This feature requires that the path to establish a WebSocket connection go through middleware such as Authenticate.
* E.g:
* Browser side: var ws = new WebSocket("ws://127.0.0.1:5200/ws");
* Then the /ws route in Laravel needs to add the middleware like Authenticate.
* Route::get('/ws', function () {
* // Respond any content with status code 200
* return 'websocket';
* })->middleware(['auth']);
*/
// $user = Auth::user();
// $userId = $user ? $user->id : 0; // 0 means a guest user who is not logged in
$userId = mt_rand(1000, 10000);
// if (!$userId) {
// // Disconnect the connections of unlogged users
// $server->disconnect($request->fd);
// return;
// }
$this->wsTable->set('uid:' . $userId, ['value' => $request->fd]);// Bind map uid to fd
$this->wsTable->set('fd:' . $request->fd, ['value' => $userId]);// Bind map fd to uid
$server->push($request->fd, "Welcome to LaravelS #{$request->fd}");
}
public function onMessage(Server $server, Frame $frame)
{
// Broadcast
foreach ($this->wsTable as $key => $row) {
if (strpos($key, 'uid:') === 0 && $server->isEstablished($row['value'])) {
$content = sprintf('Broadcast: new message "%s" from #%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']); // Unbind uid map
}
$this->wsTable->del('fd:' . $fd);// Unbind fd map
$server->push($fd, "Goodbye #{$fd}");
}
}
多端口混合协议
更多信息请参考 Swoole Server AddListener。
为了让我们的主服务器支持更多协议,而不仅仅是 Http 和 WebSocket,我们在 LaravelS 中引入了 Swoole 的 多端口混合协议 功能,并将其命名为 Socket。现在,您可以在 Laravel 上轻松构建 TCP/UDP 应用程序。
创建
Socket处理器类,并扩展Hhxsv5\LaravelS\Swoole\Socket\{TcpSocket|UdpSocket|Http|WebSocket}。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('New TCP connection', [$fd]); $server->send($fd, 'Welcome to LaravelS.'); } public function onReceive(Server $server, $fd, $reactorId, $data) { \Log::info('Received data', [$fd, $data]); $server->send($fd, 'LaravelS: ' . $data); if ($data === "quit\r\n") { $server->send($fd, 'LaravelS: bye' . PHP_EOL); $server->close($fd); } } public function onClose(Server $server, $fd, $reactorId) { \Log::info('Close TCP connection', [$fd]); $server->send($fd, 'Goodbye'); } }这些
Socket连接与您的HTTP/WebSocket连接共享相同的 Worker 进程。因此,如果您想要发送任务、使用SwooleTable或访问 Laravel 组件(如 DB、Eloquent 等),这根本不是问题。同时,您可以直接通过成员属性swoolePort访问Swoole\Server\Port对象。public function onReceive(Server $server, $fd, $reactorId, $data) { $port = $this->swoolePort; // Get the `Swoole\Server\Port` object }namespace App\Http\Controllers; class TestController extends Controller { public function test() { /**@var \Swoole\Http\Server|\Swoole\WebSocket\Server $swoole */ $swoole = app('swoole'); // $swoole->ports: Traverse all Port objects, https://www.swoole.co.uk/docs/modules/swoole-server/multiple-ports $port = $swoole->ports[0]; // Get the `Swoole\Server\Port` object, $port[0] is the port of the main server foreach ($port->connections as $fd) { // Traverse all connections // $swoole->send($fd, 'Send tcp message'); // if($swoole->isEstablished($fd)) { // $swoole->push($fd, 'Send websocket message'); // } } } }注册 Socket。
// Edit `config/laravels.php` //... 'sockets' => [ [ 'host' => '127.0.0.1', 'port' => 5291, 'type' => SWOOLE_SOCK_TCP,// Socket type: SWOOLE_SOCK_TCP/SWOOLE_SOCK_TCP6/SWOOLE_SOCK_UDP/SWOOLE_SOCK_UDP6/SWOOLE_UNIX_DGRAM/SWOOLE_UNIX_STREAM 'settings' => [// Swoole settings: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, // whether to enable, default true ], ],关于心跳配置,它只能设置在
主服务器上,不能在Socket上配置,但Socket继承了主服务器的心跳配置。对于 TCP Socket,当 Swoole 的
dispatch_mode为1/3时,onConnect和onClose事件将会阻塞,因此如果您想解除这两个事件的阻塞,请将dispatch_mode设置为2/4/5。'swoole' => [ //... 'dispatch_mode' => 2, //... ];测试。
TCP:
telnet 127.0.0.1 5291UDP: [Linux]
echo "Hello LaravelS" > /dev/udp/127.0.0.1/5292
注册其他协议的示例。
UDP
'sockets' => [ [ 'host' => '0.0.0.0', 'port' => 5292, 'type' => SWOOLE_SOCK_UDP, 'settings' => [ 'open_eof_check' => true, 'package_eof' => "\r\n", ], 'handler' => \App\Sockets\TestUdpSocket::class, ], ],Http
'sockets' => [ [ 'host' => '0.0.0.0', 'port' => 5293, 'type' => SWOOLE_SOCK_TCP, 'settings' => [ 'open_http_protocol' => true, ], 'handler' => \App\Sockets\TestHttp::class, ], ],WebSocket: 主服务器必须
开启 WebSocket,即设置websocket.enable为true。'sockets' => [ [ 'host' => '0.0.0.0', 'port' => 5294, 'type' => SWOOLE_SOCK_TCP, 'settings' => [ 'open_http_protocol' => true, 'open_websocket_protocol' => true, ], 'handler' => \App\Sockets\TestWebSocket::class, ], ],
协程
警告:协程中代码执行的顺序错误。请求级别的数据应该通过协程ID进行隔离。然而,Laravel/Lumen 中存在许多单例和静态属性,不同请求之间的数据会相互影响,这是不安全的(
不安全)。例如,数据库连接是一个单例,相同的数据库连接共享相同的 PDO 资源。在同步阻塞模式下这是可以的,但在异步协程模式下则不行。每个查询都需要创建不同的连接并维护不同连接的 IO 状态,这需要一个连接池。不要启用协程,只有自定义进程可以使用协程。
自定义进程
支持开发者创建专门的监控、报告或其他特殊任务的工作进程。请参阅 addProcess。
创建 Process 类,实现 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) { // The callback method cannot exit. Once exited, Manager process will automatically create the process while (!self::$quit) { \Log::info('Test process: running'); // sleep(1); // Swoole < 2.1 Coroutine::sleep(1); // Swoole>=2.1: Coroutine & Runtime will be automatically enabled for callback(). // Deliver task in custom process, but NOT support callback finish() of task. // Note: Modify 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 upper layer will catch the exception thrown in the callback and record it in the Swoole log, and then this process will exit. The Manager process will re-create the process after 3 seconds, so developers need to try/catch to catch the exception by themselves to avoid frequent process creation. // throw new \Exception('an exception'); } } // Requirements: LaravelS >= v3.4.0 & callback() must be async non-blocking program. public static function onReload(Server $swoole, Process $process) { // Stop the process... // Then end process \Log::info('Test process: reloading'); self::$quit = true; // $process->exit(0); // Force exit process } // Requirements: LaravelS >= v3.7.4 & callback() must be async non-blocking program. public static function onStop(Server $swoole, Process $process) { // Stop the process... // Then end process \Log::info('Test process: stopping'); self::$quit = true; // $process->exit(0); // Force exit process } }注册 TestProcess。
// Edit `config/laravels.php` // ... 'processes' => [ 'test' => [ // Key name is process name 'class' => \App\Processes\TestProcess::class, 'redirect' => false, // Whether redirect stdin/stdout, true or false 'pipe' => 0, // The type of pipeline, 0: no pipeline 1: SOCK_STREAM 2: SOCK_DGRAM 'enable' => true, // Whether to enable, default true //'queue' => [ // Enable message queue as inter-process communication, configure empty array means use default parameters // 'msg_key' => 0, // The key of the message queue. Default: ftok(__FILE__, 1). // 'mode' => 2, // Communication mode, default is 2, which means contention mode // 'capacity' => 8192, // The length of a single message, is limited by the operating system kernel parameters. The default is 8192, and the maximum is 65536 //], //'restart_interval' => 5, // After the process exits abnormally, how many seconds to wait before restarting the process, default 5 seconds ], ],注意:callback() 不能退出。如果退出,Manager 进程将重新创建进程。
示例:向自定义进程写入数据。
// 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: read data', [$data]); $process->write('TestProcess: ' . $data); } }// app/Http/Controllers/TestController.php public function testProcessWrite() { /**@var \Swoole\Process $process */ $process = app('swoole')->customProcesses['test']; $process->write('TestController: write data' . time()); var_dump($process->read()); }
常用组件
Apollo
LaravelS在启动时将拉取Apollo配置并将其写入.env文件。同时,LaravelS将启动自定义进程apollo以监控配置,并在配置更改时自动重新加载。
启用 Apollo:将
--enable-apollo和 Apollo 参数添加到启动参数中。php bin/laravels start --enable-apollo --apollo-server=http://127.0.0.1:8080 --apollo-app-id=LARAVEL-S-TEST支持热更新(可选)。
// Edit `config/laravels.php` 'processes' => Hhxsv5\LaravelS\Components\Apollo\Process::getDefinition(),// When there are other custom process configurations 'processes' => [ 'test' => [ 'class' => \App\Processes\TestProcess::class, 'redirect' => false, 'pipe' => 1, ], // ... ] + Hhxsv5\LaravelS\Components\Apollo\Process::getDefinition(),可用参数列表。
| 参数 | 描述 | 默认值 | 示例 |
|---|---|---|---|
| apollo-server | Apollo 服务器 URL | - | --apollo-server=http://127.0.0.1:8080 |
| apollo-app-id | Apollo APP ID | - | --apollo-app-id=LARAVEL-S-TEST |
| apollo-namespaces | APP 所属的命名空间,支持指定多个 | 应用程序 | --apollo-namespaces=application --apollo-namespaces=env |
| apollo-cluster | APP 所属的集群 | 默认值 | --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 |
其他功能
配置 Swoole 事件
支持的事件
| 事件 | 接口 | 何时发生 |
|---|---|---|
| ServerStart | Hhxsv5\LaravelS\Swoole\Events\ServerStartInterface | 在 Master 进程启动时发生,此事件不应处理复杂业务逻辑,只能做一些简单的初始化工作。 |
| ServerStop | Hhxsv5\LaravelS\Swoole\Events\ServerStopInterface | 在服务器正常退出时发生,在此事件中不能使用异步或协程相关的 API。 |
| WorkerStart | Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface | 在 Worker/Task 进程启动后发生,Laravel 初始化已完成。 |
| WorkerStop | Hhxsv5\LaravelS\Swoole\Events\WorkerStopInterface | 在 Worker/Task 进程正常退出后发生 |
| WorkerError | Hhxsv5\LaravelS\Swoole\Events\WorkerErrorInterface | 在 Worker/Task 进程中发生异常或致命错误时发生 |
1. 创建一个事件类以实现相应的接口。
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)
{
// Initialize a global counter (available across processes)
$server->atomicCount = new Atomic(2233);
// Invoked in controller: 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)
{
// Initialize a database connection pool
// DatabaseConnectionPool::init();
}
}
2. 配置。
// Edit `config/laravels.php`
'event_handlers' => [
'ServerStart' => [\App\Events\ServerStartEvent::class], // Trigger events in array order
'WorkerStart' => [\App\Events\WorkerStartEvent::class],
],
无服务器
阿里云函数计算
函数计算.
1. 修改 bootstrap/app.php 并设置存储目录。因为项目目录是只读的,所以 /tmp 目录只能读和写。
$app->useStoragePath(env('APP_STORAGE_PATH', '/tmp/storage'));
2. 创建一个 shell 脚本 laravels_bootstrap 并授予 可执行权限。
#!/usr/bin/env bash
set +e
# Create storage-related directories
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
# Set the environment variable APP_STORAGE_PATH, please make sure it's the same as APP_STORAGE_PATH in .env
export APP_STORAGE_PATH=/tmp/storage
# Start LaravelS
php bin/laravels start
3. 配置 template.xml。
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
laravel-s-demo:
Type: 'Aliyun::Serverless::Service'
Properties:
Description: 'LaravelS Demo for Serverless'
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服务器中,所有单例实例都将保留在内存中,其生命周期与FPM不同,请求开始→实例化实例→请求结束→不回收单例实例。因此,需要开发者在每个请求中维护单例实例的状态。
常见解决方案
编写一个
XxxCleaner类来清理单例对象的状态。这个类实现了接口Hhxsv5\LaravelS\Illuminate\Cleaners\CleanerInterface,然后在laravels.php中的cleaners中注册它。通过
Middleware重置单例实例的状态。重新注册
ServiceProvider,将XxxServiceProvider添加到laravels.php文件的register_providers中。这样,每个请求都会重新初始化单例实例。[参考](https://github.com/hhxsv5/laravel-s/blob/master/Settings.md#register_providers)
清理器
已知问题
已知问题:包含已知问题和解决方案的包。[参考](https://github.com/hhxsv5/laravel-s/blob/master/KnownIssues.md)
调试方法
日志记录;如果您想输出到控制台,可以使用
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');
// Call getContent() to get the raw POST body, instead of 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' => time()])->header('header1', 'value1')->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' => [
// Enable persistent connection
\PDO::ATTR_PERSISTENT => true,
],
],
],
- Redis连接,它将在断开连接后立即自动重新连接,并抛出关于丢失连接的异常,下次重新连接。您需要在每次操作Redis之前确保正确执行
SELECT DB。
// config/database.php
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'), // It is recommended to use phpredis for better performance.
'default' => [
'host' => env('REDIS_HOST', 'localhost'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
'persistent' => true, // Enable persistent connection
],
],
关于内存泄漏
避免使用全局变量。如果需要,请手动清理或重置它们。
无限地向
static/global变量中添加元素会导致OOM(内存不足)。class Test { public static $array = []; public static $string = ''; } // Controller 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,记得测试后改回;添加没有
route middleware的路由/debug-memory-leak,以观察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始终大于零,则表示在Global Middleware或Laravel Framework中可能存在内存泄漏;完成
第3步后,交替请求业务路由和/debug-memory-leak(建议使用ab/wrk对业务路由进行大量请求),内存的初始增加是正常的。在对业务路由进行大量请求后,如果diff_mem始终大于零且curr_mem持续增加,则很可能存在内存泄漏;如果curr_mem始终在一定范围内变化而不继续增加,则内存泄漏的可能性较低。如果您仍然无法解决问题,max_request是最后的保证。