返回首页

Laravel 中的并发与 TrueAsync — 技术指南

本文描述了 Laravel 与 TrueAsync 的集成,以实现真正并发。详细讨论了上下文隔离机制、有状态服务的适配以及与 PDO Pool 的协作。该解决方案允许在单线程中无 Swoole 处理数百个请求。

TrueAsync 替代 Swoole:Laravel 中的并发架构
Advertisement 728x90

Laravel 中的 TrueAsync:无需 Swoole 实现真正并发架构

PHP 中的传统异步解决方案,如 Swoole 或 RoadRunner,需要对代码进行深度修改并引入函数着色问题。TrueAsync 提供了一种全新的方法——修改 Zend 核心实现透明异步。我们将详细讲解如何将 Laravel 适配到这一模型,解决有状态服务的问题,并实现对 I/O 密集型操作的并发处理。

TrueAsync 基础:透明异步的工作原理

TrueAsync 是修改了 Zend Engine、I/O 库和数据库操作的 PHP 核心替代品。其关键优势是消除了函数着色问题。与 Node.js 或 Python asyncio 不同,标准的 PHP 函数(如 PDO::query()、file_get_contents())在 I/O 等待期间会自动将控制权让给调度器。协程在 C 函数级别挂起,允许单线程处理数百个请求。

需要理解的重要一点:这不是多线程。该模型依赖于协作式多任务处理——在等待外部资源时进行上下文切换。对于 CPU 密集型任务(繁重计算)效率低下,但非常适合涉及数据库和 API 调用的典型 Web 场景。下面是一个基本用法示例:

Google AdInline article slot
use function Async\spawn;
use function Async\await;
use function Async\delay;

$a = spawn(function() {
    delay(100);
    return 'result A';
});

$b = spawn(function() {
    delay(100);
    return 'result B';
});

// Obschee time vypolneniya ~100ms
 echo await($a); 
echo await($b);

Laravel 中有状态服务的问题

Laravel 最初是为 FPM 模型设计的,每个请求都在隔离的进程中处理。有状态服务(AuthManager、Session、Router)将状态存储在对象属性中,请求处理后即销毁。在异步模式下,进程无限期存活,导致状态泄漏:

// Coroutine A: request from user_1
Auth::loginUsingId(1);
delay(200);

// Coroutine B: request from user_2  
Auth::loginUsingId(2);

// Return in korutinu A
echo Auth::id(); // Vozvraschaet 2 vmesto 1

静态属性(Model::$dispatcher、Facade::$resolvedInstance)和 IoC 容器中的服务在所有请求间共享。Octane 通过克隆容器来应对此问题,但这会引入开销,且无法覆盖静态属性。

上下文隔离:解决方案架构

TrueAsync 提供了 current_context()——一个与请求生命周期绑定的数据存储。协程完成时,上下文会自动清除。该架构将 Laravel 分为两个区域:

Google AdInline article slot
  • 静态核心:router、config、IoC 容器。只加载一次,在所有请求间共享。
  • 每个请求的服务:Auth、Session、Cookie。在每个协程的上下文中创建。

关键机制是通过 AsyncApplication::resolve() 拦截服务解析:

protected function resolve($abstract, ...)
{
    if ($this->asyncMode && $this->isScoped($abstract)) {
        $ctx = current_context();
        
        if ($instance = $ctx->find($abstract)) {
            return $instance;
        }
        
        $instance = $this->buildFresh($abstract);
        $ctx->set($abstract, $instance);
        return $instance;
    }
    
    return parent::resolve($abstract, ...);
}

通过 Enum 和 Facade 解决方案实现上下文键

为避免碰撞并确保安全访问,使用 enum 键:

enum ScopedService: string
{
    case REQUEST     = 'request';
    case SESSION     = 'session';
    case AUTH        = 'auth';
    case AUTH_DRIVER = 'auth.driver';
    case COOKIE      = 'cookie';
}

这提供了:

Google AdInline article slot
  • 访问隔离(仅 enum 所有者可读写数据)
  • 无碰撞保证(即使字符串值相同)
  • 静态分析支持(PHPStan/IDE 跟踪使用)

Facades 因为在静态数组中缓存 resolvedInstance,需要单独解决方案。引入 ScopedServiceProxy:

class ScopedServiceProxy
{
    public function __call($method, $args)
    {
        return ($this->resolver)()->$method(...$args);
    }
}

代理将调用转发到当前协程的上下文,同时在代理级别维护缓存。

关键适配和 PDO 连接池

基本隔离后,发现内部存储每个请求状态的服务,需要专用的异步版本:

  • AsyncRouter(当前路由隔离)
  • AsyncDispatcher(每个协程延迟事件)
  • AsyncTranslator(翻译语言环境)
  • AsyncViewFactory(View::share())
  • AsyncConfig(运行时配置更改)
  • AsyncDatabaseSessionHandler(原子会话写入)
  • Async*Connection(事务计数器)

数据库处理需要特别注意。没有连接池,并发协程查询会破坏 PostgreSQL 协议。TrueAsync 通过内置 PDO 连接池解决:

$pdo = new PDO($dsn, $user, $password, [
    PDO::ATTR_POOL_ENABLED              => true,
    PDO::ATTR_POOL_MIN                  => 0,
    PDO::ATTR_POOL_MAX                  => 10,
    PDO::ATTR_POOL_HEALTHCHECK_INTERVAL => 30,
]);

连接池动态分配连接:SELECT 结果返回后,连接归还池中,可服务超过池最大大小的协程。对于事务,连接锁定直到 commit/rollback。

关键要点

  • TrueAsync 通过 PHP 核心修改消除函数着色
  • Laravel 的有状态服务需要通过 current_context() 实现上下文隔离
  • PDO 连接池对于并发环境下的正确数据库操作至关重要
  • Enum 键确保上下文数据的安全性和可追溯性
  • 该解决方案无法取代 CPU 密集型任务的多线程

— Editorial Team

Advertisement 728x90

继续阅读