Hugh's Blog

laravel-swoole 使用

最近常用的 PHP 框架是 Laravel,而 PHP 框架本身启动都要加载不少文件,往往 QPS 不会很高。

Swoole 扩展本身就常驻内存,异步执行,可以为项目提高不少性能,刚好也有轮子可以使用 swooletw/laravel-swoole,做个记录。

安装非常简单,按照文档来就行。

Swoole 扩展

# 安装
sudo apt-get install php7.2-dev php-pear
sudo pecl install swoole
# 扩展配置
php --ini
sudo echo "extension=swoole.so" >> /path/to/php.ini
# 查看是否启动
php -m | grep swoole
php --ri swoole

Laravel 配置

composer require swooletw/laravel-swoole

config/app.php 文件添加 providers:

[
    ...
    'providers' => [
        SwooleTW\Http\LaravelServiceProvider::class,
    ],
    ...
]

新增配置文件 config/swoole_http.php详细

return [
    'server' => [
        'host' => env('SWOOLE_HTTP_HOST', '127.0.0.1'),
        'port' => env('SWOOLE_HTTP_PORT', '1215'),
        'options' => [
            'pid_file' => env('SWOOLE_HTTP_PID_FILE', base_path('storage/logs/swoole_http.pid')),
            'log_file' => env('SWOOLE_HTTP_LOG_FILE', base_path('storage/logs/swoole_http.log')),
            'daemonize' => env('SWOOLE_HTTP_DAEMONIZE', true),
            'reactor_num' => env('SWOOLE_HTTP_REACTOR_NUM', swoole_cpu_num() * 2),
            'worker_num' => env('SWOOLE_HTTP_WORKER_NUM', swoole_cpu_num() * 2),
            'task_worker_num' => env('SWOOLE_HTTP_TASK_WORKER_NUM', swoole_cpu_num() * 2),
            'package_max_length' => 20 * 1024 * 1024,
            'buffer_output_size' => 10 * 1024 * 1024,
            'socket_buffer_size' => 128 * 1024 * 1024,
            'max_request' => 3000,
            'send_yield' => true,
            'ssl_cert_file' => null,
            'ssl_key_file' => null,
        ],
    ],

    'pre_resolved' => [
        'view', 'files', 'session', 'session.store', 'routes',
        'db', 'db.factory', 'cache', 'cache.store', 'config', 'cookie',
        'encrypter', 'hash', 'router', 'translator', 'url', 'log',
    ],

    'providers' => [
        Illuminate\Auth\AuthServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
    ],
];

启动命令:php artisan swoole:http {start|stop|restart|reload|infos}

更多的信息以及注意事项可以到文档或者 Wiki 看下,里面说得比较详细,地址

遇到的坑

全局变量

已经换成 Swoole 的运行方式了,所以全局或者单例变量都是共用的,会出现混淆的情况,例如已经登录的用户,别人无需登录便可访问。

解决很简单,配置 providers 就行,每个请求下都会重新注册。

Nginx 配置

还是对 Nginx 的配置不太熟悉,在本地设置转发代理:

proxy_pass http://localhost:1215;

显示 502 错误,查看日志显示 no resolver defined to resolve localhost ...

查了下,是因为 proxy_pass 如果使用域名的话,需要使用 resolver 指令解析变量中的域名,如果换成 IP 就没问题 http://127.0.0.1:1215

不过我一般会用 Docker 来对项目打包,可以使用 upstream 来解决域名的问题。

upstream php.swoole {
    server localhost:1215;
}
server {
    ...
    location @swoole {
        ...
        proxy_pass http://php.swoole;
    }
    ...
}

分页参数

具体表现为:不管请求传递的 page 值是多少,最终拿到的还是 page=1 的数据。

这个之前有人提过 issue,在最后,作者说在 v2.3.7 版本已经解决,不过我目前使用的版本是 v2.5.0,问题依然存在。

问题原因是 Illuminate/Pagination/PaginationServiceProvider.php 文件中这几行代码:

Paginator::currentPageResolver(function ($pageName = 'page') {
    $page = $this->app['request']->input($pageName);

    if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) {
        return (int) $page;
    }

    return 1;
});

目前的解决方法是重写分页函数,例如 simplePaginate

新建文件 app/Models/Builder.php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder as BaseBuilder;

class Builder extends BaseBuilder
{
    public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
    {
        $request = request();

        $page = $page ?: $request->get($pageName);
        if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) {
            $page = (int)$page;
        } else {
            $page = 1;
        }

        $this->skip(($page - 1) * $perPage)->take($perPage + 1);

        return $this->simplePaginator($this->get($columns), $perPage, $page, [
            'path' => $request->url(),
            'pageName' => $pageName,
        ]);
    }
}

更新文件 app/Models/Post.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model as BaseModel;

class Post extends BaseModel
{
    public function newEloquentBuilder($query)
    {
        return new Builder($query);
    }
}

项目比较小,目前就遇到这些坑。

Docker 测试