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 场景。下面是一个基本用法示例:
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 分为两个区域:
- 静态核心: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';
}
这提供了:
- 访问隔离(仅 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
暂无评论。