欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

关于 Stone-Web 和 Stone-Server# 两种方式 性能差异

大家好, 这是我最近实验的一个方案, 已经基本完成。 里面的一些思想已经在线上运行, 但是没有形成系统, 现在整理起来, 希望感兴趣的朋友们多多指教, 谢谢大家。

Stone优化原理

快速入口:

  • Stone项目地址:https://github.com/StoneGroup/stone 2016-06-07 更新地址, 原有地址包含部分公司信息, 不被允许, 所以删除了公司信息重新发布。

如果你正在考虑框架性能优化的问题, 你对PHP应该已经有足够的了解了。 如你所知, PHP每次的每次请求结束, 都会释放掉执行中建立的所有资源。这样有一个很大的好处:PHP程序员基本不用费力去考虑资源释放的问题,诸如内存,IO句柄,数据库连接等,请求结束时PHP将全部释放。PHP程序员几乎不用关心内存释放的问题,也很难写出内存泄露的程序。这让PHP变得更加简单容易上手, 直抒心意。但是也带来了一个坏处:PHP很难在请求间复用资源, 类似PHP框架这种耗时的工作, 每次请求都需要反复做——即使每次都在做同样的事情。也正因为如此,在PHP发展过程中,关于是否使用框架的争论也从未停止过。

Stone主要优化的就是这个问题。 在框架资源初始化结束后再开启一个FastCGI服务,这样, 新的请求过来是直接从资源初始化结束后的状态开始,避免每次请求去做资源初始化的事情。所以, 本质上, Stone运行时是常驻内存的,它和PHP-FPM一样,是一个FastCGI的实现,不同的是, FPM每次执行请求都需要重新初始化框架, Stone直接使用初始化的结果。

同样,事情总是有好有坏。坏处是:PHP编程变得更难了, 你需要考虑内存的释放,需要关心PHP如何使用内存。甚至, 你需要了解使用的框架,以免『不小心』写出让人『惊喜』的效果。同时, PHP的调试变得更难, 因为每次修改程序后需要重启进程才能看到效果。事实上开发Stone时针对这方面做了不少工作。好处是:程序的性能得到极大的提高。 当然, 客观上的一些利好因素是: PHP的内存回收已经相当稳定和高效, Swoole稳定性已经在相当多的项目中得到验证,Laravel代码质量相当高。

在设计Stone时的另外一个目的是简单。 希望使用者能5分钟完成部署, 并且不需要对原有功能进行改造,可以安全地停止使用。

1. 关于 Stone-Web 和 Stone-Server#

Stone支持两种运行方式, Web方式用来优化Web页面的执行, 执行流程和现在的Laravel页面完全一致。 Server方式用来解决对性能要求很高的场合, 执行流程与artisan command一致, 绕过了laravel MVC的流程, 需要自己去实现请求处理的Handler。

比如一个抢购活动, 想在用户实际下单前拦截请求避免对订单系统造成冲击, 可以使用Stone-Server实现一个抢购功能, 获得抢购资格的用户才进入下单流程。

2. 性能对比#

应用类型原始LaravelStone-WebStone-Server
laravel5 默认页面1503000--
laravel5 简单接口15030008500
laravel4 实际项目简单页面701000--
laravel4 简单接口120--8200
laravel4 实际项目首页35380--

测试环境如下:

PHP 5.6.17-0+deb8u1 (cli) (built: Jan 13 2016 09:10:12)
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2015 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2015, by Zend Technologies
Linux office 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt20-1+deb8u3 (2016-01-17) x86_64 GNU/Linux
16核 Intel(R) Xeon(R) CPU E5-2640 v2 @ 2.00GHz
16G 内存
Laravel 4.2
Laravel 5.2

Stone快速指南

注意:

  1. 如果你不能在5分钟内完成部署, 你应该停止使用Stone了 :blush:
  2. 目前Stone处在alpha阶段,本文档随时可能会失效

特别提示:

  1. 新项目中尝试Stone, 建议从Stone-Server开始, 这是一个针对API的优化方案。
  2. 已有项目中尝试Stone,建议从Stone-Web开始, 这是一个针对Web的优化方案。
  3. 请阅读一下风险提示

Stone安装指引#

安装Stone#

  1. 安装依赖包

    sudo pcel install swoole
    sudo pcel install runkit
  2. composer安装Stone

    laravel 5:

    composer require qufenqi/stone:dev-master

    laravel 4:

    composer require qufenqi/stone:dev-laravel-4.x
  3. 修改config/app.php, 加载Stone的Service Provider,

    注意Laravel4的配置文件路径和写法有细微差别。

    'providers' => [
        // laravel定义的provider
        Illuminate\Auth\AuthServiceProvider::class,
        Illuminate\Broadcasting\BroadcastServiceProvider::class,
        ....
        .... // 中间省略的其他provider
        ....
        Qufenqi\Stone\StoneServiceProvider::class,
    
        // 应用层定义的provider
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,
    
    ]
  4. 配置Stone: 新建 config/stone.php

    注意Laravel4的配置文件路径有细微差别。

    return [
    
        // server模式配置
        'server' => [
            'handler' => 'App\Servers\Handler', // request handler
            'user' => 'apple', // run user
            'group' => 'apple', // run group
            'domain' => '/var/run/stone-server-fpm.sock',
            'pid' => '/run/stone-fpm.pid',
            'process_name' => 'stone-server-fpm',
            'worker_num' => 30,
        ],
    
        // web模式配置
        'web' => [
            'user' => 'apple', // run user
            'group' => 'apple', // run group
            'domain' => '/var/run/stone-web-fpm.sock', // unix domain socket
            'pid' => '/run/stone-web.pid',
            'process_name' => 'stone-web-server',
            'worker_num' => 30,
    
            // 需要建立快照的绑定
            'snap_bindings' => [
                'view',
                'cookie',
                'session',
                'session.store',
                //'config', // debugbar 需要重置config
            ],
    
        ],
    ];

在Laravel 5 项目上使用Stone-Web:#

  1. 修改app/Http/Kernel.php, 让Stone的Kernel接管请求的处理。

    // 根据当前的运行sapi决定使用哪个kernel来处理请求, 这样FPM和Stone可以完全使用一套程序
    if (php_sapi_name() == 'cli') {
        class BaseKernel extends StoneKernel {}
    } else {
        class BaseKernel extends HttpKernel {}
    }
    
    class Kernel extends BaseKernel
    
  2. 运行Stone-Web, Web模式处在开发阶段, 所以默认不会以deamon模式启动, 便于调试

    sudo php ./public/index.php
  3. 修改nginx配置

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_index index.php;
        # fastcgi_pass unix:/var/run/php5-fpm.sock; # PHP-FPM
        fastcgi_pass unix:/var/run/stone-web-fpm.sock; # Stone
        include fastcgi_params;
    }
    sudo nginx -s reload
  4. 完成

在Laravel 4 项目上使用Stone-Web#

  1. 修改public/index.php与bootstrap/start.php, 让Stone的Kernel接管请求的处理。

    // 修改public/index.php
    if (PHP_SAPI == 'cli') {
        define('STONE_WEB_MODE', true);
        $_SERVER['RUNENV'] = 'local';
    }
    // 修改bootstrap/start.php
    if (defined('STONE_WEB_MODE')) {
        $app = new Qufenqi\Stone\Foundation\Application;
    } else {
        $app = new Illuminate\Foundation\Application;
    }
  2. 运行Stone-Web, Web模式处在开发阶段, 所以默认不会以deamon模式启动, 便于调试

    sudo php ./public/index.php
  3. 修改nginx配置

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_index index.php;
        # fastcgi_pass unix:/var/run/php5-fpm.sock; # PHP-FPM
        fastcgi_pass unix:/var/run/stone-web-fpm.sock; # Stone
        include fastcgi_params;
    }
    sudo nginx -s reload
  4. 完成

在Laravel 5 中使用Stone-Server#

  1. 修改app\Console\Kernel.php

    protected $commands = [
        // Commands\Inspire::class,
        \Qufenqi\Stone\Console\Commands\StoneServer::class, // 添加这一行
    ];
  2. 定义请求处理类, 我定义在app\Servers\Handler.php

    注意 这个其实就是 stone.php 配置里的 server.handler

    <?php namespace App\Servers;
    
    use Qufenqi\Stone\Contracts\RequestHandler;
    use Response;
    
    class Handler implements RequestHandler
    {
        public function process()
        {
            return Response::make('hello, stone server!');
        }
    
        public function onWorkerStart()
        {
    
        }
    }
  3. 运行Stone-Server

    sudo php ./artisan stone:server
  4. 修改nginx配置

    location /server/ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_index index.php;
        fastcgi_pass unix:/var/run/stone-server-fpm.sock; # Stone
        include fastcgi_params;
    }
    sudo nginx -s reload
  5. 完成

在Laravel 4 中使用Stone-Server#

  1. 修改app\start\artisan.php

    Artisan::add(new Qufenqi\Stone\Console\Commands\StoneServer);
  2. 定义请求处理类, 我定义在app\Servers\Handler.php

    注意 这个其实就是 stone.php 配置里的 server.handler

    <?php namespace App\Servers;
    
    use Qufenqi\Stone\Contracts\RequestHandler;
    use Response;
    
    class Handler implements RequestHandler
    {
        public function process()
        {
            return Response::make('hello, stone server!');
        }
    
        public function onWorkerStart()
        {
    
        }
    }
  3. 运行Stone-Server

    sudo php ./artisan stone:server
  4. 修改nginx配置

    location /server/ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_index index.php;
        fastcgi_pass unix:/var/run/stone-server-fpm.sock; # Stone
        include fastcgi_params;
    }
    sudo nginx -s reload
  5. 完成

Stone进阶指南

1. Stone的使用风险#

使用Stone的一个很重要的事情是, 需要始终对内存使用抱有敬畏之心。 在未充分了解风险前, 请不要在实际项目中轻易尝试,否则可能产生非常严重的后果!!

比如:Laravle的Cookie实现了单例模式, 是为了让开发者在任何地方可以往Response里追加cookie, 使用:

Cookie::queue('key', 'value', 60);

这在PHP-FPM下不会有任何问题。 但Stone常驻内存后, 请求的资源没有在请求结束后释放, 因此上一个请求在Cookie在下一个请求中仍然存在。 想像下, 如果这是一个关于Session Id的cookie, 会产生多么严重的后果。 同样,Laravel的Auth对象, 也可能出现类似的问题

内存泄露什么的可能只是影响稳定性, 但是这个处理不当会带来难以估计的损失, 这是使用Stone或者类似常驻内存方案的最大风险。

2. Boot Service Provider 与 Request Service Provider#

使用Stone(或者使用类似常驻内存解决方案)最重要的一点, 是需要区分哪些资源可以请求间共享, 哪些资源不能共享。这在 Stone中被区别为: boot service provider 和 request service provider。

  • boot service provider在进程初始化时被执行, 请求间共享。
  • request service provider在请求时执行, 请求间不共享, 每次请求重新执行。
  • laravel原来定义的service provider默认都是boot service Provider, 除非你在stone配置里重新定义。

比如, Laravel的路由规则的解析, 是不需要每次请求都去执行的,而在一个时间较长的项目中, 路由规则可能有几千行,解析这些规则需要耗费不少时间, 所以设置成boot service provider是比较合适的。 而有一些service provider是需要每次都执行的,比如debugbar, 这些设置成request service provider比较合适。

3. 实例快照与runkit#

还有一种情况,不需要每次执行具体的代码, 但是需要做一些重置操作。 比如Cookie, Laravel将cookie放到Response的数组中, 在结束响应时发送到浏览器端,请求结束后并没有清空而带到了下一个请求。 Stone的一个解决办法是将Cookie在创建时建立一个快照, 保存当前的状态, 等请求结束时再通过快照恢复, 避免Cookie被带到下一个请求的问题。

这个在config/stone.php里可以定义。

runkit在这个过程中的意义是给予PHP运行时注入实例的能力。 在app初始化时通过runkit注入指定实例, 让他们具有快照恢复的功能, 再建立快照, 请求结束后再通过快照恢复。

快照和request service provider都能解决Cookie在请求间共享的问题, 快照的方式避免了再次执行service provider里register和boot的工作, 效率会更好一些。 如果你只是需要每次请求得到一个新的实例, 而不是需要在请求中再次执行一段程序, 使用快照的方式会更好一些。

4. 理解请求间共享资源#

这是一把双刃剑, 利用好了性能能得到极大提升, 否则就是bug的深渊。

比如, 我们常需要实现权限系统, 如果能在初始化的时候将权限系统加载, 并在请求间共享, 这样每次请求就不需要再去从数据库里加载解析权限规则, 这样效率能得到提高。 利用好了这一点, 有助于你写出更高效的程序。

而如果你没理解好这一点, 比如你使用了一个单例模式, 这个实例被维持在类的静态变量里, 因此不会在请求结束后自然销毁。 而这个实例在请求间共享又会出现问题,类似cookie的问题,这样就会造成bug。

5. 沙盒模式#

我希望在未来能支持沙盒模式, 在初始化后建立一个沙盒, 把请求防止到沙盒里执行, 这样就可以更安全方便的实现。

设计Stone的一些想法

1. 保持与PHP-FPM的兼容#

这样做能带来几个好处:

  1. 调试方便, 在开发中, 程序员完全可以使用php-fpm来开发, 这样可以避免开发时反复重启进程的问题。 当然, 测试时还是应该使用Stone,免得一些问题需要在线上时才发现。
  2. 使用方便, 5分钟内快速使用这个目标不会改变
  3. 停用方便, 出现一些暂时无法解决的问题的时候, 可以通过修改nginx配置快速切换会PHP-FPM

2. 什么场合下适合使用Stone#

Stone的目标定位于解决已有PHP程序的性能问题。 随着开源程序的越来越完善, 现在解决高并发问题的技术方案越来越多, 有些已经非常成熟。 Stone的优势在于在解决性能问题的同时可以100%重用现在的业务逻辑。

比如, 现有系统中需要加入一个抢购的功能, 我们可能会在抢购之前根据业务规则进行流量拦截, 可能需要使用到redis, 现有的用户系统,现有的业务规则。 使用Stone-Server, 你可以直接使用。 但是如果你使用其他语言的解决方案, 你可能需要把这些规则使用另外的语言再实现一遍。 这加大了开发和维护的成本。

3. 继续降低使用Stone的难度#

使用Stone很可能会踩坑。 一方面可能是开发者对于运行机制的理解不充分; 一方面可能是现有PHP程序没有考虑请求结束后的资源销毁的问题; 也可能是Stone本身程序存在一些bug。 Stone会持续完善, 并尽量对应用程序提供一些保护机制, 降低程序使用的难度。

在其他框架下使用Stone

由于精力有限, 暂时不考虑其他框架, 但是应该是可以较快移至到其他框架的。 如果你有兴趣, 不妨fork代码自己实现一下。

问题反馈

希望感兴趣的朋友能积极给我反馈, 甚至参与到Stone的开发中来, 我们一起完善。 我的邮箱是: rssidea(at)qq.com

 本帖已被设为精华帖!
 
回复数量: 17
  • maxincai
     ⋅ 1年前

    前排占位后慢慢看,大概扫了一眼,非常好的想法。

  • chefxu
    chefxu
     ⋅ 1年前

    @maxincai 谢谢支持, 文档写得比较乱, 打算花点时间重写一下,有疑问或者更好的想法欢迎和我交流。

  • Summer MOD A Life-long learner.
     ⋅ 1年前

    很棒的想法 :thumbsup: :sparkles:

  • Link
    Link
     ⋅ 1年前

    这也太标题党了,不过当我看到整个项目居然是 php 写的时候,震惊了! :100:

  • chefxu
    chefxu
     ⋅ 1年前

    @Link 网络框架用的是swoole扩展, 这个不是php写的 :smiley:

  • 你KTS叔叔
    你KTS叔叔
     ⋅ 1年前

    看上去和 https://github.com/scil/LaravelFly 很像。都不错,表示支持。

  • hu31166
    hu31166
     ⋅ 1年前

    开了opcache不是更好嘛,这样很麻烦,有隐患

  • chefxu
    chefxu
     ⋅ 1年前

    @你KTS叔叔 嗯,的确是提前了解了下laravelfly, 它是基于swoole的http server。 大体优化思路类似, 但是实现细节还是有很多不同。

  • chefxu
    chefxu
     ⋅ 1年前

    @hu31166 opcache只能得到1倍的提升, 当然, 对于大多数应用来说, 这样的性能已经足够了。
    Stone有自己的适应范围, 个人觉得适合流量爆炸性增长的活动场景。 比如搞一个抢购, 可以把抢购流量引入Stone-Server, Stone-Server可以做一些流量清洗工作, 清洗掉恶意的,重复的,不符合要求的请求,最终引导到实际的抢购业务。 在我的环境中,Stone-Server的处理请求能力比laravel要高60~80倍, 当然, 这个是不包含任何业务处理的对比结果。

  • chefxu
    chefxu
     ⋅ 1年前

    刚才拿phphub网站程序试了一下, 性能提升只有3-4倍, 而且有内存泄露的问题 :disappointed: 看来stone-web的优化之路还很漫长啊。

  • zjien
     ⋅ 1年前

    不错呀,加油,很想参与到这样的项目中,但是自身能力还是不够呀,想问下stone是用什么写的?还有github的链接失效啦

  • zhaozhenxiang
    zhaozhenxiang
     ⋅ 1年前

    之前看到的一个laravelfly的,也是使用swoole来维护laravel框架的对象。 不知道那个项目怎么样了。 对于swoole我只是在windows下跑过他的应用,也算是了解一点点。 楼主这么做的结果是加大一点开发成本,减少了很多响应时间。 我支持楼主。加油。 PS:github项目地址404了。

  • yekexuan
     ⋅ 1年前

    地址链接都失效了

  • chefxu
    chefxu
     ⋅ 1年前

    地址更新了, 因为之前的发布项目包含公司信息, 被要求删除了。

  • iVanilla
    iVanilla PHP已掌握 | Python Ruby Go 学习中
     ⋅ 1年前

    反馈一个问题:composer安装失败:

    Installing qufenqi/stone (dev-master ad9e178)
        Cloning ad9e178684058ab569f73e4e88f4ab4a0715683a
        ad9e178684058ab569f73e4e88f4ab4a0715683a is gone (history was rewritten?)
        Failed to download qufenqi/stone from source: Failed to execute git checkout 'ad9e178684058ab569f73e4e88f4ab4a0715683a' -- && git reset --hard 'ad9e178684058ab569f73e4e88f4ab4a0715683a' --
    
    fatal: reference is not a tree: ad9e178684058ab569f73e4e88f4ab4a0715683a
    
        Now trying to download from dist
      - Installing qufenqi/stone (dev-master ad9e178)
        Downloading: Failed
    
    Installation failed, reverting ./composer.json to its original content.
    
      [Composer\Downloader\TransportException]
      The "https://packagist.phpcomposer.com/files/StoneGroup/stone/ad9e178684058
      ab569f73e4e88f4ab4a0715683a.zip" file could not be downloaded (HTTP/1.1 404
       Not Found)
  • skys215
    skys215
     ⋅ 8个月前

    请问有人接过吗……

  • 四不象
    四不象
     ⋅ 4个月前

    是利用Swoole实现了一个fastcgi接口吗?

来自 https://laravel-china.org/topics/2092/5-minutes-to-improve-the-performance-of-laravel-frame-more-tha...
普通分类: