一.安装laravel
1.安装准备
安装前必须保证系统已经安装了下列软件
PHP >= 7.1.3
PHP OpenSSL 扩展
PHP PDO 扩展
PHP Mbstring 扩展
PHP Tokenizer 扩展
PHP XML 扩展
PHP Ctype 扩展
PHP JSON 扩展
PHP BCMath 扩展
2.composer安装
composer create-project –prefer-dist laravel/laravel laravel_swoole 5.8.*
3.laravel配置
1
2
3
|
#swoole配置
LARAVELS_LISTEN_PORT=9502
LARAVELS_DAEMONIZE=true
|
二.安装swoole
见swoole学习笔记
三.安装LaravelS
1.composer安装插件
1
2
3
|
composer require hhxsv5/laravel-s
php artisan laravels publish
php bin/laravels start
|
2.构建 HTTP nginx服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
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 {
# Connect IP:Porty
server 127.0.0.1:9502 weight=5 max_fails=3 fail_timeout=30s;
keepalive 16;
}
server {
listen 80;
root /data/website/laravel_swoole/public/;
server_name lavarels.dy2.cn;
index index.html index.htm index.php;
# Nginx 处理静态资源,LaravelS 处理动态资源
location / {
try_files $uri @laravels;
}
location @laravels {
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://laravels;
}
}
|
四.性能测试
1.PHP-FPM驱动
请求1000次,每次1000并发
结果平均响应时间为24574毫秒,每秒请求数14.51
详细结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
.\ab.exe -n1000 -c1000 http://dy2.cn/laravel_swoole/public/
Server Software: nginx
Server Hostname: dy2.cn
Server Port: 80
Document Path: /laravel_swoole/public/
Document Length: 2360 bytes
Concurrency Level: 1000
Time taken for tests: 68.934 seconds
Complete requests: 1000
Failed requests: 206
(Connect: 0, Receive: 0, Length: 206, Exceptions: 0)
Non-2xx responses: 206
Total transferred: 2682422 bytes
HTML transferred: 1908036 bytes
Requests per second: 14.51 [#/sec] (mean)
Time per request: 68934.064 [ms] (mean)
Time per request: 68.934 [ms] (mean, across all concurrent requests)
Transfer rate: 38.00 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 16 211.7 1 3002
Processing: 10722 28594 14160.8 24573 66227
Waiting: 10721 28591 14161.6 24572 66227
Total: 10723 28610 14148.3 24574 66230
Percentage of the requests served within a certain time (ms)
50% 24574
66% 29607
75% 36742
80% 44639
90% 49959
95% 53639
98% 64683
99% 66110
100% 66230 (longest request)
|
2. Swoole 驱动
请求1000次,每次1000并发
结果平均响应时间为1741毫秒,每秒请求数253.92
详细结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
.\ab.exe -n1000 -c1000 http://laravels.dy2.cn/
Server Software: nginx
Server Hostname: laravels.dy2.cn
Server Port: 80
Document Path: /
Document Length: 2360 bytes
Concurrency Level: 1000
Time taken for tests: 3.938 seconds
Complete requests: 1000
Failed requests: 1
(Connect: 0, Receive: 0, Length: 1, Exceptions: 0)
Non-2xx responses: 1
Total transferred: 3263810 bytes
HTML transferred: 2357826 bytes
Requests per second: 253.92 [#/sec] (mean)
Time per request: 3938.276 [ms] (mean)
Time per request: 3.938 [ms] (mean, across all concurrent requests)
Transfer rate: 809.32 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.1 1 11
Processing: 95 1842 656.4 1739 3065
Waiting: 87 1840 657.7 1738 3064
Total: 95 1843 656.3 1741 3065
Percentage of the requests served within a certain time (ms)
50% 1741
66% 2201
75% 2489
80% 2577
90% 2800
95% 2881
98% 2962
99% 2965
100% 3065 (longest request)
|
###3. 测试总结
swoole驱动是传统php-fpm驱动的17.5倍,优势明显
五.Laravel中swoole应用
1. 在 Laravel 中集成 Swoole 实现 WebSocket 服务器
创建 WebSocketService 类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
<?php
namespace App\Services;
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
use Illuminate\Support\Facades\Log;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
class WebSocketService implements WebSocketHandlerInterface
{
public function __construct()
{
}
// 连接建立时触发
public function onOpen(Server $server, Request $request)
{
// 在触发 WebSocket 连接建立事件之前,Laravel 应用初始化的生命周期已经结束,你可以在这里获取 Laravel 请求和会话数据
// 调用 push 方法向客户端推送数据,fd 是客户端连接标识字段
Log::info('WebSocket 连接建立');
$server->push($request->fd, 'Welcome to WebSocket Server built on LaravelS');
}
// 收到消息时触发
public function onMessage(Server $server, Frame $frame)
{
// 调用 push 方法向客户端推送数据
$server->push($frame->fd, 'This is a message sent from WebSocket Server at ' . date('Y-m-d H:i:s'));
}
// 关闭连接时触发
public function onClose(Server $server, $fd, $reactorId)
{
Log::info('WebSocket 连接关闭');
}
}
|
修改配置文件
config/laravels.php
启用 WebSocket 通信并将刚刚创建的服务器类配置到对应的配置项
‘websocket’ => [
‘enable’ => true,
‘handler’ => \App\Services\WebSocketService::class,
],
在 swoole 配置项中配置 WebSocket 长连接的强制关闭逻辑
// 心跳检测,WebSocket 长连接的强制关闭逻辑,每隔 60s 检测一次所有连接,如果某个连接在 600s 内都没有发送任何数据,则关闭该连接
‘heartbeat_idle_time’ => 600,
‘heartbeat_check_interval’ => 60,
配置 Nginx 支持 WebSocket
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream laravels_websocket {
# Connect IP:Port
server 127.0.0.1:9502 weight=5 max_fails=3 fail_timeout=30s;
keepalive 16;
}
server {
listen 80;
root /data/website/laravel_swoole/public/;
server_name laravelsk.dy2.cn;
error_log /data/webserver/nginx/logs/laravels_websocket_error.log;
access_log /data/webserver/nginx/logs/laravels_websocket_access.log;
index index.php 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://todo-s.test/ws");
# 处理 WebSocket 通信
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://laravels_websocket;
}
location @laravels {
# 如果60秒内被代理的服务器没有响应数据给Nginx,那么Nginx会关闭当前连接
# 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://laravels_websocket;
}
}
|
创建views/websocket/client.blade.php视图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Chat Client</title>
</head>
<body>
<h1>websocket客户端</h1>
<script>
window.onload = function () {
var nick = prompt("Enter your nickname");
var input = document.getElementById("input");
input.focus();
// 初始化客户端套接字并建立连接
var socket = new WebSocket("ws://laravelsk.dy2.cn/ws");
// 连接建立时触发
socket.onopen = function (event) {
console.log("Connection open ...");
}
// 接收到服务端推送时执行
socket.onmessage = function (event) {
var msg = event.data;
var node = document.createTextNode(msg);
var div = document.createElement("div");
div.appendChild(node);
document.body.insertBefore(div, input);
input.scrollIntoView();
};
// 连接关闭时触发
socket.onclose = function (event) {
console.log("Connection closed ...");
}
input.onchange = function () {
var msg = nick + ": " + input.value;
// 将输入框变更信息通过 send 方法发送到服务器
socket.send(msg);
input.value = "";
};
}
</script>
<input id="input" style="width: 100%;">
</body>
</html>
|
添加路由
Route::get('/websocket_client', function () {
return view(‘websocket.client’);
});
访问:http://laravelsk.dy2.cn/websocket_client
storage/logs下查看最新日志
[2019-05-22 13:55:02] local.INFO: WebSocket 连接建立
[2019-05-22 13:55:10] local.INFO: WebSocket 连接关闭
2. 在 Laravel 中集成 Swoole 实现 毫秒级定时器
创建Jobs\Timer\IndexCronJob执行文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2019/9/10 0010
* Time: 10:13
*/
namespace App\Jobs\Timer;
use Hhxsv5\LaravelS\Swoole\Timer\CronJob;
use Illuminate\Support\Facades\Log;
class IndexCronJob extends CronJob
{
protected $i = 0;
// 该方法可类比为 Swoole 定时器中的回调方法
public function run()
{
Log::info(__METHOD__, [$this->timerId,'start', $this->i, microtime(true)]);
$this->i++;
Log::info(__METHOD__, [$this->timerId,'end', $this->i, microtime(true)]);
if ($this->i == 3) { // 总共运行3次
Log::info(__METHOD__, [$this->timerId,'stop', $this->i, microtime(true)]);
$this->stop(); // 清除定时器
}
}
// 每隔 1000ms 执行一次任务
public function interval()
{
return 1000; // 定时器间隔,单位为 ms
}
// 是否在设置之后立即触发 run 方法执行
public function isImmediate()
{
return false;
}
}
|
修改config/laravels.php 配置
1
2
3
4
5
6
7
8
9
10
11
|
'timer' => [
'enable' => true,
'jobs' => [
// 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\XxxCronJob::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
],
|
storage/logs 下的最新日志里就可以看到上述定时任务的输出了
1
2
3
4
5
6
7
8
|
[2019-05-29 22:37:20] local.INFO: App\Jobs\Timer\TestCronJob::run ["start",0,1559140640.694025]
[2019-05-29 22:37:20] local.INFO: App\Jobs\Timer\TestCronJob::run ["end",1,1559140640.752043]
[2019-05-29 22:37:21] local.INFO: App\Jobs\Timer\TestCronJob::run ["start",1,1559140641.752905]
[2019-05-29 22:37:21] local.INFO: App\Jobs\Timer\TestCronJob::run ["end",2,1559140641.754724]
[2019-05-29 22:37:22] local.INFO: App\Jobs\Timer\TestCronJob::run ["start",2,1559140642.694884]
[2019-05-29 22:37:22] local.INFO: App\Jobs\Timer\TestCronJob::run ["end",3,1559140642.696726]
[2019-05-29 22:37:22] local.INFO: App\Jobs\Timer\TestCronJob::run ["stop",3,1559140642.698137]
|
3. 在 Laravel 中集成 Swoole 实现 异步任务队列
创建 Jobs/IndexTasK类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
<?php
namespace App\Jobs;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Support\Facades\Log;
class IndexTask extends Task
{
// 待处理任务数据
private $data;
// 任务处理结果
private $result;
public function __construct($data)
{
$this->data = $data;
}
// 任务投递调用 task 回调时触发,等同于 Swoole 中的 onTask 逻辑
public function handle()
{
Log::info(__CLASS__ . ': 开始处理任务', [$this->data]);
// todo 耗时任务具体处理逻辑在这里编写
sleep(3); // 模拟任务需要3秒才能执行完毕
$this->result = 'The result of ' . $this->data . ' is balabalabala';
}
// 任务完成调用 finish 回调时触发,等同于 Swoole 中的 onFinish 逻辑
public function finish()
{
Log::info(__CLASS__ . ': 任务处理完成', [$this->result]);
// 可以在这里触发后续要执行的任务,或者执行其他善后逻辑
}
}
|
####创建http访问路由
//分组路由
Route::group([ ‘namespace’ => ‘Task’ ,‘prefix’ => ‘task’], function ($router) {
$router->get('/test', ‘TestController@test’);
});
创建异步处理控制器
1
2
3
4
5
6
7
8
9
10
11
12
13
|
namespace App\Http\Controllers\Task;
use App\Http\Controllers\Controller;
class TestController extends Controller
{
public function test(){
$task = new \App\Jobs\IndexTask('测试异步任务');
$success = \Hhxsv5\LaravelS\Swoole\Task\Task::deliver($task); // 异步投递任务,触发调用任务类的 handle 方法
var_dump($success);
}
}
|
修改配置文件
‘swoole’ => [
…
‘task_worker_num’ => function_exists(‘swoole_cpu_num’) ? swoole_cpu_num() * 2 : 8,
…
]
测试异步任务执行
php bin/laravels restart
访问 http://laravelsk.dy2.cn 返回 bool(true)
storage/logs 目录下查看最新的日志信息
[2019-09-10 03:33:49] local.INFO: App\Jobs\IndexTask: 开始处理任务 [“测试异步任务”]
[2019-09-10 03:33:52] local.INFO: App\Jobs\IndexTask: 任务处理完成 [“The result of 测试异步任务 is balabalabala”]
4.在 Laravel 中实现数据库和 Redis 的持久连接
修改配置文件,设置persistent
// 数据库持久连接配置
‘mysql’ => [
‘driver’ => ‘mysql’,
‘host’ => env(‘DB_HOST’, ‘127.0.0.1’),
‘port’ => env(‘DB_PORT’, ‘3306’),
‘database’ => env(‘DB_DATABASE’, ‘forge’),
‘username’ => env(‘DB_USERNAME’, ‘forge’),
‘password’ => env(‘DB_PASSWORD’, ‘'),
‘unix_socket’ => env(‘DB_SOCKET’, ‘'),
‘charset’ => ‘utf8mb4’,
‘collation’ => ‘utf8mb4_unicode_ci’,
‘prefix’ => ‘’,
‘prefix_indexes’ => true,
‘strict’ => true,
‘engine’ => null,
‘options’ => extension_loaded(‘pdo_mysql’) ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env(‘MYSQL_ATTR_SSL_CA’),
PDO::ATTR_PERSISTENT => true
]) : [],
],
// Redis 持久连接配置
‘redis’ => [
…
‘default’ => [
‘host’ => env(‘REDIS_HOST’, ‘127.0.0.1’),
‘password’ => env(‘REDIS_PASSWORD’, null),
‘port’ => env(‘REDIS_PORT’, 6379),
‘database’ => env(‘REDIS_DB’, 0),
‘persistent’ => true
],
…
]
修改WebSocketService,添加table处理
class WebSocketService implements WebSocketHandlerInterface
{
public function __construct()
{
}
// 连接建立时触发
public function onOpen(Server $server, Request $request)
{
// 在触发 WebSocket 连接建立事件之前,Laravel 应用初始化的生命周期已经结束,你可以在这里获取 Laravel 请求和会话数据
// 调用 push 方法向客户端推送数据,fd 是客户端连接标识字段
Log::info('WebSocket 连接建立');
app('swoole')->wsTable->set('fd:' . $request->fd, ['value' => $request->fd]);
$server->push($request->fd, 'Welcome to WebSocket Server built on LaravelS');
}
// 收到消息时触发
public function onMessage(Server $server, Frame $frame)
{
// 调用 push 方法向客户端推送数据
$server->push($frame->fd, 'This is a message sent from WebSocket Server at ' . date('Y-m-d H:i:s'));
foreach (app('swoole')->wsTable as $key => $row) {
if (strpos($key, 'fd:') === 0 && $server->exist($row['value'])) {
Log::info('Receive message from client: ' . $row['value']);
// 调用 push 方法向客户端推送数据
$server->push($frame->fd, 'This is a message sent from WebSocket Server at ' . date('Y-m-d H:i:s'));
}
}
}
// 关闭连接时触发
public function onClose(Server $server, $fd, $reactorId)
{
Log::info('WebSocket 连接关闭');
}
}
访问http://laravelsk.dy2.cn/websocket_client
tail -f storage/logs/laravel-2019-09-10.log
[2019-09-10 07:55:50] local.INFO: WebSocket 连接建立
[2019-09-10 07:55:53] local.INFO: Receive message from client: 2
[2019-09-10 07:56:08] local.INFO: WebSocket 连接关闭