Laravel编译级性能的服务模式可行性与前景

利益相关

PRipple协程 推介

不久前我写了一篇文章《无需修改任何代码和扩展将你的Laravel项目性能提高20倍》
受到了许多朋友的关注, 事实上性能提升只是它的附加项, 真正的大杀器是它的服务模式设计

今天就这个话题聊聊Laravel服务模式的可行性

毋庸置疑Laravel是最优雅的PHP框架, 它的设计思想和代码质量都非常优秀, 但在性能方面的表现却不尽如人意,若从Laravel内核开发者的角度来看

他们是否放弃了对性能的考虑?

我的观点是否定的,官方并未强调过Laravel应该在FPM/Swoole/FrankenPHP环境运行, 他们更注重的是
专注于设计本身, 我反而觉得就是这种专注于设计本身而非语言的态度使得Laravel成为了最优雅的框架

现代化框架的设计包容性

这就是我所说的专注于设计本身, 即包容多种构建模式

从我们从FPM开始, Laravel提供了一个入口文件public/index.php

<?php
use Illuminate\Http\Request;

\define('LARAVEL_START', \microtime(true));

// Determine if the application is in maintenance mode...
if (\file_exists($maintenance = __DIR__ . '/../storage/framework/maintenance.php')) {
    require $maintenance;
}

// Register the Composer autoloader...
require __DIR__ . '/../vendor/autoload.php';

// Bootstrap Laravel and handle the request...
(require_once __DIR__ . '/../bootstrap/app.php')->handleRequest(Request::capture());

我们都知道,执行该文件可能会发生以下事情

容器初始化=>服务注册(配置=>路由=>中间件)=>控制器=>渲染=>返回

从该流程中包括模板引擎的甚至在整个项目中都找不到一句echo语句

这些MVC框架在做一件什么事? 它们只是为FPM提供了一个驱动入口以实现

$application->handle($request);

这就是FPM的工作方式:

每次处理请求时,都会重新加载所有的文件和依赖

这导致了重复的文件解析和加载,影响了整体性能。

可行的取代方案

例如考虑这个流程

容器初始化=>服务注册(配置=>路由=>中间件)=>控制器=>渲染=>返回

能否在服务注册这一阶段主动监听HTTP服务,并在服务注册完成后,将服务注册的结果缓存到内存中?由HTTP服务接管后续的请求处理,这样就不需要每次请求都重新加载所有的文件和依赖?

答案是可以的, PHP许多年前就已经支持了Socket模块,实际上这就是上述Swoole/Webman正在做的事,
在广泛的实践中, 服务模式的性能提升是非常显著的,

这也为PHP开发者带来了更多的选择和挑战: 注重内存泄漏与生命周期的管理

服务模式的发展

上述服务模式也称为常驻内存模式PHP-CLI模式, PHP官方在这一领域也提供了广泛支持, 也有如Swoole Webman AMPHP 等这类许多优秀的先行者, 感谢他们在这一领域的贡献与推动,使得PHP从PC-WEB时代走向了Serverless时代也有一席之地, 也让PHP开发者面对游戏/物联网等这类高并发场景时,有了实践的可能性

Fiber与Parallel的出现

Fiber与Parallel随着PHP8.1的发布而出现, 为PHP在协程/多线程方面提供了更多的支持, 迄今为止,PHP8.1已经发布有3年之久, 经过多次迭代也象征着Fiber走向成熟的标志

正如上篇文章所讲述, 可以使用PRipple协程引擎为Laravel提速数倍,也使用了服务模式, 不同其他引擎,PRipple是一个基于PHP8.1的Fiber实现的100%PHP代码协程引擎

且服务模式还支持更多的高耦合开发手段

PRipple的食用方法

例(1) 使用协程MySQL并(兼容Eloquent)

PRipple支持将数据库driver从mysql替换为mysql-amp 实现协程MySQL并兼容ORM
目前使用该方法需要掌握足够的协程编程知识,需要注重事务的上下文隔离,未来会提供更多的文档与示例

例(2) 使用递延实现异步处理任务

public function index() : JsonResponse
{
    \P\defer(function(){
        // 异步处理耗时的发送邮件/通知等轻任务
    });

    return Response::json(['code' => 0, 'msg' => 'success']);
}

例(3) 高并发http-client请求(内置连接池)

public function index() : JsonResponse
{
    // 并发发起http请求
    for($i = 0; $i < 100; $i ++){
        \P\async(function(){
            $response = \P\Plugin::Guzzle()->get('https://www.baidu.com/');
            //TODO: 处理响应
        });
    }

    return Response::json(['code' => 0, 'msg' => 'success']);
}

例(4) 自定义服务

Ws.php

<?php declare(strict_types=1);

namespace App\Server;

use P\Net;
use Psc\Core\WebSocket\Server\Connection;
use Psc\Core\WebSocket\Server\Server;
use Psc\Worker\Command;
use Psc\Worker\Manager;
use Psc\Worker\Worker;

class WsWorker extends Worker
{
    private Server $wsServer;

    private array $connections = [];

    public function register(Manager $manager): void
    {
        // 主进程创建服务
        $this->wsServer = Net::WebSocket()->server('ws://127.0.0.1:8001', null);
    }

    public function boot(): void
    {

        $this->wsServer->onMessage(function (string $content, Connection $connection) {
            $connection->send("response > {$content}");
        });

        $this->wsServer->onConnect(function (Connection $connection) {
            $this->connections[$connection->getId()] = $connection;
        });

        $this->wsServer->onClose(function (Connection $connection) {
            unset($this->connections[$connection->getId()]);
        });

        // 服务进程监听
        $this->wsServer->listen();
    }

    // 监听其他进程发送来的指令
    public function onCommand(Command $workerCommand): void
    {
        if ($workerCommand->name === 'sendMessageToAll') {
            foreach ($this->connections as $connection) {
                $connection->send($workerCommand->arguments['message']);
            }
        }
    }

    public function getName(): string
    {
        return 'ws-server';
    }

    public function getCount(): int
    {
        return 1;
    }

    public function onReload(): void
    {
        exit(0);
    }
}

注册服务

class AppServiceProvider extends ServiceProvider
{
    public function boot(Manager $manager): void
    {
        $manager->addWorker(new \App\ServerWsWorker());
    }
}

使用方法

Route::post('/ws-xhr', function (Request $request, \Psc\Drive\Laravel\Worker $httpWorker) {
    // 构建指令
    $command = Command::make('sendMessageToAll', ['message' => 'post message ' . $request->post('message')]);

    // 跨服务服务发送指令
    $httpWorker->commandToWorker($command, 'ws-server');
});

例(5)多线程支持

待文档更新

总结

通过上述的例子, 我们可以看到服务模式的强大之处, 也可以看到服务模式的可行性与前景,
这也是我写这篇文章的初衷, 希望能够给大家带来一些启发与思考, 也希望能够有更多的朋友加入到这个领域中来

《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 10

厉害了。。。学习。。。

3周前 评论

:+1: :+1: :+1:

3周前 评论
fatrbaby

兄弟厉害。我觉得把命名这些再考究一下(考虑一些美学方面的事情),这个项目会成就辉煌。

3周前 评论
cclilshy (楼主) 3周前
fatrbaby

@cclilshy LaravelSymfony 等名称在四线三格中 上、中、下 上分布比较均衡,而主体在中间。但 PRipple 稍显头重而脚轻。
从读音上看, LaravelSymfony 等名称只有一个重音或者两个重音有一定距离,读起来很自然流畅,有节奏感。而 PRipple 连续两个重音,读起来有阻滞之感。如果去掉前缀,直接叫 Ripple 是个不错的选择。

Laravel

3周前 评论
cclilshy (楼主) 3周前
fatrbaby (作者) 3周前
还不出来 3周前
cclilshy (楼主) 3周前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!