引言
传统 PHP 的部署模型是「一台机器跑 N 个 FPM 进程」,低峰期大量 CPU 闲置、高峰期又要紧急扩容。Serverless 把这层抽象掉了:你只管交代码,Lambda 按请求拉起容器、按毫秒计费、闲时归零。Bref 就是 PHP 生态通往 Lambda 的那座桥——它提供了 PHP 运行时层、事件适配器和 Laravel 集成。本文讲清 Bref 的架构、部署、事件驱动、冷启动优化与 Octane 落地。
前置:部署运维、开发趋势与云原生、Laravel 内核。
目录
- 1. Serverless 与 PHP 的契合点
- 2. Bref 架构与运行时
- 3. 部署配置与 serverless.yml
- 4. HTTP 事件与 Lambda 函数
- 5. 事件驱动:队列、定时与存储
- 6. 冷启动与预热优化
- 7. Laravel Octane on Lambda
- 8. 存储、会话与状态
- 9. 成本、可观测与本地开发
- 10. 速查表与一句话记忆
- 延伸阅读
1. Serverless 与 PHP 的契合点
1.1 模型对比
| 维度 | PHP-FPM + Nginx | Lambda + Bref |
|---|---|---|
| 伸缩单位 | 机器/容器 | 请求(并发实例) |
| 计费 | 常驻小时费 | 请求数 + GB-秒 |
| 空闲成本 | 仍要付费 | 归零 |
| 冷启动 | 无(进程常驻) | 有(几十到几百 ms) |
| 状态 | 可写本地磁盘 | 默认无状态、/tmp 临时 |
1.2 为什么 PHP 特别适合 Serverless
- 共享无状态模型:PHP 天生「一次请求一次进程」,本就无跨请求状态,天然契合函数计算
- 启动快:解释执行 + OPcache,比 JVM/CLR 冷启动更轻
- 生态成熟:Laravel/Symfony 可直接跑,Composer 打包依赖
- 按需付费:中小流量站点成本可能比常驻 ECS 低一个数量级
1.3 主要代价
冷启动、单次 15 分钟上限、/tmp 非持久、并发模型带来的连接数爆炸(每个容器都要连数据库)。这些正是后文要解决的。
记忆:PHP 天生无状态、启动轻,是 Serverless 的天然适配者;代价是冷启动、超时上限、/tmp 非持久与「每容器一条数据库连接」的并发压力。
2. Bref 架构与运行时
2.1 Bref 由三部分组成
| 组件 | 作用 |
|---|---|
| Serverless Framework 插件 | 把 serverless.yml 翻译成 CloudFormation |
| Lambda Layers | 提供 PHP 二进制、扩展、FPM |
| 事件适配器 | 把 SQS/S3/Cron 等事件转成 PHP 可处理对象 |
2.2 三个核心运行时层
在 serverless.yml 里,runtime: php-82 表示用 PHP 8.2 的函数运行时,layers 再叠加 FPM 或 console 层:
| 层 | 用途 |
|---|---|
| php-82 | 函数式(CLI 风格),适合事件处理 |
| php-82-fpm | 内置 PHP-FPM,适合 HTTP |
| php-82-console | 命令行入口(artisan) |
2.3 Handler 是什么
Lambda 的入口是「handler」——一个 文件.函数 或一个实现 Bref\Event\Handler 的类:
// 最简单的 handler:接收事件数组,返回数组
return function (array $event, Context $context): array {
return ['statusCode' => 200, 'body' => 'hello'];
};
对 HTTP,Bref 直接用 public/index.php 作为 handler,由 FPM 层接管请求;对事件,则用 Bref\Event\Sqs\SqsHandler 这类类。
记忆:Bref = Serverless 插件 + PHP Lambda 层 + 事件适配器;三个层 php-82(函数)、php-82-fpm(HTTP)、php-82-console(CLI);handler 就是 Lambda 入口,HTTP 用 public/index.php,事件用 Bref\Event*Handler。
3. 部署配置与 serverless.yml
3.1 一个 Laravel 应用的最小配置
service: my-app
provider:
name: aws
region: us-east-1
runtime: php-82-fpm
environment:
APP_ENV: production
LOG_CHANNEL: stderr
plugins:
- ./vendor/bref/bref
functions:
web:
handler: public/index.php
timeout: 28 # API Gateway 上限 29 秒,留 1 秒余量
layers:
- ${bref:layer.php-82-fpm}
events:
- httpApi: '*'
artisan:
handler: artisan
timeout: 120
layers:
- ${bref:layer.php-82}
- ${bref:layer.php-82-console}
events:
- schedule:
rate: rate(1 minute)
3.2 关键点
plugins指向vendor/bref/bref,提供${bref:layer.*}变量- 日志必须走
stderr(CloudWatch),不能写文件 httpApi比http(REST API)更便宜、更简单,多数场景首选- 环境变量用
ssm或 Secrets Manager 注入,切勿提交.env
3.3 部署与调试命令
composer require bref/bref
vendor/bin/bref init # 生成 serverless.yml 骨架
serverless deploy # 部署到 AWS
serverless invoke local -f web # 本地模拟调用
记忆:serverless.yml 三件套——runtime 定 PHP 版本、layers 定运行时层、events 定触发器;日志走 stderr、密钥走 SSM,部署用 serverless deploy,本地用 invoke local。
4. HTTP 事件与 Lambda 函数
4.1 请求如何变成 PHP 请求
Lambda 收到 API Gateway 事件 → Bref 的 FPM 层启动 php-fpm → 把事件翻译成 CGI 环境变量 → 交给 public/index.php(Laravel 入口)→ 返回响应 → Bref 转成 API Gateway 响应格式。
4.2 路由与冷启动的关系
Lambda 的伸缩单位是「并发实例」:并发 100 就意味着最多 100 个容器同时冷启动。HTTP 场景要特别关注:
timeout: 28:API Gateway 29 秒硬上限- 请求体上限:HTTP API 10MB;大文件务必走 S3 直传(见文件上传专题)
- WebSocket 不支持:实时通信用 API Gateway WebSocket 或 Pusher
4.3 自定义 HTTP 函数
// src/health.php
return function (array $event, Context $context): array {
return [
'statusCode' => 200,
'body' => json_encode(['ok' => true, 'id' => $context->getAwsRequestId()]),
];
};
Context 提供 getAwsRequestId()、getRemainingTimeInMillis()——后者在长任务里用来判断「还剩多少时间」。
记忆:HTTP 路径 = API Gateway 事件 → Bref FPM 层 → public/index.php;牢记 29 秒超时、10MB 请求体、无 WebSocket;Context 给到 requestId 与剩余毫秒。
5. 事件驱动:队列、定时与存储
5.1 SQS 队列消费者
use Bref\Event\Sqs\SqsEvent;
use Bref\Event\Sqs\SqsHandler;
class ProcessOrder extends SqsHandler
{
public function handleSqs(SqsEvent $event, Context $context): void
{
foreach ($event->getRecords() as $record) {
$body = json_decode($record->getBody(), true);
Order::find($body['id'])?->process();
}
}
}
Laravel 的队列也能跑在 Lambda 上:用 SQS 事件触发一个处理器,内部调用 Artisan::call('queue:work', ['--max-jobs' => 1, '--stop-when-empty' => true]),把一次 Lambda 调用当成一次 Worker 生命周期。
5.2 S3 事件与定时任务
use Bref\Event\S3\S3Event;
use Bref\Event\S3\S3Handler;
class Thumbnailer extends S3Handler // S3 上传触发:生成缩略图
{
public function handleS3(S3Event $event, Context $context): void
{
foreach ($event->getRecords() as $record) {
Thumbnail::make($record->getObject()->getKey());
}
}
}
Cron 则由 events: - schedule: rate(1 hour) 声明,Bref 用 EventBridge 触发;注意 Lambda 的 Cron 是 UTC 时区,且没有「集群只跑一次」的保证(要用分布式锁兜底)。
5.3 事件处理器一览
| 事件源 | Bref 类 |
|---|---|
| SQS | Bref\Event\Sqs\SqsHandler |
| S3 | Bref\Event\S3\S3Handler |
| SNS | Bref\Event\Sns\SnsHandler |
| Kinesis | Bref\Event\Kinesis\KinesisHandler |
| DynamoDB Streams | Bref\Event\DynamoDb\DynamoDbHandler |
记忆:事件驱动 = 用 Bref\Event*Handler 承接 SQS/S3/SNS/Kinesis/DynamoDB 事件,Cron 用 schedule 事件;Laravel 队列可在 SQS 处理器里
queue:work --stop-when-empty跑完即退。
6. 冷启动与预热优化
6.1 冷启动由什么组成
冷启动 = 容器创建(AWS 侧,约 100~400ms,不可控)+ PHP 启动与加载扩展 + 框架 bootstrap(Laravel 服务提供者注册,最耗时)+ OPcache 编译。真正能优化的是后三项,其中框架 bootstrap 通常是大头。
6.2 六条优化手段
| 手段 | 效果 |
|---|---|
| 减少 Layer 体积 | 少装无用扩展,缩短下载/解压 |
| 预加载 preloading | 预热 OPcache 类缓存 |
config:cache / route:cache | 跳过配置与路由解析 |
| Provisioned Concurrency | 预留实例,冷启动归零(有成本) |
| Warmup 插件 | 定时 ping 保持容器热 |
| Octane 常驻 | 一次 bootstrap 服务多次请求 |
6.3 一个预加载配置
在 php.ini 里设置 opcache.preload=/var/task/preload.php,preload.php 中 require 框架核心类,即可把它们提前编译进共享内存。
6.4 什么时候不必纠结冷启动
后台任务(SQS/Cron)对冷启动不敏感——几百毫秒淹没在任务本身耗时里;只有面向用户的同步 HTTP 才值得为冷启动买单。
记忆:冷启动 = 容器创建 + PHP 启动 + 框架 bootstrap + OPcache;优化靠减层、预加载、config/route 缓存、Provisioned Concurrency、Warmup 与 Octane;只有同步 HTTP 才值得优化它。
7. Laravel Octane on Lambda
7.1 为什么需要 Octane
默认模型下,每次请求都要完整 bootstrap 一次 Laravel(注册上百个服务提供者)。Octane 把应用常驻在容器里,一次 bootstrap 服务多次请求,冷启动与单请求延迟都显著下降。
7.2 接入步骤
composer require bref/laravel-bridge laravel/octane
php artisan vendor:publish --tag=serverless-config
然后把 web 函数的 handler 换成 Octane 入口:
functions:
web:
handler: Bref\LaravelBridge\Http\OctaneHandler
runtime: php-82-fpm
layers:
- ${bref:layer.php-82-fpm}
environment:
BREF_LOOP_MAX: 100 # 单个容器最多处理 100 个请求后回收
events:
- httpApi: '*'
BREF_LOOP_MAX 是关键:容器不是永久常驻,处理若干请求后会被回收重建,避免「长跑容器」里的内存泄漏与状态累积。
7.3 常驻内存的三大陷阱
| 陷阱 | 后果 | 对策 |
|---|---|---|
| 静态属性/全局变量累积 | 请求间状态串味 | 不存请求态于静态变量 |
| 数据库连接失效 | 空闲后被 RDS 断开 | 连接池 + 重连 + 短 idle |
| 内存只增不减 | 容器 OOM | 监控 RSS,调小 BREF_LOOP_MAX |
Octane 把「每请求重建」换成了「常驻复用」——性能红利与状态泄漏风险是同一枚硬币的两面。
记忆:Octane on Lambda = 应用常驻 +
Bref\LaravelBridge\Http\OctaneHandler+BREF_LOOP_MAX定期回收;收益是一次 bootstrap 服务多请求,风险是静态状态串味与连接失效。
8. 存储、会话与状态
8.1 无状态是铁律
Lambda 容器随时会被销毁重建,任何写在本地磁盘的东西都不可靠(/tmp 只保证单次调用内存在,最多 512MB~10GB 可配)。
8.2 各类状态的落点
| 状态 | 方案 |
|---|---|
| 文件上传/静态资源 | S3(Flysystem 适配器) |
| 会话 Session | DynamoDB 或 Cookie 驱动 |
| 缓存 | ElastiCache / DynamoDB |
| 队列 | SQS |
| 大文件临时处理 | EFS 挂载 或 /tmp |
8.3 Laravel 侧配置
# .env —— 所有状态外置
FILESYSTEM_DISK=s3
SESSION_DRIVER=dynamodb
CACHE_STORE=dynamodb
QUEUE_CONNECTION=sqs
会话放 DynamoDB 而非本地文件,是因为同一个用户的两次请求可能落到不同容器上——没有共享存储,会话就会「丢」。
记忆:Lambda 无状态——文件进 S3、会话进 DynamoDB、缓存进 ElastiCache/DynamoDB、队列用 SQS;本地磁盘只有 /tmp 且随容器销毁,绝不能当持久存储。
9. 成本、可观测与本地开发
9.1 成本模型
Lambda 计费 = 请求次数(每百万约 0.2 美元)+ GB-秒(内存 × 时长)。要点:
- 内存调大 → 单次更快但单价更高,存在最优值(用 Lambda Power Tuning 找)
- 低流量场景比常驻实例便宜得多;高流量恒定负载则可能更贵
- API Gateway、CloudWatch Logs、NAT 网关常是「隐藏成本大户」
9.2 可观测性
| 关注点 | 工具 |
|---|---|
| 日志 | CloudWatch Logs(stdout/stderr) |
| 指标 | CloudWatch Metrics(Duration/Errors/Throttles) |
| 链路 | AWS X-Ray 或 OpenTelemetry |
| 告警 | CloudWatch Alarms + SNS |
日志务必输出 JSON 结构化日志,否则 CloudWatch 里根本没法按 requestId 检索。
9.3 本地开发
vendor/bin/bref local # 用 Docker 起本地 PHP 环境
serverless invoke local -f web --path event.json
serverless offline # 模拟 API Gateway
原则:本地开发用 Docker 复刻 Lambda 环境,不要指望 php -S 能还原运行时差异。
记忆:成本 = 请求数 + GB-秒,内存有最优值;可观测靠 CloudWatch(日志走 stderr 且结构化)+ X-Ray;本地用 bref local / serverless offline 复刻运行时。
10. 速查表与一句话记忆
| 需求 | 做法 |
|---|---|
| HTTP 应用 | runtime php-82-fpm + handler public/index.php |
| 事件函数 | runtime php-82 + Bref\Event*Handler |
| 定时任务 | events schedule rate(1 hour) |
| 队列消费 | SQS 事件 + queue:work –stop-when-empty |
| 冷启动 | 预加载 + config:cache + Octane |
| Octane 入口 | Bref\LaravelBridge\Http\OctaneHandler |
| 会话 | DynamoDB 驱动 |
| 文件 | S3(Flysystem) |
| 日志 | stderr + JSON |
| 本地 | bref local / serverless offline |
一句话记忆:Serverless PHP = Lambda 按请求伸缩 + Bref 提供运行时层(php-82 / php-82-fpm / php-82-console)与事件适配器;HTTP 走 public/index.php、事件走 Bref\Event*Handler、Cron 走 schedule;冷启动靠减层 + 预加载 + config 缓存 + Octane 常驻(BREF_LOOP_MAX 定期回收);状态一律外置——文件进 S3、会话进 DynamoDB、队列进 SQS。
延伸阅读
- 部署运维:Nginx、FPM 与 Docker
- 开发趋势与云原生部署
- Laravel 内核与服务容器
- 可观测性:日志、指标与链路追踪
- 性能调优:OPcache 与 JIT
- Bref 官方文档
- AWS Lambda PHP 运行时
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。