Laravel 11 本身不提供原生协程或异步 I/O 能力,“异步”实际依赖队列(dispatch)+ 多 worker 进程实现;真正并发请求须用 Guzzle 的 requestAsync() + Promise,且不可混用 $request、session()、auth() 等已销毁的请求上下文。

直接说结论:Laravel 11 本身不提供原生协程或异步 I/O 能力,所谓“异步”在 Laravel 中几乎全靠队列(dispatch())+ 多 worker 进程实现;真正的并发请求(如同时调用多个外部 API)必须依赖 Guzzle 的 requestAsync() + Promise,且不能混用 Laravel 的请求上下文($request、session()、auth())。
为什么 queue:work 启多个进程才算真并发
Laravel 队列消费是单线程模型:一个 php artisan queue:work 进程一次只取一个 job、串行执行。哪怕你用 Redis 驱动,单个 worker 也无法并行处理任务。
- 启动 4 个 worker(
numprocs=4)≠ 同时跑 4 个 PHP 进程 —— 它们各自独立,互不阻塞,这才是实际并发数 - database 驱动下,worker 抢任务靠
SELECT FOR UPDATE,高并发时容易锁表、堆积;Redis 驱动用BRPOP或LPOP,无锁,响应更快 -
--timeout=90必须大于你最慢任务的实际耗时,否则会被强制终止,导致任务重复入队或失败 - 别信
queue:listen—— 它已废弃多年,只轮询不长连接,延迟高、资源浪费,生产环境禁用
Guzzle 并发请求不能直接用 $request 或 session()
你在控制器里写 $client->requestAsync('GET', '/api/user'),这个请求是在当前 HTTP 进程里发起的,但回调(then())执行时,Laravel 的请求生命周期早已结束 —— $request、session()、auth()->user() 全部失效。
- 正确做法:把需要的数据提前取出,比如
$userId = auth()->id()、$token = session('api_token'),作为变量传进then()闭包 - 错误写法:
then(function () { return auth()->user()->name; })→ 报错Call to a member function user() on null - 别在
then()里调 Eloquent 模型方法(如User::find()),除非你手动重新初始化数据库连接 - 并发请求数量建议控制在 5–10 个以内,太多会触发 Guzzle 默认的连接池限制(
max_connections=10),需显式配置handler
队列任务里传模型实例是常见翻车点
写 SendInvoice::dispatch($order) 看似方便,但 $order 是 Eloquent 实例,序列化时会把整个对象(含关系、查询构造器、连接句柄)塞进 jobs.payload 字段,既膨胀又危险。
- 正确姿势:只传主键,
SendInvoice::dispatch($order->id),进handle()再查Order::findOrFail($this->orderId) - 如果必须传关联数据(比如用户邮箱),提前取出来:
SendInvoice::dispatch($order->id, $order->user->email) - 构造函数里不做 DB 查询、日志、缓存操作 —— 它在 dispatch 时就执行了,不是消费时;
handle()才是真正干活的地方 - 用
implements ShouldQueue是必须的,但光加接口没用:若QUEUE_CONNECTION=sync,任务根本不会进队列,而是立刻同步执行,压测时完全失真
并发写同一张表?别只靠事务,得加锁
比如秒杀场景,100 个请求同时扣库存,DB::transaction() 只能保证语句原子性,无法防止两个事务读到相同库存值后都判断“有货”再扣减 —— 这就是典型的竞态条件。
- 悲观锁才是解法:
$product = Product::where('id', $id)->lockForUpdate()->first(),它会在 SELECT 时加行锁,其他事务必须等它 commit 才能读 - 乐观锁适合低冲突场景,但 Laravel 没内置支持,得自己加
version字段 +where('version', $oldVersion)更新,失败后重试 - Redis 原子操作可作补充:用
INCRBY stock:123 -1判断返回值是否 ≥ 0,比数据库锁更轻量,但需额外维护一致性 - 队列不是并发安全的银弹:100 个
DecreaseStock::dispatch($productId)仍可能被多个 worker 同时消费,锁必须落在业务逻辑层,不能指望队列自动排队
真正难的不是启动几个 worker 或写几个 requestAsync(),而是分清「并发执行」和「并发安全」—— 前者靠进程/协程数量堆,后者靠锁、事务、幂等设计兜底。很多线上事故,都是以为开了 8 个 worker 就万事大吉,结果库存还是超卖了。



















