glzjin / laravel-s
🚀 LaravelS: 使用 Swoole 加速 Laravel/Lumen,'S' 代表 Swoole、速度、高性能。
Requires
- php: >=5.5.9
Suggests
- ext-inotify: Inotify, used to reload all worker processes when your code is modified.
- ext-swoole: Event-driven asynchronous & concurrent & coroutine networking engine with high performance for PHP, require >= 1.7.19.
- dev-master
- v1.10.13
- v1.10.12
- v1.10.10
- v1.10.9.2
- v1.10.9.1
- v1.10.9
- v1.10.8
- v1.10.7
- v1.10.6
- v1.10.5
- v1.10.4
- 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.3
- 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.2
- 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.2.1
- v1.2.0
- 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.3
- 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.2
- v1.0.1
- v1.0.0
- dev-patch-2
- dev-patch-1
- dev-feature/coroutine_clients
- dev-feature/connection_pool
This package is not auto-updated.
Last update: 2024-09-19 04:21:19 UTC
README
🚀 使用 Swoole 加速 Laravel/Lumen,'S' 代表 Swoole、速度、高性能。
特性
-
高性能 Swoole
-
内置 Http/WebSocket 服务器
-
内存常驻
-
优雅重启
-
代码修改时自动重启
-
支持 Laravel/Lumen,良好兼容性
-
简单易用
要求
安装
# Run in the root path of your Laravel/Lumen project. composer require "hhxsv5/laravel-s:~1.0" -vvv # Make sure that your composer.lock file is under the VCS
2. 添加服务提供者。
Laravel
: 在config/app.php
文件中
'providers' => [ //... Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class, ],
Lumen
: 在bootstrap/app.php
文件中
$app->register(Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class);
3. 发布配置。
建议每次升级 LaravelS 后都进行发布
php artisan laravels publish
Lumen 特殊说明
: 您无需在 bootstrap/app.php
文件中手动加载此配置,LaravelS 会自动加载。
// Unnecessary to call configure() $app->configure('laravels');
4. 修改 config/laravels.php
:监听 IP、监听端口,参考 设置。
运行演示
php artisan laravels {start|stop|restart|reload|publish}
与 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 laravels { server 192.168.0.1:5200 weight=5 max_fails=3 fail_timeout=30s; #server 192.168.0.2:5200 weight=3 max_fails=3 fail_timeout=30s; #server 192.168.0.3:5200 backup; } server { listen 80; server_name laravels.com; root /xxxpath/laravel-s-test/public; access_log /yyypath/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; } location @laravels { proxy_http_version 1.1; # proxy_connect_timeout 60s; # proxy_send_timeout 60s; # proxy_read_timeout 120s; proxy_set_header Connection "keep-alive"; 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 $host; proxy_pass http://laravels; } }
与 Apache 合作
LoadModule proxy_module /yyypath/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> ServerName www.laravels.com ServerAdmin hhxsv5@sina.com DocumentRoot /xxxpath/laravel-s-test/public; DirectoryIndex index.html index.htm <Directory "/"> AllowOverride None Require all granted </Directory> LoadModule proxy_module /yyypath/modules/mod_proxy.so LoadModule proxy_module /yyypath/modules/mod_proxy_balancer.so LoadModule proxy_module /yyypath/modules/mod_lbmethod_byrequests.so.so LoadModule proxy_module /yyypath/modules/mod_proxy_http.so.so LoadModule proxy_module /yyypath/modules/mod_slotmem_shm.so LoadModule proxy_module /yyypath/modules/mod_rewrite.so ProxyRequests Off ProxyPreserveHost On <Proxy balancer://laravels> BalancerMember http://192.168.1.1:8011 loadfactor=7 #BalancerMember http://192.168.1.2:8011 loadfactor=3 #BalancerMember http://192.168.1.3:8011 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; /** * @see https://www.swoole.co.uk/docs/modules/swoole-websocket-server */ class WebsocketService implements WebsocketHandlerInterface { // Declare constructor without parameters public function __construct() { } public function onOpen(\swoole_websocket_server $server, \swoole_http_request $request) { \Log::info('New Websocket connection', [$request->fd]); $server->push($request->fd, 'Welcome to LaravelS'); // throw new \Exception('an exception');// all exceptions will be ignored, then record them into Swoole log, you need to try/catch them } public function onMessage(\swoole_websocket_server $server, \swoole_websocket_frame $frame) { \Log::info('Received message', [$frame->fd, $frame->data, $frame->opcode, $frame->finish]); $server->push($frame->fd, date('Y-m-d H:i:s')); // throw new \Exception('an exception');// all exceptions will be ignored, then record them into Swoole log, you need to try/catch them } public function onClose(\swoole_websocket_server $server, $fd, $reactorId) { // throw new \Exception('an exception');// all exceptions will be ignored, then record them into Swoole log, you need to try/catch them } }
2. 修改 config/laravels.php
。
// ... 'websocket' => [ 'enable' => 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. 使用 swoole_table
绑定 FD & UserId,可选,参考 Swoole Table 示例。您还可以使用其他全局存储服务,如 Redis/Memcached/MySQL,但请注意,多个 Swoole 服务器
之间 FD 可能会发生冲突。
4. 与 Nginx 合作(推荐)
参考 WebSocket 代理
map $http_upgrade $connection_upgrade { default upgrade; '' close; } upstream laravels-ws { server 192.168.0.1:5200 weight=5 max_fails=3 fail_timeout=30s; #server 192.168.0.2:5200 weight=3 max_fails=3 fail_timeout=30s; #server 192.168.0.3:5200 backup; } server { listen 80; server_name laravels-ws.com; root /xxxpath/laravel-s-test/public; access_log /yyypath/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; } location @laravels { proxy_http_version 1.1; # proxy_connect_timeout 60s; # proxy_send_timeout 60s; # proxy_read_timeout: Nginx will close the connection if client does not send data to server in 60 seconds; At the same time, this close behavior is also affected by heartbeat setting of Swoole # proxy_read_timeout 60s; 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 $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_pass http://laravels-ws; } }
监听事件
系统事件
通常,您可以重置/销毁一些
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 { 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; class TestListener1 extends Listener { // Declare constructor without parameters public function __construct() { } public function handle(Event $event) { \Log::info(__CLASS__ . ':handle start', [$event->getData()]); sleep(2);// Simulate the slow codes // throw new \Exception('an exception');// all exceptions will be ignored, then record them into Swoole log, you need to try/catch them } }
3. 绑定事件 & 监听器。
// Bind event & listeners in file "config/laravels.php", one event => many listeners [ // ... 'events' => [ \App\Tasks\TestEvent::class => [ \App\Tasks\TestListener1::class, //\App\Tasks\TestListener2::class, ], ], // ... ];
4. 触发事件。
// Create instance of event and fire it, "fire" is asynchronous. use Hhxsv5\LaravelS\Swoole\Task\Event; $success = Event::fire(new TestEvent('event data')); 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 // throw new \Exception('an exception');// all exceptions will be ignored, then record them into Swoole log, you need to try/catch them $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 $ret = Task::deliver($task); var_dump($ret);// Return true if sucess, otherwise false
毫秒级定时任务
基于 Swoole 的毫秒级定时器 的包装器 cron 作业,替代
Linux
的Crontab
。
1. 创建 cron 作业类。
namespace App\Jobs\Timer; use Hhxsv5\LaravelS\Swoole\Timer\CronJob; class TestCronJob extends CronJob { protected $i = 0; // Declare constructor without parameters public function __construct() { } public function interval() { return 1000;// Run every 1000ms } public function run() { \Log::info(__METHOD__, ['start', $this->i, microtime(true)]); // do something $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 } // throw new \Exception('an exception');// all exceptions will be ignored, then record them into Swoole log, you need to try/catch them } }
2. 绑定 cron 作业。
// Bind 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, \App\Jobs\Timer\TestCronJob::class, ], ], // ... ];
在您的项目中获取 swoole_server
的实例
/** * $swoole is the instance of `swoole_websocket_server` if enable websocket server, otherwise `\swoole_http_server` * @var \swoole_http_server|\swoole_websocket_server $swoole */ $swoole = app('swoole'); var_dump($swoole->stats());// Singleton
使用 swoole_table
1. 定义 swoole_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 conficts. 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. 访问 swoole_table
:所有表实例都将绑定到 swoole_server
,通过 app('swoole')->xxxTable
访问。
// Scene:bind UserId & FD in WebSocket public function onOpen(\swoole_websocket_server $server, \swoole_http_request $request) { // var_dump(app('swoole') === $server);// The same instance $userId = mt_rand(1000, 10000); app('swoole')->wsTable->set('uid:' . $userId, ['value' => $request->fd]);// Bind map uid to fd app('swoole')->wsTable->set('fd:' . $request->fd, ['value' => $userId]);// Bind map fd to uid $server->push($request->fd, 'Welcome to LaravelS'); } public function onMessage(\swoole_websocket_server $server, \swoole_websocket_frame $frame) { foreach (app('swoole')->wsTable as $key => $row) { if (strpos($key, 'uid:') === 0) { $server->push($row['value'], 'Broadcast: ' . date('Y-m-d H:i:s'));// Broadcast } } } public function onClose(\swoole_websocket_server $server, $fd, $reactorId) { $uid = app('swoole')->wsTable->get('fd:' . $fd); if ($uid !== false) { app('swoole')->wsTable->del('uid:' . $uid['value']); // Ubind uid map } app('swoole')->wsTable->del('fd:' . $fd);// Unbind fd map $server->push($fd, 'Goodbye'); }
重要提示
- 从
Illuminate\Http\Request
对象获取所有请求信息,兼容 $_SERVER/$_ENV/$_GET/$_POST/$_FILES/$_COOKIE/$_REQUEST,不能使用
$_SESSION。
public function form(\Illuminate\Http\Request $request) { $name = $request->input('name'); $all = $request->all(); $sessionId = $request->cookie('sessionId'); $photo = $request->file('photo'); $rawContent = $request->getContent(); //... }
- 通过
Illuminate\Http\Response
对象响应,兼容 echo/vardump()/print_r(),不能使用
header()/setcookie()/http_response_code() 等函数。
public function json() { return response()->json(['time' => time()])->header('header1', 'value1')->withCookie('c1', 'v1'); }
-
global
、static
变量需要手动销毁(重置)。 -
无限地向
static
/global
变量中添加元素会导致内存泄漏。
// Some class class Test { public static $array = []; public static $string = ''; } // Controller public function test(Request $req) { // Memory leak Test::$array[] = $req->input('param1'); Test::$string .= $req->input('param2'); }
已知的兼容性问题
待办事项列表
-
MySQL/Redis 的连接池。
-
包装 MySQL/Redis/Http 的协程客户端。