PHP实现AI异步响应的核心是网络I/O非阻塞处理:用curl_multi_exec并发请求并轮询完成句柄触发回调,或用ReactPHP流式处理SSE/chunked响应;回调须隔离状态、避免副作用,结果应投递至Redis等中间件而非直接输出。

PHP 本身没有原生的 AI 异步响应机制,所谓“AI 异步响应”本质是调用外部 AI 服务(如 OpenAI API、本地大模型 HTTP 接口)时的网络 I/O 异步处理问题。直接 file_get_contents 或 curl_exec 会阻塞,必须用非阻塞方式发起请求,并在结果就绪后触发回调逻辑。
用 curl_multi_exec 实现并发请求 + 回调分发
这是最轻量、无需额外扩展、兼容 PHP 7.2+ 的方案。关键不是“让 PHP 主动回调 AI”,而是你主动轮询多个 cURL 句柄,等某个完成就立即执行对应回调函数。
-
curl_multi_init()创建批处理句柄,curl_multi_add_handle()加入多个 AI 请求(比如同时调用文本生成 + 嵌入向量) - 用
curl_multi_exec()非阻塞执行,配合curl_multi_select()等待 socket 就绪(避免忙轮询) - 一旦
curl_multi_info_read()返回完成句柄,立刻提取curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)和curl_multi_getcontent($ch),再根据 URL 或自定义 tag 匹配预设的$callbacks数组调用对应函数 - 注意:回调函数必须能接收
$response和$error两个参数,因为网络失败也需通知
示例片段:
$mh = curl_multi_init();
$callbacks = [];
foreach ($ai_requests as $id => $url) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 30000);
curl_multi_add_handle($mh, $ch);
$callbacks[$ch] = fn($resp, $err) => handle_ai_result($id, $resp, $err);
}
while ($active && curl_multi_exec($mh, $active) == CURLM_OK) {
if ($msg = curl_multi_info_read($mh)) {
$ch = $msg['handle'];
$resp = curl_multi_getcontent($ch);
$err = curl_error($ch);
$callbacks[$ch]($resp, $err);
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
}
curl_multi_close($mh);
用 ReactPHP 处理长连接流式 AI 响应(如 SSE / chunked)
当 AI 接口支持流式返回(text/event-stream 或分块 Transfer-Encoding: chunked),就不能等整个响应体再回调——需要边收边触发回调。ReactPHP 的 React\Http\Browser 天然支持流式处理。
立即学习“PHP免费学习笔记(深入)”;
- 必须用
$browser->request()而非get(),拿到Response对象后监听data事件 - 回调函数里对每段
$chunk做解析(如提取data: {...}中的 JSON),并累积或实时推送前端 - 别忘了监听
end和error事件,否则流中断不会被感知 - 注意:ReactPHP 是单进程事件循环,不能混用
sleep、mysqli_query等阻塞调用,否则整个服务卡死
关键代码节选:
$browser->request('POST', $ai_url, $headers, $body)
->then(function (Response $response) use ($on_chunk) {
$response->on('data', function ($chunk) use ($on_chunk) {
// 解析 chunk,例如 preg_match('/data: (.*)/', $chunk, $m) && $on_chunk(json_decode($m[1]));
});
$response->on('end', fn() => $on_chunk(null, 'done'));
$response->on('error', fn($e) => $on_chunk(null, 'error: ' . $e->getMessage()));
});
回调函数设计必须隔离状态与副作用
很多人把回调写成闭包内直接 echo 或更新全局变量,这在并发场景下必然出错。真正的回调应只做三件事:解析、转换、投递。
- 不直接输出:回调内不要
echo、header()、session_write_close()—— 这些属于响应层,应由主流程统一控制 - 不操作共享资源:避免在回调里改
$_SESSION、写同一张数据库表的同一行;若必须写,用INSERT ... ON DUPLICATE KEY UPDATE或加SELECT ... FOR UPDATE - 推荐模式:回调只把结果推到
Redis的PUB/SUB频道,或写入job_id命名的缓存键(redis->set("ai_result:$job_id", json_encode($data))),前端用轮询或 WebSocket 主动拉取 - 错误回调要带上下文:传入原始请求参数、HTTP 状态码、cURL 错误码(
CURLE_COULDNT_CONNECT)、超时时间,否则无法区分是模型崩了还是网络断了
真正难的不是写回调函数,而是保证它被调用时,PHP 进程还没退出、内存没释放、数据库连接还活着——所以所有异步方案都要确认生命周期管理:pcntl_fork 子进程要 exit 干净,curl_multi 要及时 curl_close,ReactPHP 要正确 Loop::stop()。漏掉任何一环,都会导致回调静默失败,且无日志可查。



















