高并发请求需分层拆解:web层快速响应、缓存层挡重复读、数据库层防写冲突、队列层削峰填谷;laravel无开箱即用魔法,但每层均有明确控制点——如web层加缓存与字段精简、db层用锁或原子更新、redis启用持久连接、队列配多worker及超时协同。

高并发请求不是靠“加服务器”硬扛,而是分层拆解:Web 层快速响应、缓存层挡掉重复读、数据库层防写冲突、队列层削峰填谷。Laravel 本身不提供“开箱即用”的高并发魔法,但每层都有明确可落地的控制点。
Web 层别卡在 DB 查询上
用户发起请求后,第一关是控制器是否立刻查库。常见错误是 Product::where('sku', $sku)->first() 这类操作直接暴露在接口里,没缓存、没索引、没超时控制,1000 个并发进来就是 1000 次全表扫描或锁等待。
- 必须给高频查询加缓存:用
cache()->remember()包住 Eloquent 查询,TTL 设为业务可接受的陈旧窗口(比如商品信息设 60 秒) - 避免在请求中做耗时同步操作:HTTP 调用、文件读写、循环计算一律移出 Web 请求生命周期
- 用
select('id', 'name', 'stock')显式指定字段,别用select('*')——字段越多,锁行越宽、网络传输越慢、内存占用越高 - 检查慢查询日志,对
EXPLAIN显示type=ALL或rows远大于结果数的 SQL,补复合索引(例如WHERE status = ? AND created_at > ?就该建(status, created_at))
DB 层防止“读-改-写”覆盖
两个请求同时读到库存为 5,各自减 1 后都写回 4,结果变成 4 而不是 3——这不是事务没用,是没在读的时候就锁住那行。单纯 DB::transaction() 不解决竞态,必须配合显式加锁或原子更新。
- 悲观锁场景(如订单状态流转):用
where('id', $id)->lockForUpdate()->first(),且必须包在DB::transaction()里;别写成Product::find($id)->lockForUpdate(),因为find()已执行完查询,锁无效 - 原子更新场景(如库存扣减):优先用原生
DB::update('UPDATE products SET stock = stock - 1 WHERE id = ? AND stock >= 1', [$id]),它自带条件检查和行级原子性,不依赖锁,也不触发模型事件 - 乐观锁场景(如文章编辑):加
version字段,更新时WHERE id = ? AND version = ?,若affectedRows === 0则说明被别人抢先改了,业务层决定重试或提示冲突 - 所有锁操作必须设超时:MySQL 层调
innodb_lock_wait_timeout,PHP 层在事务里用DB::connection()->getPdo()->setAttribute(PDO::ATTR_TIMEOUT, 3)控制连接等待上限
Redis 缓存连接不能每次都新建
高并发下出现 “Connection refused” 或 Redis 延迟飙升,90% 是因为每个请求都新建 TCP 连接。Laravel 默认用 Predis,它不带连接池;PhpRedis 支持持久连接,但需手动开启且只在 FPM 子进程内复用。
- 确认已启用
phpredis扩展:php -m | grep redis,再在config/database.php的 redis 配置里加'persistent' => true - 如果跑在 Swoole 或 RoadRunner 等常驻进程环境,必须用 Predis + 自定义连接池工厂,否则每个协程都新建连接,连接数爆炸
- 别信“Laravel 原生支持连接池”——它没有。连接池得靠底层客户端(PhpRedis/Predis)或中间件(twemproxy)实现
- 缓存 key 命名要有业务粒度:
cache()->get('product:123:detail')比cache()->get('product_detail')安全得多,后者一锁全站
队列不是“加个 job 就完事”,而是消费能力要配平
把“下单”塞进队列,但只起一个 queue:work 进程,等于把 1000 个并发请求从 Web 层塞进单线程管道——只是延迟爆发点从接口移到了队列里。
- 并发消费靠多 worker:用 Supervisor 启多个
queue:work实例,numprocs=4表示最多 4 个任务并行处理 - 每个 worker 必须设
--timeout大于单任务最坏耗时(比如支付回调最长 30 秒,那就设--timeout=45),否则会被强制杀掉导致任务丢失 - Redis 队列驱动比 database 驱动快一个数量级,高并发必切;database 驱动在大量任务入队时会因表锁拖慢整个应用
- 队列任务里别再查缓存或 DB:任务体应尽量轻量,数据应在入队前查好、序列化传入;否则每个 worker 都重复查一次,缓存击穿风险翻倍
真正容易被忽略的,是各层超时时间的协同:Web 请求超时(Nginx fastcgi_read_timeout)、PHP 脚本超时(max_execution_time)、DB 连接超时(PDO::ATTR_TIMEOUT)、Redis 连接超时(redis.default.timeout)、队列任务超时(--timeout)——它们不能互相矛盾,否则会出现“前端已断连,后端还在死等”的静默失败。











