Laravel处理外部请求避免Worker闲置:Http::async实测遇阻
Laravel Job异步处理长耗时外部请求方案
Laravel原生完全支持你要的需求,核心思路是让发起请求的Job尽早结束以释放Worker,然后通过两种方式触发后续的ProcessResponse任务:
场景一:外部服务支持Webhook(最优方案)
如果外部服务支持回调通知,这是最高效的方式,不需要轮询浪费资源:
- 发起请求的Job只负责触发外部任务,带上你的Webhook回调地址后直接结束
- 外部服务处理完成后主动调用你的Webhook接口,接口再触发
ProcessResponse任务
发起请求的Job代码
<?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Http; class InitiateExternalRequest implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function handle() { // 发起外部请求,携带回调地址 Http::post('https://external-service.com/long-task', [ 'callback_url' => route('webhooks.external-response'), // 其他业务参数 ]); // 直接结束Job,Worker立刻被释放去处理其他任务 } }
Webhook接口实现
先在routes/api.php加路由:
Route::post('/webhooks/external-response', [WebhookController::class, 'handleExternalResponse'])->name('webhooks.external-response');
然后写控制器逻辑:
<?php namespace App\Http\Controllers; use App\Jobs\ProcessResponse; use Illuminate\Http\Request; class WebhookController extends Controller { public function handleExternalResponse(Request $request) { // 务必添加Webhook签名验证,防止恶意请求 // 比如根据外部服务提供的签名规则校验请求合法性 // 触发响应处理Job ProcessResponse::dispatch($request->all()); return response()->json(['status' => 'success']); } }
场景二:外部服务不支持Webhook(轮询方案)
如果外部服务只能通过查询接口获取状态,就用任务释放+轮询的方式:
- 第一次执行Job时发起请求,拿到任务ID后释放Job(设置延迟时间)
- 延迟时间到后,Job再次执行,查询任务状态:未完成则继续释放,完成则触发
ProcessResponse
轮询版Job代码
<?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Http; class InitiateExternalRequest implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $taskId; // 构造函数接收任务ID(第一次执行时为null) public function __construct($taskId = null) { $this->taskId = $taskId; } public function handle() { if (!$this->taskId) { // 第一次执行:发起长耗时请求,获取任务ID $response = Http::post('https://external-service.com/long-task', [ // 业务参数 ]); $this->taskId = $response->json('task_id'); // 释放任务,10秒后再次执行(根据实际情况调整延迟时间) $this->release(10); return; } // 后续执行:查询任务状态 $statusRes = Http::get("https://external-service.com/tasks/{$this->taskId}/status"); $status = $statusRes->json('status'); if (in_array($status, ['pending', 'processing'])) { // 任务未完成,继续释放轮询 $this->release(10); return; } if ($status === 'completed') { // 获取任务结果 $result = Http::get("https://external-service.com/tasks/{$this->taskId}/result")->json(); // 触发处理Job ProcessResponse::dispatch($result); // 任务完成,结束当前Job return; } // 任务失败,标记当前Job为失败 $this->fail(new \Exception("外部任务执行失败,状态:{$status}")); } }
为什么Http::async()达不到你的需求?
Http::async()是客户端层面的异步请求,本质上还是在当前Job进程中执行——如果不调用$promise->wait(),Job进程会直接结束,promise还没完成就被销毁,自然触发不了后续逻辑;如果调用wait(),进程会阻塞直到请求完成,Worker还是被占用,完全失去了异步释放的意义。
内容的提问来源于stack exchange,提问作者Seth
相关产品推荐
相关产品推荐

