This action will force synchronization from Xie Biao/laravel-s, which will overwrite any changes that you have made since you forked the repository, and can not be recovered!!!
Synchronous operation will process in the background and will refresh the page when finishing processing. Please be patient.
_ _ _____
| | | |/ ____|
| | __ _ _ __ __ ___ _____| | (___
| | / _` | '__/ _` \ \ / / _ \ |\___ \
| |___| (_| | | | (_| |\ V / __/ |____) |
|______\__,_|_| \__,_| \_/ \___|_|_____/
🚀
LaravelS
是胶水,用于快速集成Swoole
到Laravel
或Lumen
,然后赋予它们更好的性能、更多可能性。
English Documentation QQ交流群:698480528
内置Http/WebSocket服务器
常驻内存
平滑Reload
修改代码后自动Reload
同时支持Laravel与Lumen,兼容主流版本
简单,开箱即用
依赖 | 说明 |
---|---|
PHP | >= 5.5.9 |
Swoole |
>= 1.7.19 推荐最新的稳定版 从2.0.12开始不再支持PHP5
|
Laravel/Lumen | >= 5.1 |
Gzip[可选的] |
zlib,用于压缩HTTP响应,检查本机libz 是否可用 ldconfig -p|grep libz
|
Inotify[可选的] |
inotify,用于修改代码后自动Reload Worker进程,检查本机inotify 是否可用 php --ri inotify
|
# 在你的Laravel/Lumen项目的根目录下执行
composer require "hhxsv5/laravel-s:~2.0" -vvv
# 确保你的composer.lock文件是在版本控制中
2.注册Service Provider。
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时的特别说明
: 你不需要手动加载配置laravels.php
,LaravelS底层已自动加载。
// 不必手动加载,但加载了也不会有问题
$app->configure('laravels');
4.修改配置config/laravels.php
:监听的IP、端口等,请参考配置项。
php artisan laravels {start|stop|restart|reload|publish}
命令 | 说明 |
---|---|
start |
启动LaravelS,展示已启动的进程列表 ps -ef|grep laravels |
stop |
停止LaravelS |
restart |
重启LaravelS |
reload |
平滑重启所有worker进程,这些worker进程内包含你的业务代码和框架(Laravel/Lumen)代码,不会重启master/manger进程 |
publish |
发布配置文件到你的项目中config/laravels.php
|
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 {
# By IP:Port
server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;
# By UnixSocket Stream file
#server unix:/xxxpath/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;
}
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处理静态资源(建议开启gzip),LaravelS处理动态资源。
location / {
try_files $uri @laravels;
}
# 当请求PHP文件时直接响应404,防止暴露public/*.php
#location ~* \.php$ {
# return 404;
#}
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 $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://laravels;
}
}
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处理静态资源,LaravelS处理动态资源。
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服务器监听的IP和端口与Http服务器相同。
1.创建WebSocket Handler类,并实现接口WebSocketHandlerInterface
。
namespace App\Services;
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
/**
* @see https://wiki.swoole.com/wiki/page/400.html
*/
class WebSocketService implements WebSocketHandlerInterface
{
// 声明没有参数的构造函数
public function __construct()
{
}
public function onOpen(\swoole_websocket_server $server, \swoole_http_request $request)
{
// 在触发onOpen事件之前Laravel的生命周期已经完结,所以Laravel的Request是可读的,Session是可读写的
\Log::info('New WebSocket connection', [$request->fd, request()->all(), session()->getId(), session('xxx'), session(['yyy' => time()])]);
$server->push($request->fd, 'Welcome to LaravelS');
// throw new \Exception('an exception');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理
}
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');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理
}
public function onClose(\swoole_websocket_server $server, $fd, $reactorId)
{
// throw new \Exception('an exception');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理
}
}
2.更改配置config/laravels.php
。
// ...
'websocket' => [
'enable' => true,
'handler' => \App\Services\WebSocketService::class,
],
'swoole' => [
//...
// dispatch_mode只能设置为2、4、5,https://wiki.swoole.com/wiki/page/277.html
'dispatch_mode' => 2,
//...
],
// ...
3.使用swoole_table
绑定FD与UserId,可选的,Swoole Table示例。也可以用其他全局存储服务,例如Redis/Memcached/MySQL,但需要注意多个Swoole Server
实例时FD可能冲突。
4.与Nginx配合使用(推荐)
参考 WebSocket代理
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream laravels {
# By IP:Port
server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;
# By UnixSocket Stream file
#server unix:/xxxpath/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;
}
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处理静态资源(建议开启gzip),LaravelS处理动态资源。
location / {
try_files $uri @laravels;
}
# 当请求PHP文件时直接响应404,防止暴露public/*.php
#location ~* \.php$ {
# return 404;
#}
# Http和WebSocket共存,Nginx通过location区分
# Javascript: var ws = new WebSocket("ws://laravels.com/ws");
location =/ws {
proxy_http_version 1.1;
# proxy_connect_timeout 60s;
# proxy_send_timeout 60s;
# proxy_read_timeout:如果60秒内客户端没有发数据到服务端,那么Nginx会关闭连接;同时,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 $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://laravels;
}
location @laravels {
proxy_http_version 1.1;
# proxy_connect_timeout 60s;
# proxy_send_timeout 60s;
# 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 $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://laravels;
}
}
通常,你可以在这些事件中重置或销毁一些全局或静态的变量,也可以修改当前的请求和响应。
laravels.received_request
将swoole_http_request
转成Illuminate\Http\Request
后,在Laravel内核处理请求前。// 修改`app/Providers/EventServiceProvider.php`, 添加下面监听代码到boot方法中
// 如果变量$events不存在,你也可以通过Facade调用\Event::listen()。
$events->listen('laravels.received_request', function (\Illuminate\Http\Request $req, $app) {
$req->query->set('get_key', 'hhxsv5');// 修改querystring
$req->request->set('post_key', 'hhxsv5'); // 修改post body
});
laravels.generated_response
在Laravel内核处理完请求后,将Illuminate\Http\Response
转成swoole_http_response
之前(下一步将响应给客户端)。// 修改`app/Providers/EventServiceProvider.php`, 添加下面监听代码到boot方法中
// 如果变量$events不存在,你也可以通过Facade调用\Event::listen()。
$events->listen('laravels.generated_response', function (\Illuminate\Http\Request $req, \Symfony\Component\HttpFoundation\Response $rsp, $app) {
$rsp->headers->set('header-key', 'hhxsv5');// 修改header
});
此特性依赖
Swoole
的AsyncTask
,必须先设置config/laravels.php
的swoole.task_worker_num
。异步事件的处理能力受Task进程数影响,需合理设置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
{
// 声明没有参数的构造函数
public function __construct()
{
}
public function handle(Event $event)
{
\Log::info(__CLASS__ . ':handle start', [$event->getData()]);
sleep(2);// 模拟一些慢速的事件处理
// throw new \Exception('an exception');// handle时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理
}
}
3.绑定事件与监听器。
// 在"config/laravels.php"中绑定事件与监听器,一个事件可以有多个监听器,多个监听器按顺序执行
[
// ...
'events' => [
\App\Tasks\TestEvent::class => [
\App\Tasks\TestListener1::class,
//\App\Tasks\TestListener2::class,
],
],
// ...
];
4.触发事件。
// 实例化TestEvent并通过fire触发,此操作是异步的,触发后立即返回,由Task进程继续处理监听器中的handle逻辑
use Hhxsv5\LaravelS\Swoole\Task\Event;
$success = Event::fire(new TestEvent('event data'));
var_dump($success);//判断是否触发成功
此特性依赖
Swoole
的AsyncTask
,必须先设置config/laravels.php
的swoole.task_worker_num
。异步任务的处理能力受Task进程数影响,需合理设置task_worker_num。
1.创建任务类。
use Hhxsv5\LaravelS\Swoole\Task\Task;
class TestTask extends Task
{
private $data;
private $result;
public function __construct($data)
{
$this->data = $data;
}
// 处理任务的逻辑,运行在Task进程中,不能投递任务
public function handle()
{
\Log::info(__CLASS__ . ':handle start', [$this->data]);
sleep(2);// 模拟一些慢速的事件处理
// throw new \Exception('an exception');// handle时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理
$this->result = 'the result of ' . $this->data;
}
// 可选的,完成事件,任务处理完后的逻辑,运行在Worker进程中,可以投递任务
public function finish()
{
\Log::info(__CLASS__ . ':finish start', [$this->result]);
Task::deliver(new TestTask2('task2')); // 投递其他任务
}
}
2.投递任务。
// 实例化TestTask并通过deliver投递,此操作是异步的,投递后立即返回,由Task进程继续处理TestTask中的handle逻辑
use Hhxsv5\LaravelS\Swoole\Task\Task;
$task = new TestTask('task data');
// $task->delay(3);// 延迟3秒投放任务
$ret = Task::deliver($task);
var_dump($ret);//判断是否投递成功
基于Swoole的毫秒定时器,封装的定时任务,取代
Linux
的Crontab
。
1.创建定时任务类。
namespace App\Jobs\Timer;
use App\Tasks\TestTask;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Hhxsv5\LaravelS\Swoole\Timer\CronJob;
class TestCronJob extends CronJob
{
protected $i = 0;
// 声明没有参数的构造函数
public function __construct()
{
}
public function interval()
{
return 1000;// 每1秒运行一次
}
public function isImmediate()
{
return false;// 是否立即执行第一次,false则等待间隔时间后执行第一次
}
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) { // 运行10次后不再执行
\Log::info(__METHOD__, ['stop', $this->i, microtime(true)]);
$this->stop(); // 终止此任务
$ret = Task::deliver(new TestTask('task data'), true); // CronJob中也可以投递Task,注意第二个参数传true
var_dump($ret);
}
// throw new \Exception('an exception');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理
}
}
2.绑定定时任务类。
// 在"config/laravels.php"绑定定时任务类
[
// ...
'timer' => [
'enable' => true, //启用Timer
'jobs' => [ //绑定的定时任务类列表
// 启用LaravelScheduleJob来执行`php artisan schedule:run`,每分钟一次,替代Linux Crontab
//\Hhxsv5\LaravelS\Illuminate\LaravelScheduleJob::class,
\App\Jobs\Timer\TestCronJob::class,
],
],
// ...
];
3.注意在构建服务器集群时,会启动多个定时器
,要确保只启动一个定期器,避免重复执行定时任务。
swoole_server
实例/**
* 如果启用WebSocket server,$swoole是`swoole_websocket_server`的实例,否则是是`\swoole_http_server`的实例
* @var \swoole_http_server|\swoole_websocket_server $swoole
*/
$swoole = app('swoole');
var_dump($swoole->stats());// 单例
swoole_table
1.定义swoole_table
,支持定义多个Table。
Swoole启动之前会创建定义的所有Table。
// 在"config/laravels.php"配置`swoole_table`
[
// ...
'swoole_tables' => [
// 场景:WebSocket中UserId与FD绑定
'ws' => [// Key为Table名称,使用时会自动添加Table后缀,避免重名。这里定义名为wsTable的Table
'size' => 102400,//Table的最大行数
'column' => [// Table的列定义
['name' => 'value', 'type' => \swoole_table::TYPE_INT, 'size' => 8],
],
],
//...继续定义其他Table
],
// ...
];
2.访问swoole_table
:所有的Table实例均绑定在swoole_server
上,通过app('swoole')->xxxTable
访问。
// 场景:WebSocket中UserId与FD绑定
public function onOpen(\swoole_websocket_server $server, \swoole_http_request $request)
{
// var_dump(app('swoole') === $server);// 同一实例
$userId = mt_rand(1000, 10000);
app('swoole')->wsTable->set('uid:' . $userId, ['value' => $request->fd]);// 绑定uid到fd的映射
app('swoole')->wsTable->set('fd:' . $request->fd, ['value' => $userId]);// 绑定fd到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'));// 广播
}
}
}
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']);// 解绑uid映射
}
app('swoole')->wsTable->del('fd:' . $fd);// 解绑fd映射
$server->push($fd, 'Goodbye');
}
更多的信息,请参考Swoole增加监听的端口与多端口混合协议
为了使我们的主服务器能支持除HTTP
和WebSocket
外的更多协议,我们引入了Swoole
的多端口混合协议
特性,在LaravelS中称为Socket
。现在,可以很方便地在Laravel
上被构建TCP/UDP
应用。
Hhxsv5\LaravelS\Swoole\Socket\{TcpSocket|UdpSocket|Http|WebSocket}
namespace App\Sockets;
use Hhxsv5\LaravelS\Swoole\Socket\TcpSocket;
class TestTcpSocket extends TcpSocket
{
public function onConnect(\swoole_server $server, $fd, $reactorId)
{
\Log::info('New TCP connection', [$fd]);
$server->send($fd, 'Welcome to LaravelS.');
}
public function onReceive(\swoole_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(\swoole_server $server, $fd, $reactorId)
{
\Log::info('New TCP connection', [$fd]);
$server->send($fd, 'Goodbye');
}
}
这些连接和主服务器上的HTTP/WebSocket连接共享Worker进程,因此可以在这些事件操作中使用LaravelS提供的异步任务投递
、swoole_table
、Laravel提供的组件如DB
、Eloquent
等。同时,如果需要使用该协议端口的swoole_server_port
对象,只需要像如下代码一样访问Socket
类的成员swoolePort
即可。
public function onReceive(\swoole_server $server, $fd, $reactorId, $data)
{
$port = $this->swoolePort; //获得`swoole_server_port`对象
}
// 修改文件 config/laravels.php
// ...
'sockets' => [
[
'host' => '127.0.0.1',
'port' => 5291,
'type' => SWOOLE_SOCK_TCP,// 支持的嵌套字类型:https://wiki.swoole.com/wiki/page/16.html#entry_h2_0
'settings' => [// Swoole可用的配置项:https://wiki.swoole.com/wiki/page/526.html
'open_eof_check' => true,
'package_eof' => "\r\n",
],
'handler' => \App\Sockets\TestTcpSocket::class,
],
],
对于TCP协议,dispatch_mode
选项设为1/3
时,底层会屏蔽onConnect
/onClose
事件,原因是这两种模式下无法保证onConnect
/onClose
/onReceive
的顺序。如果需要用到这两个事件,请将dispatch_mode
改为2/4/5
,参考。
'swoole' => [
//...
'dispatch_mode' => 2,
//...
];
TCP:telnet 127.0.0.1 5291
UDP:Linux下 echo "Hello 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,
],
],
'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,
],
],
支持MySQL数据库的
协程
客户端。注意:目前客户端连接为单例,并发时存在问题,正在开发连接池已解决此问题。
1.要求:Swoole>=4.0
,Laravel>=5.1
(后续将支持Lumen)。
2.修改config/database.php
MySQL连接的driver
为sw-co-mysql
。
'connections' => [
//...
'mysql-test' => [
//'driver' => 'mysql',
'driver' => 'sw-co-mysql',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', 3306),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
],
//...
],
3.替换(注释掉之前的)config/app.php
中providers
的Illuminate\Database\DatabaseServiceProvider::class
为\Hhxsv5\LaravelS\Illuminate\Database\DatabaseServiceProvider::class
。
'providers' => [
//...
//Illuminate\Database\DatabaseServiceProvider::class,// Just annotate this line.
\Hhxsv5\LaravelS\Illuminate\Database\DatabaseServiceProvider::class,
//...
],
4.配置完成,查询构造器
和ORM
按正常的使用即可。
支持开发者创建一些特殊的工作进程,用于监控、上报或者其他特殊的任务,参考addProcess。
namespace App\Processes;
use Hhxsv5\LaravelS\Swoole\Process\CustomProcessInterface;
class TestProcess implements CustomProcessInterface
{
public static function getName()
{
// 进程名称
return 'test';
}
public static function isRedirectStdinStdout()
{
// 是否重定向输入输出
return false;
}
public static function getPipeType()
{
// 管道类型:0不创建管道,1创建SOCK_STREAM类型管道,2创建SOCK_DGRAM类型管道
return 0;
}
public static function callback(\swoole_server $swoole)
{
// 进程运行的代码,不能退出,一旦退出Manager进程会自动再次创建该进程。
\Log::info(__METHOD__, [posix_getpid(), $swoole->stats()]);
while (true) {
sleep(1);
\Log::info('Do something');
}
}
}
// 修改文件 config/laravels.php
// ...
'processes' => [
\App\Processes\TestProcess::class,
],
单例问题
传统FPM下,单例模式的对象的生命周期仅在每次请求中,请求开始=>实例化单例=>请求结束后=>单例对象资源回收。
Swoole Server下,所有单例对象会常驻于内存,这个时候单例对象的生命周期与FPM不同,请求开始=>实例化单例=>请求结束=>单例对象依旧保留,需要开发者自己维护单例的状态。
常见的解决方案:
用一个中间件
来重置
单例对象的状态。
如果是以ServiceProvider
注册的单例对象,可添加该ServiceProvider
到laravels.php
的register_providers
中,这样每次请求会重新注册该ServiceProvider
,重新实例化单例对象,参考。
推荐通过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(),不能使用
函数像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' => [
// 开启持久连接
\PDO::ATTR_PERSISTENT => true,
],
],
//...
],
//...
不会立即
自动重连,会抛出一个关于连接断开的异常,下次会自动重连。需确保每次操作Redis前正确的SELECT DB
。// config/database.php
'redis' => [
'default' => [
'host' => env('REDIS_HOST', 'localhost'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
'persistent' => true, // 开启持久连接
],
],
//...
你声明的全局、静态变量必须手动清理或重置。
无限追加元素到静态或全局变量中,将导致内存爆满。
// 某类
class Test
{
public static $array = [];
public static $string = '';
}
// 某控制器
public function test(Request $req)
{
// 内存爆满
Test::$array[] = $req->input('param1');
Test::$string .= $req->input('param2');
}
针对MySQL/Redis的连接池。
包装Redis/Http的协程客户端。
您的支持是我们坚持的最大动力。
支持者 | 金额 |
---|---|
*思勇 | 18.88元 |
*德国 | 18.88元 |
魂之挽歌 | 100元 |
小南瓜 | 10.01元 |
*丁智 | 16.66元 |
匿名 | 20元 |
匿名 | 20元 |
*洋 Blues | 18.88元 |
*钧泽 Panda | 10.24元 |
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。