Laravel是目前比较好的PHP框架,为工匠设计。在熟悉以后,其丰富的特性可以极大的提升开发的效率。但是Laravel有一个致命的缺点:相对于其他的MVC框架来说,性能差强人意。

这里我们使用Laravel 5.1.6,PHP 5.6.9,PHP-FPM的组合进行测试,测试页面为Laravel默认的welcome。经过反复试验,PHP-FPM的worker的数量对测试结果没有明显的影响

Alt text

RPS仅仅只有30.4,这性能表现对于一个正常的Hello World性质的welcome页面来说实在是太差

到底性能差在什么地方?框架加载时间

我们在Laravel的入口文件里加上时间计算
Alt text

访问一次后

Alt text

仅仅只是计算了框架初始化的时间,还并没真正进入到业务的逻辑就已经消耗了这么长的时间

如何解决或者说缓解这个问题,使我们可以开发效率和性能兼得呢?用Swoole

PHP-FPM的工作原理是对于每次请求,环境及上下文都是全新的。因为它并不知道相邻请求之间有没有关联关系,可能前一个请求是访问Wordpress,后一个请求却是Joomla,它只能在接收到请求之后载入PHP文件,处理完后在结束时将所有资源释放掉等待下一个请求。对于使用Laravel的应用来说,并不需要PHP-FPM这样的特性,最好是让Laravel框架的核心保持在内存里,免去每次请求的框架重复加载工作

Swoole的HttpServer可以解决这个问题。HttpServer和Node.js、Python的Tornado类似,可以使框架驻留在内存中,并且具有较强的网络性能,能直接作为Web前端服务器

谢谢Laravel干净的代码,使我们只需要进行少许的改造就可以在Swoole中运行Laravel了

首先分析Laravel的入口文件
index.php

<?php
/**
 * Laravel - A PHP Framework For Web Artisans
 *
 * @package  Laravel
 * @author   Taylor Otwell <[email protected]>
 */
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. It feels nice to relax.
|
*/
require __DIR__.'/../bootstrap/autoload.php';
/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/
$app = require_once __DIR__.'/../bootstrap/app.php';
// 上述的开销可以省去

/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); // 初始化业务核心
$response = $kernel->handle( // 走完业务流程后获取到$response
    $request = Illuminate\Http\Request::capture() // 将$_SERVER, $_GET, $_POST, $_FILE等转换为Laravel的$request
);
$response->send(); // 将$response发送出去
$kernel->terminate($request, $response); // 结束业务核心

一个修改的示例

<?php
use Illuminate\Http\Request as IlluminateRequest;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class Server
{
    private $swoole_http_server;
    private $laravel_kernel;
    //创建swoole http服务器
    function __construct($host, $port)
    {       
        $this->swoole_http_server = new swoole_http_server($host, $port);
        
        //swoole配置项
        $this->swoole_http_server->set([
	        'max_conn' => 1024,
	        'timeout' => 2.5,
	        'poll_thread_num' => 8, //reactor thread num
			'writer_num' => 8,     //writer thread num
			'worker_num' => 8,    //worker process num
			'max_request' => 4000,
			'dispatch_mode' => 1,
			'log_file' => __DIR__ . '/log/log',
			'daemonize' => 0
        ]);
    }
    
    public function start()
    {
        //注册workerStart回调
        $this->swoole_http_server->on('WorkerStart', array($this, 'onWorkerStart'));
        //注册request回调
        $this->swoole_http_server->on('request', array($this, 'onRequest'));
    
        //启动swoole
        $this->swoole_http_server->start();
    }
    
    public function onWorkerStart($serv, $worker_id)
    {
        require __DIR__ . '/../bootstrap/autoload.php';
        $app = require __DIR__.'/../bootstrap/app.php';
        $this->laravel_kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
        
        Illuminate\Http\Request::enableHttpMethodParameterOverride();
    }
    
    public function onRequest($request, $response)
    {
        // 转换为Swoole的请求对象
        $get = isset($request->get) ? $request->get : array();
        $post = isset($request->post) ? $request->post : array();
        $cookie = isset($request->cookie) ? $request->cookie : array();
        $server = isset($request->server) ? $request->server : array();
        $files = isset($request->files) ? $request->files : array();
        
        $server = array_change_key_case($server, CASE_UPPER);

        // 创建illuminate_request
        $illuminate_request = IlluminateRequest::createFromBase(
            new SymfonyRequest($get, $post, array(), $cookie, $files, $server)
        );
        
        //把illuminate_request传入laravel_kernel后,取回illuminate_response
        $illuminate_response = $this->laravel_kernel->handle($illuminate_request);
        
        // 转换为Swoole的响应对象
        // status
        $response->status($illuminate_response->getStatusCode());
        // headers
        foreach ($illuminate_response->headers->allPreserveCase() as $name => $values) {
            foreach ($values as $value) {
                $response->header($name, $value);
            }
        }
        // Cookies
        foreach ($illuminate_response->headers->getCookies() as $cookie) {
            $response->cookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
        }
        // Content
        $content = $illuminate_response->getContent();
        // Send content & Close
        $response->end($content);
        
        // 结束请求
        $this->laravel_kernel->terminate($illuminate_request, $illuminate_response);
    }
}

在一开始的测试环境下改用Swoole来运行Laravel,测试结果如下
Alt text

与使用PHP-FPM相比有17.9x的加速

看上去很完美?其实有一点美中不足的地方,swoole_http_request->$files是无法兼容PHP内置的is_uploaded_filemove_uploaded_file函数的。在 Laravel 中使用Validator校验上传的文件时,由于Validator使用了is_uploaded_file,校验会失败。虽然可以使用apd之类的扩展替换掉内置函数,但是生产中并不建议这样做,还是需要其他方式的 workaround