LaravelS - 基于Swoole加速Laravel/Lumen
LaravelS
特性
要求
安装1.通过Composer安装(packagist)。有可能找不到 composer require "hhxsv5/laravel-s:~3.5.0" -vvv # 确保你的composer.lock文件是在版本控制中 ? 2.注册Service Provider(以下两步二选一)。
3.发布配置和二进制文件。 每次升级LaravelS后,需重新publish;点击Release去了解各个版本的变更记录。
4.修改配置 运行
部署建议通过Supervisord监管主进程,前提是不能加 [program:laravel-s-test] command=/user/local/bin/php /opt/www/laravel-s-test/bin/laravels start -i numprocs=1 autostart=true autorestart=true startretries=3 user=www-data redirect_stderr=true stdout_logfile=/opt/www/laravel-s-test/storage/logs/supervisord-stdout.log ? 与Nginx配合使用(推荐)示例。 gzip on; gzip_min_length 1024; gzip_comp_level 2; gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml; gzip_vary on; gzip_disable "msie6"; upstream swoole { # 通过 IP:Port 连接 server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s; # 通过 UnixSocket Stream 连接,小诀窍:将socket文件放在/dev/shm目录下,可获得更好的性能 #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; keepalive 16; } server { listen 80; # 别忘了绑Host哟 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_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; proxy_pass http://swoole; } } ? 与Apache配合使用1 LoadModule proxy_module /yyypath/modules/mod_deflate.so 2 <IfModule deflate_module> 3 SetOutputFilter DEFLATE 4 DeflateCompressionLevel 2 5 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 6 </IfModule> 7 8 <VirtualHost *:80> 9 # 别忘了绑Host哟 10 ServerName www.laravels.com 11 ServerAdmin hhxsv5@sina.com 12 13 DocumentRoot /xxxpath/laravel-s-test/public; 14 DirectoryIndex index.html index.htm 15 <Directory "/"> 16 AllowOverride None 17 Require all granted 18 </Directory> 19 20 LoadModule proxy_module /yyypath/modules/mod_proxy.so 21 LoadModule proxy_module /yyypath/modules/mod_proxy_balancer.so 22 LoadModule proxy_module /yyypath/modules/mod_lbmethod_byrequests.so.so 23 LoadModule proxy_module /yyypath/modules/mod_proxy_http.so.so 24 LoadModule proxy_module /yyypath/modules/mod_slotmem_shm.so 25 LoadModule proxy_module /yyypath/modules/mod_rewrite.so 26 27 ProxyRequests Off 28 ProxyPreserveHost On 29 <Proxy balancer://laravels> 30 BalancerMember http://192.168.1.1:5200 loadfactor=7 31 #BalancerMember http://192.168.1.2:5200 loadfactor=3 32 #BalancerMember http://192.168.1.3:5200 loadfactor=1 status=+H 33 ProxySet lbmethod=byrequests 34 </Proxy> 35 #ProxyPass / balancer://laravels/ 36 #ProxyPassReverse / balancer://laravels/ 37 38 # Apache处理静态资源,LaravelS处理动态资源。 39 RewriteEngine On 40 RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d 41 RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f 42 RewriteRule ^/(.*)$ balancer://laravels/%{REQUEST_URI} [P,L] 43 44 ErrorLog ${APACHE_LOG_DIR}/www.laravels.com.error.log 45 CustomLog ${APACHE_LOG_DIR}/www.laravels.com.access.log combined 46 </VirtualHost> ? 启用WebSocket服务器WebSocket服务器监听的IP和端口与Http服务器相同。 1.创建WebSocket Handler类,并实现接口 1 namespace AppServices; 2 use Hhxsv5LaravelSSwooleWebSocketHandlerInterface; 3 use SwooleHttpRequest; 4 use SwooleWebSocketFrame; 5 use SwooleWebSocketServer; 6 /** 7 * @see https://wiki.swoole.com/wiki/page/400.html 8 */ 9 class WebSocketService implements WebSocketHandlerInterface 10 { 11 // 声明没有参数的构造函数 12 public function __construct() 13 { 14 } 15 public function onOpen(Server $server,Request $request) 16 { 17 // 在触发onOpen事件之前,建立WebSocket的HTTP请求已经经过了Laravel的路由, 18 // 所以Laravel的Request、Auth等信息是可读的,Session是可读写的,但仅限在onOpen事件中。 19 // Log::info('New WebSocket connection',[$request->fd,request()->all(),session()->getId(),session('xxx'),session(['yyy' => time()])]); 20 $server->push($request->fd,'Welcome to LaravelS'); 21 // throw new Exception('an exception');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理 22 } 23 public function onMessage(Server $server,Frame $frame) 24 { 25 // Log::info('Received message',[$frame->fd,$frame->data,$frame->opcode,$frame->finish]); 26 $server->push($frame->fd,date('Y-m-d H:i:s')); 27 // throw new Exception('an exception');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理 28 } 29 public function onClose(Server $server,$fd,$reactorId) 30 { 31 // throw new Exception('an exception');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理 32 } 33 } ? 2.更改配置 1 // ... 2 'websocket' => [ 3 'enable' => true,// 看清楚,这里是true 4 'handler' => AppServicesWebSocketService::class, 5 ], 6 'swoole' => [ 7 //... 8 // dispatch_mode只能设置为2、4、5,https://wiki.swoole.com/wiki/page/277.html 9 'dispatch_mode' => 2,10 //... 11 ],12 // ... ? 3.使用 4.与Nginx配合使用(推荐) 参考?WebSocket代理 1 map $http_upgrade $connection_upgrade { 2 default upgrade; 3 '' close; 4 } 5 upstream swoole { 6 # 通过 IP:Port 连接 7 server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s; 8 # 通过 UnixSocket Stream 连接,小诀窍:将socket文件放在/dev/shm目录下,可获得更好的性能 9 #server unix:/xxxpath/laravel-s-test/storage/laravels.sock weight=5 max_fails=3 fail_timeout=30s; 10 #server 192.168.1.1:5200 weight=3 max_fails=3 fail_timeout=30s; 11 #server 192.168.1.2:5200 backup; 12 keepalive 16; 13 } 14 server { 15 listen 80; 16 # 别忘了绑Host哟 17 server_name laravels.com; 18 root /xxxpath/laravel-s-test/public; 19 access_log /yyypath/log/nginx/$server_name.access.log main; 20 autoindex off; 21 index index.html index.htm; 22 # Nginx处理静态资源(建议开启gzip),LaravelS处理动态资源。 23 location / { 24 try_files $uri @laravels; 25 } 26 # 当请求PHP文件时直接响应404,防止暴露public/*.php 27 #location ~* .php$ { 28 # return 404; 29 #} 30 # Http和WebSocket共存,Nginx通过location区分 31 # !!! WebSocket连接时路径为/ws 32 # Javascript: var ws = new WebSocket("ws://laravels.com/ws"); 33 location =/ws { 34 # proxy_connect_timeout 60s; 35 # proxy_send_timeout 60s; 36 # proxy_read_timeout:如果60秒内被代理的服务器没有响应数据给Nginx,那么Nginx会关闭当前连接;同时,Swoole的心跳设置也会影响连接的关闭 37 # proxy_read_timeout 60s; 38 proxy_http_version 1.1; 39 proxy_set_header X-Real-IP $remote_addr; 40 proxy_set_header X-Real-PORT $remote_port; 41 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 42 proxy_set_header Host $http_host; 43 proxy_set_header Scheme $scheme; 44 proxy_set_header Server-Protocol $server_protocol; 45 proxy_set_header Server-Name $server_name; 46 proxy_set_header Server-Addr $server_addr; 47 proxy_set_header Server-Port $server_port; 48 proxy_set_header Upgrade $http_upgrade; 49 proxy_set_header Connection $connection_upgrade; 50 proxy_pass http://swoole; 51 } 52 location @laravels { 53 # proxy_connect_timeout 60s; 54 # proxy_send_timeout 60s; 55 # proxy_read_timeout 60s; 56 proxy_http_version 1.1; 57 proxy_set_header Connection ""; 58 proxy_set_header X-Real-IP $remote_addr; 59 proxy_set_header X-Real-PORT $remote_port; 60 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 61 proxy_set_header Host $http_host; 62 proxy_set_header Scheme $scheme; 63 proxy_set_header Server-Protocol $server_protocol; 64 proxy_set_header Server-Name $server_name; 65 proxy_set_header Server-Addr $server_addr; 66 proxy_set_header Server-Port $server_port; 67 proxy_pass http://swoole; 68 } 69 } ? 5.心跳配置
监听事件系统事件通常,你可以在这些事件中重置或销毁一些全局或静态的变量,也可以修改当前的请求和响应。
自定义的异步事件此特性依赖 1.创建事件类。 1 use Hhxsv5LaravelSSwooleTaskEvent; 2 class TestEvent extends Event 3 { 4 private $data; 5 public function __construct($data) 6 { 7 $this->data = $data; 8 } 9 public function getData() 10 { 11 return $this->data; 12 } 13 } ? 2.创建监听器类。 1 use Hhxsv5LaravelSSwooleTaskTask; 2 use Hhxsv5LaravelSSwooleTaskEvent; 3 use Hhxsv5LaravelSSwooleTaskListener; 4 class TestListener1 extends Listener 5 { 6 // 声明没有参数的构造函数 7 public function __construct() 8 { 9 } 10 public function handle(Event $event) 11 { 12 Log::info(__CLASS__ . ':handle start',[$event->getData()]); 13 sleep(2);// 模拟一些慢速的事件处理 14 // 监听器中也可以投递Task,但不支持Task的finish()回调。 15 // 注意: 16 // 1.参数2需传true 17 // 2.config/laravels.php中修改配置task_ipc_mode为1或2,参考 https://wiki.swoole.com/wiki/page/296.html 18 $ret = Task::deliver(new TestTask('task data'),true); 19 var_dump($ret); 20 // throw new Exception('an exception');// handle时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理 21 } 22 } ? 3.绑定事件与监听器。 1 // 在"config/laravels.php"中绑定事件与监听器,一个事件可以有多个监听器,多个监听器按顺序执行 2 [ 3 // ... 4 'events' => [ 5 AppTasksTestEvent::class => [ 6 AppTasksTestListener1::class, 7 //AppTasksTestListener2::class, 8 ], 9 ],10 // ... 11 ]; ? 4.触发事件。 1 // 实例化TestEvent并通过fire触发,此操作是异步的,触发后立即返回,由Task进程继续处理监听器中的handle逻辑 2 use Hhxsv5LaravelSSwooleTaskEvent; 3 $success = Event::fire(new TestEvent('event data')); 4 var_dump($success);//判断是否触发成功 ? 异步的任务队列此特性依赖 1.创建任务类。 1 use Hhxsv5LaravelSSwooleTaskTask; 2 class TestTask extends Task 3 { 4 private $data; 5 private $result; 6 public function __construct($data) 7 { 8 $this->data = $data; 9 } 10 // 处理任务的逻辑,运行在Task进程中,不能投递任务 11 public function handle() 12 { 13 Log::info(__CLASS__ . ':handle start',[$this->data]); 14 sleep(2);// 模拟一些慢速的事件处理 15 // throw new Exception('an exception');// handle时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理 16 $this->result = 'the result of ' . $this->data; 17 } 18 // 可选的,完成事件,任务处理完后的逻辑,运行在Worker进程中,可以投递任务 19 public function finish() 20 { 21 Log::info(__CLASS__ . ':finish start',[$this->result]); 22 Task::deliver(new TestTask2('task2')); // 投递其他任务 23 } 24 } ? 2.投递任务。 1 // 实例化TestTask并通过deliver投递,此操作是异步的,投递后立即返回,由Task进程继续处理TestTask中的handle逻辑 2 use Hhxsv5LaravelSSwooleTaskTask; 3 $task = new TestTask('task data'); 4 // $task->delay(3);// 延迟3秒投放任务 5 $ret = Task::deliver($task); 6 var_dump($ret);//判断是否投递成功 ? 毫秒级定时任务基于Swoole的毫秒定时器,封装的定时任务,取代 1.创建定时任务类。 1 namespace AppJobsTimer; 2 use AppTasksTestTask; 3 use SwooleCoroutine; 4 use Hhxsv5LaravelSSwooleTaskTask; 5 use Hhxsv5LaravelSSwooleTimerCronJob; 6 class TestCronJob extends CronJob 7 { 8 protected $i = 0; 9 // !!! 定时任务的`interval`和`isImmediate`有两种配置方式(二选一):一是重载对应的方法,二是注册定时任务时传入参数。 10 // --- 重载对应的方法来返回配置:开始 11 public function interval() 12 { 13 return 1000;// 每1秒运行一次 14 } 15 public function isImmediate() 16 { 17 return false;// 是否立即执行第一次,false则等待间隔时间后执行第一次 18 } 19 // --- 重载对应的方法来返回配置:结束 20 public function run() 21 { 22 Log::info(__METHOD__,['start',$this->i,microtime(true)]); 23 // do something 24 // sleep(1); // Swoole < 2.1 25 Coroutine::sleep(1); // Swoole>=2.1 run()方法已自动创建了协程。 26 $this->i++; 27 Log::info(__METHOD__,['end',microtime(true)]); 28 29 if ($this->i >= 10) { // 运行10次后不再执行 30 Log::info(__METHOD__,['stop',microtime(true)]); 31 $this->stop(); // 终止此任务 32 // CronJob中也可以投递Task,但不支持Task的finish()回调。 33 // 注意: 34 // 1.参数2需传true 35 // 2.config/laravels.php中修改配置task_ipc_mode为1或2,参考 https://wiki.swoole.com/wiki/page/296.html 36 $ret = Task::deliver(new TestTask('task data'),true); 37 var_dump($ret); 38 } 39 // throw new Exception('an exception');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理 40 } 41 } ? 2.注册定时任务类。 1 // 在"config/laravels.php"注册定时任务类 2 [ 3 // ... 4 'timer' => [ 5 'enable' => true,// 启用Timer 6 'jobs' => [ // 注册的定时任务类列表 7 // 启用LaravelScheduleJob来执行`php artisan schedule:run`,每分钟一次,替代Linux Crontab 8 // Hhxsv5LaravelSIlluminateLaravelScheduleJob::class, 9 // 两种配置参数的方式: 10 // [AppJobsTimerTestCronJob::class,[1000,true]],// 注册时传入参数 11 AppJobsTimerTestCronJob::class,// 重载对应的方法来返回参数 12 ],13 'max_wait_time' => 5,// Reload时最大等待时间 14 ],15 // ... 16 ]; ? 3.注意在构建服务器集群时,会启动多个 4.LaravelS? 修改代码后自动Reload
在你的项目中使用
|
事件 | 需实现的接口 | 发生时机 |
---|---|---|
ServerStart | Hhxsv5LaravelSSwooleEventsServerStartInterface | 发生在Master进程启动时,此事件中不应处理复杂的业务逻辑,只能做一些初始化的简单工作
|
ServerStop | Hhxsv5LaravelSSwooleEventsServerStopInterface | 发生在Server正常退出时,此事件中不能使用异步或协程相关的API
|
WorkerStart | Hhxsv5LaravelSSwooleEventsWorkerStartInterface | 发生在Worker/Task进程启动完成后 |
WorkerStop | Hhxsv5LaravelSSwooleEventsWorkerStopInterface | 发生在Worker/Task进程正常退出后 |
WorkerError | Hhxsv5LaravelSSwooleEventsWorkerErrorInterface | 发生在Worker/Task进程发生异常或致命错误时 |
1.创建事件处理类,实现相应的接口。
1 namespace AppEvents; 2 use Hhxsv5LaravelSSwooleEventsServerStartInterface; 3 use SwooleAtomic; 4 use SwooleHttpServer; 5 class ServerStartEvent implements ServerStartInterface 6 { 7 public function __construct() 8 { 9 } 10 public function handle(Server $server) 11 { 12 // 初始化一个全局计数器(跨进程的可用) 13 $server->atomicCount = new Atomic(2233); 14 15 // 控制器中调用:app('swoole')->atomicCount->get(); 16 } 17 } 18 namespace AppEvents; 19 use Hhxsv5LaravelSSwooleEventsWorkerStartInterface; 20 use SwooleHttpServer; 21 class WorkerStartEvent implements WorkerStartInterface 22 { 23 public function __construct() 24 { 25 } 26 public function handle(Server $server,$workerId) 27 { 28 // 初始化一个数据库连接池对象 29 // DatabaseConnectionPool::init(); 30 } 31 }
?
2.配置。
1 // 修改文件 config/laravels.php 2 'event_handlers' => [ 3 'ServerStart' => AppEventsServerStartEvent::class,4 'WorkerStart' => AppEventsWorkerStartEvent::class,5 ],
?
注意事项
-
单例问题
- 传统FPM下,单例模式的对象的生命周期仅在每次请求中,请求开始=>实例化单例=>请求结束后=>单例对象资源回收。
- Swoole Server下,所有单例对象会常驻于内存,这个时候单例对象的生命周期与FPM不同,请求开始=>实例化单例=>请求结束=>单例对象依旧保留,需要开发者自己维护单例的状态。
-
常见的解决方案:
- 写一个
XxxCleaner
类来清理单例对象状态,此类需实现接口Hhxsv5LaravelSIlluminateCleanersCleanerInterface
,然后注册到laravels.php
的cleaners
中。 - 用一个
中间件
来重置
单例对象的状态。 - 如果是以
ServiceProvider
注册的单例对象,可添加该ServiceProvider
到laravels.php
的register_providers
中,这样每次请求会重新注册该ServiceProvider
,重新实例化单例对象,参考。
- 写一个
- LaravelS 已经内置了一些Cleaner。
- 常见问题:一揽子的已知问题和解决方案。
- 调试方式:记录日志、Laravel Dump Server(Laravel 5.7已默认集成)
-
应通过
IlluminateHttpRequest
对象来获取请求信息,是可读取的,_SERVER是部分可读的,不能使用
、_POST、、_COOKIE、、_SESSION、$GLOBALS。1 public function form(IlluminateHttpRequest $request) 2 { 3 $name = $request->input('name'); 4 $all = $request->all(); 5 $sessionId = $request->cookie('sessionId'); 6 $photo = $request->file('photo'); 7 // 调用getContent()来获取原始的POST body,而不能用file_get_contents('php://input') 8 $rawContent = $request->getContent(); 9 //... 10 }
?
-
推荐通过返回
IlluminateHttpResponse
对象来响应请求,兼容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'); }
?
- 各种
单例的连接
将被常驻内存,建议开启持久连接
。
-
数据库连接,连接断开后会自动重连
1 // config/database.php 2 'connections' => [ 3 'my_conn' => [ 4 'driver' => 'mysql', 5 'host' => env('DB_MY_CONN_HOST','localhost'), 6 'port' => env('DB_MY_CONN_PORT',3306), 7 'database' => env('DB_MY_CONN_DATABASE','forge'), 8 'username' => env('DB_MY_CONN_USERNAME', 9 'password' => env('DB_MY_CONN_PASSWORD',''),10 'charset' => 'utf8mb4',11 'collation' => 'utf8mb4_unicode_ci',12 'prefix' => '',13 'strict' => false,14 'options' => [ 15 // 开启持久连接 16 PDO::ATTR_PERSISTENT => true,17 ],18 ],19 //... 20 ],21 //...
?
-
Redis连接,连接断开后
不会立即
自动重连,会抛出一个关于连接断开的异常,下次会自动重连。需确保每次操作Redis前正确的SELECT DB
。1 // config/database.php 2 'redis' => [ 3 'client' => env('REDIS_CLIENT','phpredis'),// 推荐使用phpredis,以获得更好的性能 4 'default' => [ 5 'host' => env('REDIS_HOST', 6 'password' => env('REDIS_PASSWORD',null), 7 'port' => env('REDIS_PORT',6379), 8 'database' => 0, 9 'persistent' => true,// 开启持久连接 10 ],12 //...
?
- 你声明的全局、静态变量必须手动清理或重置。
-
无限追加元素到静态或全局变量中,将导致内存爆满。
1 // 某类 2 class Test 3 { 4 public static $array = []; 5 public static $string = ''; 6 } 7 8 // 某控制器 9 public function test(Request $req) 10 { 11 // 内存爆满 12 Test::$array[] = $req->input('param1'); 13 Test::$string .= $req->input('param2'); 14 }
?
- Linux内核参数调整
- 压力测试
用户与案例
- KuCoin
- 医联:WEB站、M站、APP、小程序的账户体系服务。
- ITOK在线客服平台:用户IT工单的处理跟踪及在线实时沟通。
- 盟呱呱
- 微信公众号-广州塔:活动、商城
- 企鹅游戏盒子、明星新势力、以及小程序广告服务
- 小程序-修机匠手机上门维修服务:手机维修服务,提供上门服务,支持在线维修。
- 亿健APP
?
推荐阅读:
实现websocket 主动消息推送,用laravel+Swoole
PHP laravel+thrift+swoole打造微服务框架
用Swoole+React 实现的聊天室
Swoole和Redis实现的并发队列处理系统
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!