使用workerman加速任意项目

walkor

众所周知,workerman是基于php cli的,由于php cli模式下无法使用php自带的header、sesion、cookie等函数,这导致将传统的php项目无法直接在workerman容器下直接运行。

我一度以为让传统业务在workerman中运行,就必须更改框架甚至业务代码以适配workerman,直到joanhey发了一个issue,打破了我的认知。

他们发布了一个名叫AdapterMan的项目,它可以做到不更改传统框架代码的情况下让你的传统php项目放到workerman中正常运行,并且他们公司已经在生产环境用了2年。

注意,是零代码改动直接让laravel、lumen、Slim等框架的项目在workerman上运行。

目前他们已经在laravel、lumen、Slim、Symfony、CakePHP、Yii2、KumbiaPHP 等做了初步压力测试,性能有很大的提升。

以下是压测结果

Laravel 8

Fw Plaintext Json Single query Multiple query Updates Fortunes
Laravel 14,799 14,770 9,263 3,247 1,452 8,354
Laravel Roadrunner 482 478 474 375 359 472
Laravel Swoole 38,824 37,439 21,687 3,958 1,588 16,035
Laravel Laravel s 54,617 49,372 23,677 2,917 1,255 16,696
Laravel Workerman 103,004 99,891 46,001 5,828 1,666 27,158
Laravel with Workerman % gain 596.02% 576.31% 396.61% 79.489% 14.738% 225.09%

截图

Symfony 6

截图

Fw Plaintext Json Single query Multiple query Updates Fortunes
Symfony 38,231 37,557 12,578 10,741 3,420 10,741
Symfony Workerman 210,796 197,059 107,050 13,401 4,062 71,092

Lumen 9

Fw Plaintext Json Single query Multiple query Updates Fortunes
Lumen 18,998 18,616 10,791 3,496 1,461 9,223
Lumen Swoole 44,861 43,598 24,255 4,178 1,599 16,854
Lumen Laravel s 93,335 82,745 31,567 3,030 1,282 21,130
Lumen Workerman 185,126 177,667 58,729 5,857 1,662 31,430

Slim with Workerman

Without ORM

Framework JSON 1-query 20-query Fortunes Updates Plaintext
Slim 4 38,305 34,272 12,579 32,634 2,097 35,251
Slim 4 Workerman 129,393 81,889 15,803 73,212 2,456 134,531
Slim 4 Workerman pgsql * 102,926 19,637 92,752 14,875

Lumen v9
截图

接入代码类似

<?php
require_once __DIR__ . '/vendor/autoload.php';

use Adapterman\Adapterman;
use Workerman\Worker;

Adapterman::init();

$http_worker                = new Worker('http://0.0.0.0:8080');
$http_worker->count         = 8;
$http_worker->name          = 'AdapterMan';

$http_worker->onWorkerStart = static function () {
    //init();
    require __DIR__.'/start.php';
};

$http_worker->onMessage = static function ($connection, $request) {
    $connection->send(run());
};

Worker::runAll();

项目地址:
https://github.com/joanhey/AdapterMan 强烈建议大家为其点赞(点星星)
相关链接:
https://github.com/walkor/workerman/issues/824

28641 52 36
52个回答

water2023

先顶为敬

muvtou

学习了!

  • 暂无评论
liziyu

赞!已经响应号召送上星星。^_^

  • 暂无评论
chaz6chez

🐂🍺

  • 暂无评论
= - =

老大,不行哇,我拿着 laravel 项目测试运行,静态资源没成功加载到,laravel 的 缓存使用了 phpredis,提示找不到 Redis。

  • = - = 2022-12-15

    而且根据 workerman 文档写的响应静态文件,因为版本依赖过低,导致无法使用 (new Response)->withFile(); 的方法进行处理。

  • Tinywan 2022-12-15

    可以做到不更改传统框架代码的情况下让你的传统php项目放到workerman中正常运行,并且他们公司已经在生产环境用了2年。

  • = - = 2022-12-18

    然鹅官方和我说了 redis 作为 session 驱动时存在问题,正在排查ing?

wolfcode

占位

  • 暂无评论
不败少龙

威武霸气

  • 暂无评论
Tinywan

厉害!已 start

  • banro512 2022-12-17

    对于 dz wp等系统,有否办法直接使用,里面大量依赖 $_GET $_POST等,尤其是 dz 多个入口文件。

WatcherLuo

  • 暂无评论
admin

https://github.com/TechEmpower/FrameworkBenchmarks/pull/7626/files#diff-e853be1cf6b848987afe860157bc6ed090ffe163e868fb96e7c515c787963e89

还是没看懂这么简单的一部分 怎么就ok了,目前看上去好像就是 初始化了request

zh7314

原理是吧workerman当fpm使用,吧session,cookies等使用封装函数代替了,是个不错的想法,但是不能全部直接移植使用,还需要更改一些东西

  • ichynul 2022-12-15

    看原理也不复杂,把一些相关方法替换了。
    然后用ob_get_clean();获取对应框架的输出。

  • zh7314 2022-12-20

    他这个框架还没做到这个程度,应该只是适配了部分fpm的功能

  • xamarin 2023-01-18

    同意

10bang

厉害

  • 暂无评论
wo642436249

可以啊,希望webman的生态越来越好

  • 暂无评论
真的是你呀

我有点不相信不改代码,特别是引入其他库【抠鼻子】

  • 暂无评论
chen

  • 暂无评论
banro512

看起来是对 laravel tp6 这类封装较好的框架有效,如果拿来运行 dz dedecms 还是不行的

  • 缝合 2022-12-16

    应该也可以,他这个思路是disable_function 掉自带函数,然后实现自己的函数。这么玩的话,框架基本上不存在跑起来的问题,问题是有些开发过程中的静态变量可能搞的内存泄漏。需要稍为调整下

powerbowen

迟来的赞

  • 暂无评论
hzz

已star,因为这个帖子才注册的账号,来晚了~

  • 暂无评论
la32ffff

php -c cli-php.ini server.php start 正常
php -c cli-php.ini server.php start -d 就报错

[root@localhost laravel9]# php -c cli-php.ini  server.php  start -d
Adapterman v0.4 OK

Workerman[server.php] start in DAEMON mode
-------------------------------------------- WORKERMAN --------------------------------------------
Workerman version:3.5.33          PHP version:8.1.13           Event-Loop:\Workerman\Events\Select
--------------------------------------------- WORKERS ---------------------------------------------
proto   user            worker          listen                 processes    status           
tcp     root            AdapterMan      http://0.0.0.0:7070    8             [OK]            
---------------------------------------------------------------------------------------------------
Input "php server.php stop" to stop. Start success.

PHP Fatal error:  Uncaught TypeError: fclose(): Argument #1 ($stream) must be of type resource, null given in /mnt/hgfs/www/laravel9/vendor/workerman/workerman/Worker.php:1245
Stack trace:
#0 /mnt/hgfs/www/laravel9/vendor/workerman/workerman/Worker.php(1245): fclose()
#1 /mnt/hgfs/www/laravel9/vendor/workerman/workerman/Worker.php(548): Workerman\Worker::resetStd()
#2 /mnt/hgfs/www/laravel9/server.php(24): Workerman\Worker::runAll()
#3 {main}
  thrown in /mnt/hgfs/www/laravel9/vendor/workerman/workerman/Worker.php on line 1245

Fatal error: Uncaught TypeError: fclose(): Argument #1 ($stream) must be of type resource, null given in /mnt/hgfs/www/laravel9/vendor/workerman/workerman/Worker.php:1245
Stack trace:
#0 /mnt/hgfs/www/laravel9/vendor/workerman/workerman/Worker.php(1245): fclose()
#1 /mnt/hgfs/www/laravel9/vendor/workerman/workerman/Worker.php(548): Workerman\Worker::resetStd()
#2 /mnt/hgfs/www/laravel9/server.php(24): Workerman\Worker::runAll()
#3 {main}
  thrown in /mnt/hgfs/www/laravel9/vendor/workerman/workerman/Worker.php on line 1245
PHP Fatal error:  Uncaught TypeError: fclose(): Argument #1 ($stream) must be of type resource, null given in /mnt/hgfs/www/laravel9/vendor/workerman/workerman/Worker.php:1245
  • xamarin 2023-01-18

    今天的版本应该不报个错了 作者的workman已经是4.1

la32ffff

我的修改 不知道影响不影响

public static function resetStd()
    {
        if (!static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) {
            return;
        }
        global $STDOUT, $STDERR;
        $handle = \fopen(static::$stdoutFile, "a");
        if ($handle) {
            unset($handle);
            \set_error_handler(function(){});
            $STDOUT && \fclose($STDOUT); //新增判断
            $STDERR && \fclose($STDERR); //新增判断
            \STDOUT &&  \fclose(\STDOUT); //新增判断
            \STDERR &&   \fclose(\STDERR); //新增判断
            $STDOUT = \fopen(static::$stdoutFile, "a");
            $STDERR = \fopen(static::$stdoutFile, "a");
            // change output stream
            static::$_outputStream = null;
            static::outputStream($STDOUT);
            \restore_error_handler();
            return;
        }

        throw new Exception('Can not open stdoutFile ' . static::$stdoutFile);
    }
a784910468

webman和adaptman相当于是两种解决方案了吧,一种是当作fpm,一种是直接在fpm里写服务。性能应该还是webman好吧

  • 暂无评论
ts0523481

这个怎么用啊,现有的项目直接composer require joanhey/adapterman?

  • 暂无评论
artisan
牛逼,关注
  • 暂无评论
古树

如果可以完美兼容,感觉webman就不香了

  • tanhongbin 2023-05-18

    还是webman香,性能、写法、和后面的各种组件看,还是webman还用

古树

@walkor 这个功能应该官方来搞

  • 暂无评论
la32ffff

laravel9 还测试到两个问题 一个静态文件加载失败
第二个上传图片失败

Workerman version:3.5.34          PHP version:8.1.3
------------------------ WORKERS -------------------------------
worker               listen                              processes status
AdapterMan           http://0.0.0.0:8080                 8         [ok]
Error: Call to a member function getClientOriginalName() on string in E:\www\laravel9\vendor\laravel\telescope\src\Watchers\RequestWatcher.php:164
Stack trace:
#0 [internal function]: Laravel\Telescope\Watchers\RequestWatcher->Laravel\Telescope\Watchers\{closure}('files', 'name')
#1 E:\www\laravel9\vendor\laravel\telescope\src\Watchers\RequestWatcher.php(167): array_walk_recursive(Array, Object(Closure))
#2 E:\www\laravel9\vendor\laravel\telescope\src\Watchers\RequestWatcher.php(54): Laravel\Telescope\Watchers\RequestWatcher->input(Object(Illuminate\Http\Request))
#3 E:\www\laravel9\vendor\laravel\framework\src\Illuminate\Events\Dispatcher.php(421): Laravel\Telescope\Watchers\RequestWatcher->recordRequest(Object(Illuminate\Foundation\Http\Events\RequestHandled))
#4 E:\www\laravel9\vendor\laravel\framework\src\Illuminate\Events\Dispatcher.php(249): Illuminate\Events\Dispatcher->Illuminate\Events\{closure}('Illuminate\\Foun...', Array)
#5 E:\www\laravel9\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php(142): Illuminate\Events\Dispatcher->dispatch('Illuminate\\Foun...')
#6 E:\www\laravel9\public\start.php(61): Illuminate\Foundation\Http\Kernel->handle(Object(Illuminate\Http\Request))
#7 E:\www\laravel9\server.php(21): run()
#8 E:\www\laravel9\vendor\workerman\workerman\Connection\TcpConnection.php(656): {closure}(Object(Workerman\Connection\TcpConnection), Array)
#9 E:\www\laravel9\vendor\workerman\workerman\Events\Select.php(292): Workerman\Connection\TcpConnection->baseRead(Resource id #142)
#10 E:\www\laravel9\vendor\workerman\workerman\Worker.php(2410): Workerman\Events\Select->loop()
#11 E:\www\laravel9\vendor\workerman\workerman\Worker.php(1406): Workerman\Worker->run()
#12 E:\www\laravel9\vendor\workerman\workerman\Worker.php(1349): Workerman\Worker::forkWorkersForWindows()
#13 E:\www\laravel9\vendor\workerman\workerman\Worker.php(547): Workerman\Worker::forkWorkers()
#14 E:\www\laravel9\server.php(24): Workerman\Worker::runAll()
#15 {main}
Worker process terminated
  • la32ffff 2022-12-22

    可能是workerman3.5的bug 希望能早点升级到4.*

  • xini2603 2022-12-26

    对于laravel 应在请求时初使化一些laravel的单例,不初使化有可能会出现单例污染情况如:auth
    在run中加入:$kernel->getApplication()->forgetInstance('auth');
    依次类推荐!

hungmou

没看懂咋用

  • 暂无评论
senge520

有没有大佬分析下这个实现原理是啥呢?

  • 暂无评论
euii

我在使用redis-queue,用的是workerman 4.*,希望早日升级到workerman 4.0,让我试试。

  • 暂无评论
xini2603

对于laravel 应在请求时初使化一些laravel的单例,不初使化有可能会出现单例污染情况如:auth如果不重置,会导致登陆后,
返回的是第一个登陆的用户信息,
在run中加入:$kernel->getApplication()->forgetInstance('auth');如下,当然还有其它组件,用啥加啥

$kernel->getApplication()->forgetInstance('auth');
$response = $kernel->handle(
            $request = Illuminate\Http\Request::capture()
        );

具体有多少单例是通过 判断变量返结果的,得大家使用中发现或者参考:swoole与laravel的整合了完善!
目前用于生产应核有点难,除非用的组件少,

  • 493226876 2023-01-17

    加了
    $kernel->getApplication()->forgetInstance('auth');
    还是会污染 其他地方打开页面出现的是第一个的session

toooooop

thinkphp6能用了么

xianrenqh

顶顶更健康

  • 暂无评论
xini2603

laravel 使用得自己加一条动态路由实现,静态路由会有一个问题:控制器中的 ,__construct 方法只会执行首次,路由也是按单例模式只初使化一次,要吗得按webman方法改变一下路由为每次初使化,当然不使用__construct魔术方法就好了

  • ab0029 2023-01-11

    这个对低版本laravel很有用只是需要手动清理下单例那些,可以参考官方Octane扩展去清理,高版本直接用自带的官方扩展,或者有人已经出了workerman Octane的驱动了

ncwsky

HttpForPHP 我这个项目也是一样的 为什么就没有人关注下
使用workerman实现http服务,把现有其他框架的代码简单改为常驻内存http服务,未实现session支持,最适合用于接口服务,已有在yii项目中运行,参见自带的yii示例。
原理就是对PHP的$_SERVER $_COOKIE $_FILES $_REQUEST $_POST $_GET全局变量重置数据
$_SESSION太麻烦就没有处理 毕竟只针对接口应用的服务 所以就没有必要

主页:https://github.com/ncwsky/HttpForPHP

  • wo642436249 2023-01-03

    给你一个star

  • ncwsky 2023-01-03

    3q

  • banro512 2023-01-10

    充分说明了:不管再好的项目,没有详细清晰的文档,也难以吸引关注。
    至少要写出这个项目的使用场景、解决的问题、如何在项目中引用,比如laravel中、lumen中或者其他三方项目中如何接入

  • ak47f16200 2023-01-15

    你这个有项目在跑吗?如果接入,是不是也是零修改?

  • ak47f16200 2023-01-16

    哈哈,零修改,基本是跑起来了。回头试下压测看看效果

  • ncwsky 2023-01-17

    项目的yii示例就是在线运行的 通过nginx匹配符合的请求代理到服务里处理

  • ncwsky 2023-01-17

    零修改 不现实 针对自定义的header http_code还是需要处理的 就像yii那个示例里的一样

  • ak47f16200 2023-02-19

    file_get_contents('php://input')这个卡住了,,像微信支付回调都是用这个接收的,你是怎么搞的?不会都类库都自己处理一下吧??

  • ncwsky 2023-02-28

    这是我yii项目里easywechat库我的处理方式 仅供参考
    $app = Factory::officialAccount($config);
    $app->request = WeixinRequest::createFromGlobals(); //重置wx的request
    $app->request->setContent(\Yii::$app->request->getRawBody()); //常驻内存时推送内容置入

suendy

文件上传是不是需要单独处理啊,其他的都没有问题,就是上传文件这个地方不管用

  • 暂无评论
xianrenqh

thinkphp6咋用的啊

  • 暂无评论
极胜100

我尝试了一下适配现有项目,现有项目使用的lumen,发现http post body 里面的值,在lumen 的$request对象里获取不到,是我使用姿势不对吗?

  • xini2603 2023-01-07

    $request 在有些地方获取不到,不过你可以换成 request() 就获取到了

  • 极胜100 2023-01-09

    试了一下,还是不行的。看起来有些地方还是有问题,没办法直接用

  • ab0029 2023-01-11

    参考Octane扩展,入口去初始化request即可

ab0029

看了下,骚操作呀,应该是可以适配任意项目,本质就直接禁止掉部分系统函数,手动实现去适配

  • 暂无评论
ichynul

最大的障碍就是静态引用。
所以说零代码改动是不可能的。

  • 暂无评论

这个需要php8 老项目版本一般比较低,会兼容吗?

xiaobai

不明所以,随便看看

  • 暂无评论
cshaptx4869

thinkphp6 加速失败 各种报错...

  • 暂无评论
jetlong

安装报错 composer require joanhey/adapterman

Problem 1
    - Root composer.json requires joanhey/adapterman ^0.5.5 -> satisfiable by joanhey/adapterman[0.5.5].
    - joanhey/adapterman 0.5.5 requires workerman/workerman ^3.5 -> found workerman/workerman[v3.5.0, ..., v3.5.34] but it conflicts with your root composer.json require (^4.1).

是我用tp6,已经安装过workerman了,版本较高

  • 冬至 2023-03-25

    tp5可以用吗

  • jetlong 2023-03-27

    没用过,现在tp6 + 8.1 还是不能用,启动会报错,没继续弄这个了

lvshuang

牛逼的项目

  • 暂无评论
euii

Test environment:
Mac 13.3.1
Workerman version:4.1.5
PHP version:8.1.17
Event-Loop:\Workerman\Events\Select
Adapterman: 0.6.1
Laravel : 9.33.0
当我运行 php server.php start 时是可正常使用的,但是当我运行php server.php start -d 在访问接口的时候就报错了,错误在这个帖子里。
https://github.com/joanhey/AdapterMan/issues/36

我现在的疑问是 workerman的守护模式和非守护模式有什么不一样的地方?不知道有没有人遇到这样的问题

  • 暂无评论
xini2603

laravel 文件上传处理,更改原码中 http.php 500行,下面的switch部分为:

 switch ($header_key) {
                    case "content-disposition":
                        // Is file data.
                        if (\preg_match('/name="(.*?)"; filename="(.*?)"/', $header_value, $match)) {
                            $error = 0;
                            $tmp_file = '';
                            $file_name = $match[2];
                            $size = \strlen($boundary_value);
                            $tmp_upload_dir = \Workerman\Protocols\Http::uploadTmpDir();
                            if (!$tmp_upload_dir) {
                                $error = UPLOAD_ERR_NO_TMP_DIR;
                            } else if ($boundary_value === '' && $file_name === '') {
                                $error = UPLOAD_ERR_NO_FILE;
                            } else {
                                $tmp_file = \tempnam($tmp_upload_dir, 'workerman.upload.');
                                if ($tmp_file === false || false === \file_put_contents($tmp_file, $boundary_value)) {
                                    $error = UPLOAD_ERR_CANT_WRITE;
                                }
                            }
                            $upload_key = $match[1];
                            // Parse upload files.
                            $_FILES[$upload_key] = [
                                'name' => $file_name,
                                'tmp_name' => $tmp_file,
                                'size' => $size,
                                'error' => $error,
                                'type' => '',
                            ];
                            break;
                        } // Is post field.
                        else {
                            // Parse $_POST.
                            if (\preg_match('/name="(.*?)"$/', $header_value, $match)) {
                                //TODO search a fast solution
                                $post_encode_string .= urlencode($match[1]) . '=' . urlencode($boundary_value) . '&';
                            }
                        }
                        break;
                    case "content-type":
                        // add file_type
                        $_FILES[$upload_key]['type'] = \trim($header_value);
                        break;
                }
  • 暂无评论
fengchujun

php7.X 能跑吗?装了一下提示PHP版本不兼容,看了一下貌似要 PHP8 以上

  • 缝合 2023-10-23

    不可以。因为php8 支持 disable_function 的函数重新定义。在8之前会报错。

dignfei

唯一的问题: 代码创建的 静态变量 怎么解决?如何一键清理所有静态变量?

  • tanhongbin 2023-07-20

    这个没得搞,就怕静态 数组 无限添加 必然内存泄露

  • dignfei 2023-07-20

    实现不了吗,一键删除所有静态变量?

dignfei

需要老大修改一下workerman,来解决内存泄漏的问题
$http_worker->onMessage = static function ($connection, $request) {
$connection->send(run());
};
这里onMessage 需要改一下。增加一个控制的开关:实现每次收到消息后都用新进程处理,解决静态变量、内存泄漏的问题:
方法:
onWorkerStart执行一些需要提前加载的代码之后,worker进程提前fork出很多子进程备用,
然后每次 onMessage 接收到消息都用fork出的进程处理,用完就销毁,同时fork一个新的。

其中涉及到信号的处理,fork出的进程如何接收数据和主进程通讯等

  • 暂无评论

看时间节点,已经使用两年半了,哈哈哈哈

  • 暂无评论
zjkal✅

明天试试在Thinkphp6或者8上面是什么效果

  • 暂无评论
王二狗

加速的话对服务器内存要求是多少呀,资源消耗会很大嘛

  • 缝合 2023-10-23

    内存占用会比php-fpm 少很多的。对比那些php-fpm开很多的服务来说

年代过于久远,无法发表回答
×
🔝